From f2371e3bdaa94c3da6a3cbe2543a83c4a1a1716b Mon Sep 17 00:00:00 2001 From: Camembear Date: Thu, 16 Apr 2026 17:47:12 -0400 Subject: [PATCH 01/21] feat(cli): add console subcommand for operator RPC access Merge reth-console into bera-reth as `bera-reth console`. Provides an interactive REPL and one-shot --exec mode for RPC calls over IPC, HTTP, or WebSocket. Pre-registers beraAdmin aliases when PoG is available. Bypasses reth tracing/Prometheus init for clean REPL startup. BERA-233 --- Cargo.lock | 101 ++++++ Cargo.toml | 12 +- src/berachain_cli.rs | 58 ++++ src/console/cli.rs | 61 ++++ src/console/command.rs | 299 ++++++++++++++++++ src/console/endpoint.rs | 78 +++++ src/console/engine.rs | 144 +++++++++ src/console/exec.rs | 32 ++ src/console/mod.rs | 16 + src/console/output.rs | 556 ++++++++++++++++++++++++++++++++++ src/console/query.rs | 188 ++++++++++++ src/console/repl.rs | 314 +++++++++++++++++++ src/console/rpc.rs | 396 ++++++++++++++++++++++++ src/console/rpc_completion.rs | 401 ++++++++++++++++++++++++ src/console/run.rs | 76 +++++ src/lib.rs | 2 + src/main.rs | 61 +++- 17 files changed, 2777 insertions(+), 18 deletions(-) create mode 100644 src/berachain_cli.rs create mode 100644 src/console/cli.rs create mode 100644 src/console/command.rs create mode 100644 src/console/endpoint.rs create mode 100644 src/console/engine.rs create mode 100644 src/console/exec.rs create mode 100644 src/console/mod.rs create mode 100644 src/console/output.rs create mode 100644 src/console/query.rs create mode 100644 src/console/repl.rs create mode 100644 src/console/rpc.rs create mode 100644 src/console/rpc_completion.rs create mode 100644 src/console/run.rs diff --git a/Cargo.lock b/Cargo.lock index 09321e79..6ad7a6f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1471,16 +1471,20 @@ dependencies = [ "bytes", "clap", "derive_more", + "dirs", "eyre", + "http", "jsonrpsee", "jsonrpsee-core", "jsonrpsee-proc-macros", + "libc", "modular-bitfield", "reth", "reth-basic-payload-builder", "reth-chainspec", "reth-cli", "reth-cli-commands", + "reth-cli-runner", "reth-cli-util", "reth-codecs", "reth-consensus-common", @@ -1510,13 +1514,16 @@ dependencies = [ "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-storage-api", "reth-transaction-pool", "reth-trie-common", "revm-inspectors", + "rustyline", "serde", "serde_json", "sha2", + "tempfile", "test-fuzz", "thiserror 2.0.18", "tokio", @@ -2123,6 +2130,15 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "coins-bip32" version = "0.12.0" @@ -3015,6 +3031,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enr" version = "0.13.0" @@ -3103,6 +3125,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + [[package]] name = "ethereum_hashing" version = "0.7.0" @@ -3225,6 +3253,17 @@ dependencies = [ "bytes", ] +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "fdlimit" version = "0.3.0" @@ -3845,6 +3884,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "1.4.0" @@ -5175,6 +5223,27 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -6220,6 +6289,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -9713,6 +9792,28 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "rustyline" +version = "17.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.60.2", +] + [[package]] name = "ryu" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 21942def..1707eef4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,15 @@ alloy-sol-types = "1.5.6" bytes = "1.10.1" clap = { version = "4.5.40", features = ["derive"] } derive_more = "2.0.1" +dirs = "6.0.0" eyre = "0.6.12" +http = "1.3.1" +libc = "0.2.177" +rustyline = "17.0.2" sha2 = "0.10" # rpc -jsonrpsee = "0.26.0" +jsonrpsee = { version = "0.26.0", features = ["http-client", "ws-client"] } jsonrpsee-core = { version = "0.26.0", features = ["server"] } jsonrpsee-proc-macros = "0.26.0" @@ -36,6 +40,7 @@ reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } +reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-codecs = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } @@ -63,12 +68,13 @@ reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3 reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } +reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } serde = { version = "1.0", features = ["derive"], default-features = false } serde_json = "1.0" thiserror = "2.0" -tokio = "1.46.0" +tokio = { version = "1.46.0", features = ["macros", "rt-multi-thread", "net", "io-util"] } tracing = "0.1.41" [dev-dependencies] @@ -76,10 +82,12 @@ alloy-provider = "1.6.3" alloy-rpc-client = "1.6.3" alloy-rpc-types-trace = "1.6.3" eyre = "0.6.12" +jsonrpsee = { version = "0.26.0", features = ["server"] } reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } reth-trie-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } revm-inspectors = "0.34.2" +tempfile = "3.15.0" test-fuzz = "7" [build-dependencies] diff --git a/src/berachain_cli.rs b/src/berachain_cli.rs new file mode 100644 index 00000000..ee2cdc68 --- /dev/null +++ b/src/berachain_cli.rs @@ -0,0 +1,58 @@ +//! Berachain-specific CLI subcommands (extension to reth Ethereum CLI). + +use clap::Subcommand; +use reth_cli_runner::CliRunner; +use reth_ethereum_cli::app::ExtendedCommand; + +#[derive(Debug, Subcommand)] +pub enum BerachainSubcommands { + /// JSON-RPC console over IPC, HTTP, or WebSocket. + Console(crate::console::ConsoleCommand), +} + +impl ExtendedCommand for BerachainSubcommands { + fn execute(self, runner: CliRunner) -> eyre::Result<()> { + match self { + Self::Console(cmd) => cmd.run(runner), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::chainspec::BerachainChainSpecParser; + use clap::Parser; + use reth_cli_commands::node::NoArgs; + use reth_ethereum_cli::interface::{Cli, Commands}; + use reth_rpc_server_types::DefaultRpcModuleValidator; + + #[test] + fn parses_console_subcommand() { + let err = Cli::< + BerachainChainSpecParser, + NoArgs, + DefaultRpcModuleValidator, + BerachainSubcommands, + >::try_parse_from(["bera-reth", "console", "--help"]) + .unwrap_err(); + assert_eq!(err.kind(), clap::error::ErrorKind::DisplayHelp); + } + + #[test] + fn ext_console_variant_reachable() { + let cli = Cli::< + BerachainChainSpecParser, + NoArgs, + DefaultRpcModuleValidator, + BerachainSubcommands, + >::try_parse_from(["bera-reth", "console", "--exec", "eth_blockNumber"]) + .unwrap(); + match cli.command { + Commands::Ext(BerachainSubcommands::Console(ref c)) => { + assert_eq!(c.exec.as_deref(), Some("eth_blockNumber")); + } + _ => panic!("expected console ext"), + } + } +} diff --git a/src/console/cli.rs b/src/console/cli.rs new file mode 100644 index 00000000..822341aa --- /dev/null +++ b/src/console/cli.rs @@ -0,0 +1,61 @@ +use clap::Args; +use reth_cli_runner::CliRunner; + +/// JSON-RPC console (IPC, HTTP, or WebSocket). +#[derive(Debug, Clone, Args)] +pub struct ConsoleCommand { + /// IPC path, or `http(s)://…`, or `ws(s)://…`. If omitted, uses the platform default + /// datadir with `reth.ipc`. + #[arg(value_name = "ENDPOINT")] + pub endpoint: Option, + + /// Run a single command and print raw JSON (implies raw output; no prompts). + #[arg(long = "exec")] + pub exec: Option, + + /// In REPL mode, print raw JSON instead of tables and annotations. + #[arg(long)] + pub raw: bool, +} + +impl ConsoleCommand { + pub fn run(self, runner: CliRunner) -> eyre::Result<()> { + runner.block_on(super::run::run_console(self)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + #[derive(clap::Parser)] + #[command(name = "bera-reth")] + struct Top { + #[command(subcommand)] + sub: Sub, + } + + #[derive(clap::Subcommand)] + enum Sub { + Console(ConsoleCommand), + } + + #[test] + fn parses_exec_and_raw() { + let Top { sub: Sub::Console(c) } = + Top::try_parse_from(["bera-reth", "console", "--exec", "eth.blockNumber", "--raw"]) + .unwrap(); + assert_eq!(c.exec.as_deref(), Some("eth.blockNumber")); + assert!(c.raw); + assert!(c.endpoint.is_none()); + } + + #[test] + fn parses_positional_endpoint() { + let Top { sub: Sub::Console(c) } = + Top::try_parse_from(["bera-reth", "console", "/tmp/reth.ipc"]).unwrap(); + assert_eq!(c.endpoint.as_deref(), Some("/tmp/reth.ipc")); + assert!(!c.raw); + } +} diff --git a/src/console/command.rs b/src/console/command.rs new file mode 100644 index 00000000..48521a82 --- /dev/null +++ b/src/console/command.rs @@ -0,0 +1,299 @@ +use eyre::{Result, eyre}; +use serde_json::Value; + +#[derive(Debug, Clone, PartialEq)] +pub enum InputCommand { + Empty, + Help, + Exit, + Query(String), + Rpc { method: String, params: Option }, + RpcWithQuery { method: String, params: Option, query: String }, + Alias(String), +} + +pub fn parse_input(line: &str) -> Result { + let line = line.trim(); + if line.is_empty() { + return Ok(InputCommand::Empty); + } + if line == "help" || line == "?" { + return Ok(InputCommand::Help); + } + if line == "exit" || line == "quit" { + return Ok(InputCommand::Exit); + } + if line.starts_with('.') { + return Ok(InputCommand::Query(line.to_owned())); + } + if let Some((rpc_part, query)) = split_rpc_query_tail(line) { + let (method, params_raw) = split_method_and_params(rpc_part)?; + let params = parse_params(params_raw)?; + return Ok(InputCommand::RpcWithQuery { method, params, query }); + } + if looks_like_implicit_rpc(line) { + return parse_rpc(line); + } + Ok(InputCommand::Alias(line.to_owned())) +} + +fn split_rpc_query_tail(line: &str) -> Option<(&str, String)> { + if line.contains(char::is_whitespace) || line.contains('(') { + return None; + } + + let query_start = if let Some(p1) = line.find('.') { + let before_dot = &line[..p1]; + if before_dot.contains('_') { + // Underscore-style method (e.g. admin_peers): first '.' or '[' is query start. + let first_bracket = line.find('['); + match first_bracket { + Some(b) if b < p1 => Some(b), + _ => Some(p1), + } + } else { + // Dot-style method (e.g. admin.peers): consume two segments, remainder is query. + let after_p1 = p1 + 1; + let seg2_len = line[after_p1..] + .find(|c: char| ['.', '['].contains(&c)) + .unwrap_or(line.len() - after_p1); + let seg2_end = after_p1 + seg2_len; + if seg2_end < line.len() { Some(seg2_end) } else { None } + } + } else { + line.find('[') + }?; + + let rpc_part = &line[..query_start]; + let raw_tail = &line[query_start..]; + if raw_tail.is_empty() { + return None; + } + let query = + if raw_tail.starts_with('[') { format!(".{raw_tail}") } else { raw_tail.to_owned() }; + Some((rpc_part, query)) +} + +fn parse_rpc(rest: &str) -> Result { + let rest = rest.trim(); + if rest.is_empty() { + return Err(eyre!("usage: [json_params]")); + } + let (method, params_raw) = split_method_and_params(rest)?; + let parsed_params = parse_params(params_raw)?; + Ok(InputCommand::Rpc { method, params: parsed_params }) +} + +fn looks_like_implicit_rpc(line: &str) -> bool { + // Accept direct method calls like: + // - eth_getBalance ["0x...", "latest"] + // - eth.getBalance ["0x...", "latest"] + // Keep simple alias calls like eth.blockNumber as aliases. + line.contains(char::is_whitespace) || + (line.contains('(') && line.ends_with(')')) || + (line.contains('_') && !line.contains(' ')) +} + +fn split_method_and_params(input: &str) -> Result<(String, Option<&str>)> { + let input = input.trim(); + if let Some(paren_start) = input.find('(') { + let method = input[..paren_start].trim(); + if method.is_empty() { + return Err(eyre!("missing method name")); + } + if !input.ends_with(')') { + return Err(eyre!("unbalanced parentheses in method call")); + } + let inner = &input[paren_start + 1..input.len() - 1]; + let params = if inner.trim().is_empty() { None } else { Some(inner.trim()) }; + return Ok((method.to_owned(), params)); + } + + let mut split = input.splitn(2, char::is_whitespace); + let method = split.next().unwrap_or_default().trim(); + if method.is_empty() { + return Err(eyre!("missing method name")); + } + let params = split.next().map(str::trim).filter(|s| !s.is_empty()); + Ok((method.to_owned(), params)) +} + +fn parse_params(params_raw: Option<&str>) -> Result> { + let Some(raw) = params_raw else { + return Ok(None); + }; + let mut s = raw.trim(); + while s.starts_with('(') && s.ends_with(')') && s.len() >= 2 { + s = s[1..s.len() - 1].trim(); + } + if s.is_empty() { + return Ok(None); + } + + // If wrapped as a JSON array, use it directly as RPC positional params. + if s.starts_with('[') && s.ends_with(']') { + return Ok(Some(serde_json::from_str::(s)?)); + } + // If wrapped as a JSON object, pass as named params. + if s.starts_with('{') && s.ends_with('}') { + return Ok(Some(serde_json::from_str::(s)?)); + } + + // Function-style "a, b, c" => parse as positional params. + let array_form = format!("[{s}]"); + if let Ok(v) = serde_json::from_str::(&array_form) { + return Ok(Some(v)); + } + + // Single scalar JSON value fallback. + if let Ok(v) = serde_json::from_str::(s) { + return Ok(Some(Value::Array(vec![v]))); + } + + Err(eyre!("unable to parse params; use JSON values, e.g. [\"0x...\", \"latest\"]")) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn parses_rpc_with_params() { + let cmd = parse_input(r#"eth_getBlockByNumber ["latest", false]"#).unwrap(); + assert_eq!( + cmd, + InputCommand::Rpc { + method: "eth_getBlockByNumber".to_owned(), + params: Some(json!(["latest", false])), + } + ); + } + + #[test] + fn parses_query() { + let cmd = parse_input(".count").unwrap(); + assert_eq!(cmd, InputCommand::Query(".count".to_owned())); + } + + #[test] + fn parses_implicit_rpc_with_params() { + let cmd = parse_input(r#"eth.getBalance ["0xabc", "latest"]"#).unwrap(); + assert_eq!( + cmd, + InputCommand::Rpc { + method: "eth.getBalance".to_owned(), + params: Some(json!(["0xabc", "latest"])), + } + ); + } + + #[test] + fn parses_parenthesized_call_with_array() { + let cmd = parse_input(r#"eth.getBalance(["0xabc", "latest"])"#).unwrap(); + assert_eq!( + cmd, + InputCommand::Rpc { + method: "eth.getBalance".to_owned(), + params: Some(json!(["0xabc", "latest"])), + } + ); + } + + #[test] + fn parses_parenthesized_call_without_array() { + let cmd = parse_input(r#"eth.getBalance("0xabc", "latest")"#).unwrap(); + assert_eq!( + cmd, + InputCommand::Rpc { + method: "eth.getBalance".to_owned(), + params: Some(json!(["0xabc", "latest"])), + } + ); + } + + #[test] + fn parses_empty_as_empty_command() { + assert_eq!(parse_input(" ").unwrap(), InputCommand::Empty); + } + + #[test] + fn keeps_dot_alias_as_alias_when_no_params() { + assert_eq!( + parse_input("eth.blockNumber").unwrap(), + InputCommand::Alias("eth.blockNumber".to_owned()) + ); + } + + #[test] + fn errors_on_unbalanced_parentheses() { + let err = parse_input(r#"eth.getBalance("0xabc", "latest""#).unwrap_err(); + assert!(err.to_string().contains("unbalanced parentheses")); + } + + #[test] + fn errors_on_invalid_params() { + let err = parse_input(r#"eth_getBalance [broken"#).unwrap_err(); + assert!(err.to_string().contains("unable to parse params")); + } + + #[test] + fn chained_dot_count() { + let cmd = parse_input("admin.peers.count").unwrap(); + assert_eq!( + cmd, + InputCommand::RpcWithQuery { + method: "admin.peers".to_owned(), + params: None, + query: ".count".to_owned(), + } + ); + } + + #[test] + fn chained_bracket_index_and_field() { + let cmd = parse_input("admin.peers[0].caps").unwrap(); + assert_eq!( + cmd, + InputCommand::RpcWithQuery { + method: "admin.peers".to_owned(), + params: None, + query: ".[0].caps".to_owned(), + } + ); + } + + #[test] + fn chained_bracket_only() { + let cmd = parse_input("admin.peers[0]").unwrap(); + assert_eq!( + cmd, + InputCommand::RpcWithQuery { + method: "admin.peers".to_owned(), + params: None, + query: ".[0]".to_owned(), + } + ); + } + + #[test] + fn chained_underscore_style() { + let cmd = parse_input("admin_peers.count").unwrap(); + assert_eq!( + cmd, + InputCommand::RpcWithQuery { + method: "admin_peers".to_owned(), + params: None, + query: ".count".to_owned(), + } + ); + } + + #[test] + fn no_chain_on_plain_alias() { + assert_eq!( + parse_input("admin.peers").unwrap(), + InputCommand::Alias("admin.peers".to_owned()) + ); + } +} diff --git a/src/console/endpoint.rs b/src/console/endpoint.rs new file mode 100644 index 00000000..2e9a60a6 --- /dev/null +++ b/src/console/endpoint.rs @@ -0,0 +1,78 @@ +use eyre::{Result, bail}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Transport { + Http, + Ws, + Ipc, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResolvedEndpoint { + pub raw: String, + pub transport: Transport, +} + +const DEFAULT_IPC_FILENAME: &str = "reth.ipc"; + +pub fn default_datadir() -> PathBuf { + if cfg!(target_os = "macos") && + let Some(home) = dirs::home_dir() + { + return home.join("Library").join("Application Support").join("reth"); + } + dirs::data_dir().unwrap_or_else(|| PathBuf::from(".")).join("reth") +} + +pub fn resolve_endpoint(endpoint: Option<&str>) -> Result { + let raw = match endpoint { + Some(e) => e.to_owned(), + None => default_datadir().join(DEFAULT_IPC_FILENAME).to_string_lossy().into_owned(), + }; + let transport = detect_transport(&raw)?; + Ok(ResolvedEndpoint { raw, transport }) +} + +fn detect_transport(endpoint: &str) -> Result { + if endpoint.starts_with("http://") || endpoint.starts_with("https://") { + return Ok(Transport::Http); + } + if endpoint.starts_with("ws://") || endpoint.starts_with("wss://") { + return Ok(Transport::Ws); + } + if endpoint.contains("://") { + bail!("unsupported endpoint scheme in {endpoint:?}"); + } + Ok(Transport::Ipc) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_to_ipc_path() { + let got = resolve_endpoint(None).unwrap(); + assert_eq!(got.transport, Transport::Ipc); + assert!(got.raw.ends_with("reth.ipc")); + } + + #[test] + fn parses_http() { + let got = resolve_endpoint(Some("http://127.0.0.1:8545")).unwrap(); + assert_eq!(got.transport, Transport::Http); + } + + #[test] + fn parses_ws() { + let got = resolve_endpoint(Some("ws://127.0.0.1:8546")).unwrap(); + assert_eq!(got.transport, Transport::Ws); + } + + #[test] + fn rejects_unknown_scheme() { + let err = resolve_endpoint(Some("ftp://example.test")).unwrap_err(); + assert!(err.to_string().contains("unsupported endpoint scheme")); + } +} diff --git a/src/console/engine.rs b/src/console/engine.rs new file mode 100644 index 00000000..f25330a5 --- /dev/null +++ b/src/console/engine.rs @@ -0,0 +1,144 @@ +use super::{ + command::{InputCommand, parse_input}, + query::apply_query, + rpc::RpcClient, +}; +use eyre::{Result, eyre}; +use serde_json::Value; +use std::collections::BTreeMap; + +pub enum EvalOutcome { + Noop, + Exit, + Help, + Value(Value), +} + +pub async fn evaluate_line( + rpc: &RpcClient, + aliases: &BTreeMap, + line: &str, + last_rpc_result: &mut Option, +) -> Result { + match parse_input(line)? { + InputCommand::Empty => Ok(EvalOutcome::Noop), + InputCommand::Exit => Ok(EvalOutcome::Exit), + InputCommand::Help => Ok(EvalOutcome::Help), + InputCommand::Query(expr) => { + let next = apply_query_to_last_rpc(&expr, last_rpc_result)?; + Ok(EvalOutcome::Value(next)) + } + InputCommand::Rpc { method, params } => { + let normalized_method = normalize_rpc_method(&method); + let value = rpc.request_value(&normalized_method, params).await?; + *last_rpc_result = Some(value.clone()); + Ok(EvalOutcome::Value(value)) + } + InputCommand::RpcWithQuery { method, params, query } => { + let normalized_method = normalize_rpc_method(&method); + let value = rpc.request_value(&normalized_method, params).await?; + *last_rpc_result = Some(value.clone()); + let result = apply_query(&query, &value)?; + Ok(EvalOutcome::Value(result)) + } + InputCommand::Alias(alias) => { + if is_remove_all_peers_alias(alias.as_str()) { + return run_remove_all_peers(rpc, last_rpc_result).await; + } + + let method = resolve_alias_method(aliases, &alias); + let value = rpc.request_value(&method, None).await?; + *last_rpc_result = Some(value.clone()); + Ok(EvalOutcome::Value(value)) + } + } +} + +fn apply_query_to_last_rpc(expr: &str, last_rpc_result: &Option) -> Result { + let value = + last_rpc_result.as_ref().ok_or_else(|| eyre!("no last rpc result available for query"))?; + apply_query(expr, value) +} + +fn normalize_rpc_method(method: &str) -> String { + method.replace('.', "_") +} + +fn resolve_alias_method(aliases: &BTreeMap, alias: &str) -> String { + aliases.get(alias).cloned().unwrap_or_else(|| alias.replace('.', "_")) +} + +fn is_remove_all_peers_alias(alias: &str) -> bool { + matches!(alias, "removeAllPeers" | "admin.removeAllPeers") +} + +async fn run_remove_all_peers( + rpc: &RpcClient, + last_rpc_result: &mut Option, +) -> Result { + let peers = rpc.request_value("admin_peers", None).await?; + let arr = peers.as_array().ok_or_else(|| eyre!("admin.peers did not return an array"))?; + let total = arr.len() as u64; + let mut removed = 0u64; + let mut failed = 0u64; + for peer in arr { + let enode = peer + .get("enode") + .and_then(Value::as_str) + .ok_or_else(|| eyre!("peer entry missing enode"))?; + match rpc.request_value("admin_removePeer", Some(serde_json::json!([enode]))).await { + Ok(Value::Bool(true)) => removed += 1, + Ok(other) => { + eprintln!("admin.removePeer({enode}): unexpected response: {other}"); + failed += 1; + } + Err(err) => { + eprintln!("admin.removePeer({enode}): {err}"); + failed += 1; + } + } + } + *last_rpc_result = Some(peers); + eprintln!("Removed {removed}/{total} peer(s)."); + Ok(EvalOutcome::Value( + serde_json::json!({ "removed": removed, "failed": failed, "total": total }), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn normalizes_rpc_methods() { + assert_eq!(normalize_rpc_method("eth.getBalance"), "eth_getBalance"); + } + + #[test] + fn resolves_alias_from_map() { + let aliases = BTreeMap::from([("bn".to_owned(), "eth_blockNumber".to_owned())]); + assert_eq!(resolve_alias_method(&aliases, "bn"), "eth_blockNumber"); + } + + #[test] + fn resolves_alias_by_dot_fallback() { + let aliases = BTreeMap::new(); + assert_eq!(resolve_alias_method(&aliases, "net.peerCount"), "net_peerCount"); + } + + #[test] + fn query_without_last_result_errors() { + let err = apply_query_to_last_rpc(".count", &None).unwrap_err(); + assert!(err.to_string().contains("no last rpc result available for query")); + } + + #[test] + fn rpc_with_query_stores_raw_result_in_last() { + let peers = json!([{"id": "a"}, {"id": "b"}]); + let count = apply_query(".count", &peers).unwrap(); + let last: Option = Some(peers.clone()); + assert_eq!(count, json!(2)); + assert_eq!(last, Some(peers)); + } +} diff --git a/src/console/exec.rs b/src/console/exec.rs new file mode 100644 index 00000000..7a64b45e --- /dev/null +++ b/src/console/exec.rs @@ -0,0 +1,32 @@ +use super::{ + engine::{EvalOutcome, evaluate_line}, + rpc::RpcClient, +}; +use eyre::Result; +use serde_json::Value; +use std::collections::BTreeMap; + +pub async fn run_exec( + rpc: &RpcClient, + script: &str, + aliases: &BTreeMap, +) -> Result<()> { + let mut last = None; + match evaluate_line(rpc, aliases, script, &mut last).await? { + EvalOutcome::Value(value) => print_raw_json(&value), + EvalOutcome::Help => print_help(), + EvalOutcome::Noop | EvalOutcome::Exit => {} + } + Ok(()) +} + +fn print_raw_json(value: &Value) { + println!("{}", serde_json::to_string(value).unwrap_or_else(|_| value.to_string())); +} + +fn print_help() { + println!("Usage:"); + println!(" [json_params] (RPC call)"); + println!(" (e.g. eth.blockNumber)"); + println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); +} diff --git a/src/console/mod.rs b/src/console/mod.rs new file mode 100644 index 00000000..e81a4dfc --- /dev/null +++ b/src/console/mod.rs @@ -0,0 +1,16 @@ +//! Operator JSON-RPC console (merged from reth-console). + +mod cli; +mod command; +mod endpoint; +mod engine; +mod exec; +mod output; +mod query; +mod repl; +mod rpc; +/// Method suffix tables for tab-completing `namespace.method` in the REPL. +mod rpc_completion; +mod run; + +pub use cli::ConsoleCommand; diff --git a/src/console/output.rs b/src/console/output.rs new file mode 100644 index 00000000..96246a63 --- /dev/null +++ b/src/console/output.rs @@ -0,0 +1,556 @@ +use serde_json::Value; + +const DEFAULT_NATIVE_SYMBOL: &str = "ETH"; + +pub fn print_value_for_chain(value: &Value, chain_id: Option) { + print_value_with_symbol(value, native_symbol_for_chain_id(chain_id), false); +} + +pub fn print_value_for_chain_raw(value: &Value, chain_id: Option, raw: bool) { + if raw { + println!("{}", pretty(value)); + } else { + print_value_for_chain(value, chain_id); + } +} + +pub fn native_symbol_for_chain_id(chain_id: Option) -> &'static str { + match chain_id { + Some(80_069) | Some(80_094) => "BERA", + _ => DEFAULT_NATIVE_SYMBOL, + } +} + +fn print_value_with_symbol(value: &Value, native_symbol: &str, raw: bool) { + if raw { + println!("{}", pretty(value)); + return; + } + + if let Some(table) = try_format_detailed_peers(value) { + println!("{}", table); + return; + } + if let Some(status) = try_format_node_status(value) { + println!("{}", status); + return; + } + if let Some(table) = try_format_peer_scores(value) { + println!("{}", table); + return; + } + if let Some(table) = try_format_banned_subnets(value) { + println!("{}", table); + return; + } + + println!("{}", pretty(value)); + + let annotations = collect_annotations_with_symbol(value, native_symbol); + if !annotations.is_empty() { + println!("-- interpreted values --"); + for note in annotations { + println!("{note}"); + } + } +} + +fn pretty(value: &Value) -> String { + serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string()) +} + +fn collect_annotations_with_symbol(value: &Value, native_symbol: &str) -> Vec { + let mut out = Vec::new(); + walk("$", value, native_symbol, &mut out); + out +} + +fn walk(path: &str, value: &Value, native_symbol: &str, out: &mut Vec) { + match value { + Value::Object(map) => { + for (k, v) in map { + walk(&format!("{path}.{k}"), v, native_symbol, out); + } + } + Value::Array(items) => { + for (idx, v) in items.iter().enumerate() { + walk(&format!("{path}[{idx}]"), v, native_symbol, out); + } + } + Value::String(s) => { + if let Some(dec) = small_hex_to_dec(s) { + out.push(format!("{path}: {s} -> {dec}")); + if looks_like_wei(dec) { + out.push(format!("{path}: {dec} wei -> {} {native_symbol}", format_eth(dec))); + } + } + if let Some(wei) = decimal_like_wei(s) { + out.push(format!("{path}: {wei} wei -> {} {native_symbol}", format_eth(wei))); + } + } + Value::Number(n) => { + if let Some(wei) = n.as_u64().map(u128::from) && + looks_like_wei(wei) + { + out.push(format!("{path}: {wei} wei -> {} {native_symbol}", format_eth(wei))); + } + } + _ => {} + } +} + +fn small_hex_to_dec(input: &str) -> Option { + if !(input.starts_with("0x") || input.starts_with("0X")) { + return None; + } + let hex = &input[2..]; + if hex.is_empty() || hex.len() > 32 { + return None; + } + u128::from_str_radix(hex, 16).ok() +} + +fn decimal_like_wei(input: &str) -> Option { + if input.is_empty() || !input.chars().all(|c| c.is_ascii_digit()) { + return None; + } + let value = input.parse::().ok()?; + if looks_like_wei(value) { Some(value) } else { None } +} + +fn looks_like_wei(value: u128) -> bool { + // Heuristic: large integer range typically used for wei amounts. + (1_000_000_000_000_000u128..=1_000_000_000_000_000_000_000_000_000_000_000u128).contains(&value) +} + +fn format_eth(wei: u128) -> String { + const WEI_PER_ETH: u128 = 1_000_000_000_000_000_000; + let whole = wei / WEI_PER_ETH; + let frac = wei % WEI_PER_ETH; + if frac == 0 { + return whole.to_string(); + } + let mut frac_str = format!("{frac:018}"); + while frac_str.ends_with('0') { + frac_str.pop(); + } + format!("{whole}.{frac_str}") +} + +fn try_format_detailed_peers(value: &Value) -> Option { + let peers = value.as_array()?; + if peers.is_empty() { + return None; + } + + let first = peers.first()?; + if !first.is_object() { + return None; + } + + let obj = first.as_object()?; + if !obj.contains_key("peerId") && !obj.contains_key("peer_id") { + return None; + } + + let mut lines = vec![]; + let header = format!( + "{:<18} {:<18} {:<4} {:<4} {:<6} {:<14} {:<8} {:<10}", + "PEER", "ADDR", "DIR", "REP", "BLOCK", "CLIENT", "STATE", "PoG" + ); + lines.push(header); + + for peer in peers { + if let Some(peer_obj) = peer.as_object() { + let peer_id = peer_obj + .get("peerId") + .or_else(|| peer_obj.get("peer_id")) + .and_then(|v| v.as_str()) + .unwrap_or("?"); + let addr = peer_obj + .get("remoteAddr") + .or_else(|| peer_obj.get("remote_addr")) + .and_then(|v| v.as_str()) + .unwrap_or("?"); + let direction = peer_obj + .get("direction") + .and_then(|v| v.as_str()) + .map(|s| &s[..s.len().min(3)]) + .unwrap_or("-"); + let reputation = peer_obj + .get("reputation") + .and_then(|v| v.as_i64()) + .map(|r| r.to_string()) + .unwrap_or_else(|| "?".to_string()); + let block = peer_obj + .get("latestBlock") + .or_else(|| peer_obj.get("latest_block")) + .and_then(|v| v.as_u64()) + .map(|b| b.to_string()) + .unwrap_or_else(|| "?".to_string()); + let client = peer_obj + .get("clientVersion") + .or_else(|| peer_obj.get("client_version")) + .and_then(|v| v.as_str()) + .unwrap_or("-"); + let client_short = if client.len() > 14 { + format!("{}..{}", &client[..8], &client[client.len() - 3..]) + } else { + client.to_string() + }; + let state = peer_obj + .get("connectionState") + .or_else(|| peer_obj.get("connection_state")) + .and_then(|v| v.as_str()) + .unwrap_or("-"); + + let pog_str = if let Some(pog) = peer_obj.get("pog") { + if pog.is_null() { + "-".to_string() + } else if let Some(pog_obj) = pog.as_object() { + let failures = pog_obj + .get("failureCount") + .or_else(|| pog_obj.get("failure_count")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + format!("{}", failures) + } else { + "-".to_string() + } + } else { + "-".to_string() + }; + + let peer_short = if peer_id.len() > 12 { + format!("{}..{}", &peer_id[..6], &peer_id[peer_id.len() - 4..]) + } else { + peer_id.to_string() + }; + + let line = format!( + "{:<18} {:<18} {:<4} {:<4} {:<6} {:<14} {:<8} {:<10}", + peer_short, addr, direction, reputation, block, client_short, state, pog_str + ); + lines.push(line); + } + } + + Some(lines.join("\n")) +} + +fn try_format_node_status(value: &Value) -> Option { + let obj = value.as_object()?; + + if !obj.contains_key("chainId") && + !obj.contains_key("chain_id") && + !obj.contains_key("genesisHash") && + !obj.contains_key("genesis_hash") && + !obj.contains_key("headNumber") && + !obj.contains_key("head_number") + { + return None; + } + + let chain = obj + .get("chainId") + .or_else(|| obj.get("chain_id")) + .or_else(|| obj.get("networkId")) + .or_else(|| obj.get("network_id")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let genesis = obj + .get("genesisHash") + .or_else(|| obj.get("genesis_hash")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let genesis_short = if genesis.len() > 12 { + format!("{}..{}", &genesis[..6], &genesis[genesis.len() - 4..]) + } else { + genesis.to_string() + }; + let head = obj + .get("headNumber") + .or_else(|| obj.get("head_number")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let head_hash = obj + .get("headHash") + .or_else(|| obj.get("head_hash")) + .or_else(|| obj.get("head")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + let head_hash_short = if head_hash.len() > 12 { + format!("{}..{}", &head_hash[..6], &head_hash[head_hash.len() - 4..]) + } else { + head_hash.to_string() + }; + let syncing = obj.get("syncing").and_then(|v| v.as_bool()).unwrap_or(false); + let peers_total = obj + .get("peerCountTotal") + .or_else(|| obj.get("peer_count_total")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let peers_in = obj + .get("peerCountInbound") + .or_else(|| obj.get("peer_count_inbound")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let peers_out = obj + .get("peerCountOutbound") + .or_else(|| obj.get("peer_count_outbound")) + .and_then(|v| v.as_u64()) + .unwrap_or(0); + let client = obj + .get("clientVersion") + .or_else(|| obj.get("client_version")) + .and_then(|v| v.as_str()) + .unwrap_or("unknown"); + + let output = format!( + "chain={} genesis={} fork=unknown\nhead={} ({}) syncing={}\npeers={} (in={} out={}) client={} net={}", + chain, + genesis_short, + head, + head_hash_short, + syncing, + peers_total, + peers_in, + peers_out, + client, + chain + ); + + Some(output) +} + +fn try_format_peer_scores(value: &Value) -> Option { + let scores = value.as_array()?; + if scores.is_empty() { + return Some("-- no peers scored --".to_string()); + } + + let first = scores.first()?; + if !first.is_object() { + return None; + } + + let obj = first.as_object()?; + // Detect if this looks like peer scores: should have fields like peerId, threatScore, node, + // policies + if !obj.contains_key("peerId") && !obj.contains_key("peer_id") { + return None; + } + if !obj.contains_key("threatScore") && !obj.contains_key("threat_score") { + return None; + } + + let mut lines = vec![]; + let header = + format!("{:<18} {:<6} {:<10} {:<14} {:<8}", "PEER", "THREAT", "NODE", "REASON", "POLICIES"); + lines.push(header); + + for score in scores { + if let Some(score_obj) = score.as_object() { + let peer_id = score_obj + .get("peerId") + .or_else(|| score_obj.get("peer_id")) + .and_then(|v| v.as_str()) + .unwrap_or("?"); + let threat = score_obj + .get("threatScore") + .or_else(|| score_obj.get("threat_score")) + .and_then(|v| v.as_u64()) + .map(|t| t.to_string()) + .unwrap_or_else(|| "?".to_string()); + let node = score_obj.get("node").and_then(|v| v.as_str()).unwrap_or("?"); + let reason = score_obj.get("reason").and_then(|v| v.as_str()).unwrap_or("-"); + + let policies = score_obj + .get("policies") + .and_then(|v| v.as_array()) + .map(|p| p.len().to_string()) + .unwrap_or_else(|| "0".to_string()); + + let peer_short = if peer_id.len() > 12 { + format!("{}..{}", &peer_id[..6], &peer_id[peer_id.len() - 4..]) + } else { + peer_id.to_string() + }; + let reason_short = if reason.len() > 14 { + format!("{}..{}", &reason[..8], &reason[reason.len() - 3..]) + } else { + reason.to_string() + }; + + let line = format!( + "{:<18} {:<6} {:<10} {:<14} {:<8}", + peer_short, threat, node, reason_short, policies + ); + lines.push(line); + } + } + + Some(lines.join("\n")) +} + +fn try_format_banned_subnets(value: &Value) -> Option { + let subnets = value.as_array()?; + if subnets.is_empty() { + return Some("-- no subnets banned --".to_string()); + } + + let first = subnets.first()?; + if !first.is_object() { + return None; + } + + let obj = first.as_object()?; + // Detect if this looks like banned subnets: should have fields like subnet, reason, peers, + // nodes + if !obj.contains_key("subnet") && !obj.contains_key("cidr") { + return None; + } + + let mut lines = vec![]; + let header = format!("{:<20} {:<14} {:<8} {:<20}", "SUBNET", "REASON", "PEERS", "NODES"); + lines.push(header); + + for subnet in subnets { + if let Some(subnet_obj) = subnet.as_object() { + let cidr = subnet_obj + .get("subnet") + .or_else(|| subnet_obj.get("cidr")) + .and_then(|v| v.as_str()) + .unwrap_or("?"); + let reason = subnet_obj.get("reason").and_then(|v| v.as_str()).unwrap_or("-"); + let peer_count = subnet_obj + .get("peerCount") + .or_else(|| subnet_obj.get("peer_count")) + .and_then(|v| v.as_u64()) + .map(|p| p.to_string()) + .unwrap_or_else(|| "?".to_string()); + let nodes = subnet_obj + .get("nodes") + .and_then(|v| v.as_array()) + .map(|n| n.len().to_string()) + .unwrap_or_else(|| "?".to_string()); + + let reason_short = if reason.len() > 14 { + format!("{}..{}", &reason[..8], &reason[reason.len() - 3..]) + } else { + reason.to_string() + }; + + let line = format!("{:<20} {:<14} {:<8} {:<20}", cidr, reason_short, peer_count, nodes); + lines.push(line); + } + } + + Some(lines.join("\n")) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_small_hex() { + assert_eq!(small_hex_to_dec("0x2a"), Some(42)); + assert_eq!(small_hex_to_dec("0xffffffffffffffff"), Some(u64::MAX as u128)); + assert_eq!(small_hex_to_dec("0x10000000000000000"), Some(18446744073709551616)); + assert_eq!(small_hex_to_dec("0x100000000000000000000000000000000"), None); + } + + #[test] + fn formats_eth() { + assert_eq!(format_eth(1_000_000_000_000_000_000), "1"); + assert_eq!(format_eth(1_500_000_000_000_000_000), "1.5"); + } + + #[test] + fn annotates_wei_and_hex() { + let value = serde_json::json!({ + "gasUsed": "0x5208", + "amount": "1000000000000000000" + }); + let notes = collect_annotations_with_symbol(&value, DEFAULT_NATIVE_SYMBOL); + assert!(notes.iter().any(|n| n.contains("0x5208 -> 21000"))); + assert!(notes.iter().any(|n| n.contains("1 ETH"))); + } + + #[test] + fn annotates_hex_wei_as_eth() { + let value = serde_json::json!("0x0de0b6b3a7640000"); + let notes = collect_annotations_with_symbol(&value, DEFAULT_NATIVE_SYMBOL); + assert!(notes.iter().any(|n| n.contains("1000000000000000000"))); + assert!(notes.iter().any(|n| n.contains("1 ETH"))); + } + + #[test] + fn detects_bera_native_symbol() { + assert_eq!(native_symbol_for_chain_id(Some(80_069)), "BERA"); + assert_eq!(native_symbol_for_chain_id(Some(80_094)), "BERA"); + assert_eq!(native_symbol_for_chain_id(Some(1)), "ETH"); + assert_eq!(native_symbol_for_chain_id(None), "ETH"); + } + + #[test] + fn annotates_native_symbol_for_chain() { + let value = serde_json::json!("1000000000000000000"); + let notes = collect_annotations_with_symbol(&value, "BERA"); + assert!(notes.iter().any(|n| n.contains("1 BERA"))); + } + + #[test] + fn formats_peer_scores_table() { + let scores = serde_json::json!([ + { + "peerId": "0xabcdef1234567890abcdef1234567890abcdef12", + "threatScore": 150, + "node": "node-1", + "reason": "stale_head", + "policies": ["stale_head", "subnet_concentration"] + } + ]); + let formatted = try_format_peer_scores(&scores); + assert!(formatted.is_some()); + let formatted_str = formatted.unwrap(); + assert!(formatted_str.contains("PEER")); + assert!(formatted_str.contains("THREAT")); + assert!(formatted_str.contains("0xabcd..ef12")); + assert!(formatted_str.contains("150")); + } + + #[test] + fn formats_banned_subnets_table() { + let subnets = serde_json::json!([ + { + "subnet": "192.168.1.0/24", + "reason": "subnet_concentration", + "peerCount": 5, + "nodes": ["node-1", "node-2"] + } + ]); + let formatted = try_format_banned_subnets(&subnets); + assert!(formatted.is_some()); + let formatted_str = formatted.unwrap(); + assert!(formatted_str.contains("SUBNET")); + assert!(formatted_str.contains("PEERS")); + assert!(formatted_str.contains("192.168.1.0/24")); + assert!(formatted_str.contains("5")); + } + + #[test] + fn handles_empty_peer_scores_gracefully() { + let empty_scores = serde_json::json!([]); + let formatted = try_format_peer_scores(&empty_scores); + assert_eq!(formatted, Some("-- no peers scored --".to_string())); + } + + #[test] + fn handles_empty_banned_subnets_gracefully() { + let empty_subnets = serde_json::json!([]); + let formatted = try_format_banned_subnets(&empty_subnets); + assert_eq!(formatted, Some("-- no subnets banned --".to_string())); + } +} diff --git a/src/console/query.rs b/src/console/query.rs new file mode 100644 index 00000000..f4eb5768 --- /dev/null +++ b/src/console/query.rs @@ -0,0 +1,188 @@ +use eyre::{Result, bail, eyre}; +use serde_json::{Number, Value}; + +#[derive(Debug, Clone)] +enum Segment { + Field(String), + Index(usize), +} + +pub fn apply_query(expr: &str, input: &Value) -> Result { + let expr = expr.trim(); + if !expr.starts_with('.') { + bail!("query must start with '.'"); + } + + if let Some((inner, rest)) = parse_map(expr)? { + let items = input + .as_array() + .ok_or_else(|| eyre!(".map(...) requires last result to be an array"))?; + let mut out = Vec::with_capacity(items.len()); + for item in items { + out.push(apply_query(inner, item)?); + } + let mapped = Value::Array(out); + if rest.is_empty() { + return Ok(mapped); + } + return apply_query(rest, &mapped); + } + + if expr == ".count" || expr == ".len" { + return count_value(input); + } + if expr == ".first" { + return first_value(input); + } + if expr == ".last" { + return last_value(input); + } + + let segments = parse_segments(expr)?; + let mut current = input; + for segment in segments { + current = match segment { + Segment::Field(name) => { + current.get(&name).ok_or_else(|| eyre!("field {name:?} not found"))? + } + Segment::Index(i) => current.get(i).ok_or_else(|| eyre!("index {i} out of range"))?, + }; + } + Ok(current.clone()) +} + +fn parse_map(expr: &str) -> Result> { + if !expr.starts_with(".map(") { + return Ok(None); + } + let close = expr.find(')').ok_or_else(|| eyre!("invalid .map(...) expression"))?; + let inner = &expr[5..close]; + if !inner.starts_with('.') { + bail!("map selector must start with '.'"); + } + let rest = &expr[close + 1..]; + Ok(Some((inner, rest))) +} + +fn parse_segments(expr: &str) -> Result> { + let mut out = Vec::new(); + let bytes = expr.as_bytes(); + let mut i = 0usize; + while i < bytes.len() { + if bytes[i] != b'.' { + bail!("invalid query syntax near {}", &expr[i..]); + } + i += 1; + if i >= bytes.len() { + break; + } + if bytes[i] == b'[' { + i += 1; + let start = i; + while i < bytes.len() && bytes[i] != b']' { + i += 1; + } + if i >= bytes.len() { + bail!("unterminated index in query"); + } + let idx: usize = expr[start..i].parse()?; + out.push(Segment::Index(idx)); + i += 1; + continue; + } + let start = i; + while i < bytes.len() { + let c = bytes[i]; + if c == b'.' || c == b'[' { + break; + } + i += 1; + } + let name = expr[start..i].trim(); + if name.is_empty() { + bail!("empty field in query"); + } + out.push(Segment::Field(name.to_owned())); + } + Ok(out) +} + +fn count_value(value: &Value) -> Result { + match value { + Value::Array(items) => Ok(Value::Number(Number::from(items.len() as u64))), + Value::Object(fields) => Ok(Value::Number(Number::from(fields.len() as u64))), + _ => Err(eyre!(".count/.len only apply to arrays and objects")), + } +} + +fn first_value(value: &Value) -> Result { + let items = value.as_array().ok_or_else(|| eyre!(".first only applies to arrays"))?; + Ok(items.first().cloned().unwrap_or(Value::Null)) +} + +fn last_value(value: &Value) -> Result { + let items = value.as_array().ok_or_else(|| eyre!(".last only applies to arrays"))?; + Ok(items.last().cloned().unwrap_or(Value::Null)) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn applies_count() { + let value = json!([1, 2, 3]); + let out = apply_query(".count", &value).unwrap(); + assert_eq!(out, json!(3)); + } + + #[test] + fn applies_len() { + let value = json!({"a": 1, "b": 2}); + let out = apply_query(".len", &value).unwrap(); + assert_eq!(out, json!(2)); + } + + #[test] + fn applies_index_and_field() { + let value = json!([{ "a": 1 }, { "a": 2 }]); + let out = apply_query(".[1].a", &value).unwrap(); + assert_eq!(out, json!(2)); + } + + #[test] + fn applies_map() { + let value = json!([{ "a": 1 }, { "a": 2 }]); + let out = apply_query(".map(.a)", &value).unwrap(); + assert_eq!(out, json!([1, 2])); + } + + #[test] + fn applies_map_then_count() { + let value = json!([{ "a": 1 }, { "a": 2 }, { "a": 3 }]); + let out = apply_query(".map(.a).count", &value).unwrap(); + assert_eq!(out, json!(3)); + } + + #[test] + fn first_and_last_on_empty_array_return_null() { + let value = json!([]); + assert_eq!(apply_query(".first", &value).unwrap(), Value::Null); + assert_eq!(apply_query(".last", &value).unwrap(), Value::Null); + } + + #[test] + fn map_requires_array_input() { + let value = json!({"a": 1}); + let err = apply_query(".map(.a)", &value).unwrap_err(); + assert!(err.to_string().contains("requires last result to be an array")); + } + + #[test] + fn count_rejects_scalars() { + let value = json!("abc"); + let err = apply_query(".count", &value).unwrap_err(); + assert!(err.to_string().contains("only apply to arrays and objects")); + } +} diff --git a/src/console/repl.rs b/src/console/repl.rs new file mode 100644 index 00000000..264ec41f --- /dev/null +++ b/src/console/repl.rs @@ -0,0 +1,314 @@ +use super::{ + endpoint::{ResolvedEndpoint, default_datadir}, + engine::{EvalOutcome, evaluate_line}, + output::print_value_for_chain_raw, + rpc::RpcClient, +}; +use eyre::Result; +use rustyline::{ + CompletionType, Context, Editor, Helper, + completion::{Completer, Pair}, + config::Configurer, + error::ReadlineError, + highlight::Highlighter, + hint::Hinter, + history::DefaultHistory, + validate::Validator, +}; +use serde_json::Value; +use std::{collections::BTreeMap, path::PathBuf}; + +#[allow(clippy::too_many_arguments)] +pub async fn run_repl( + rpc: &RpcClient, + history_path: PathBuf, + endpoint: ResolvedEndpoint, + aliases: &BTreeMap, + chain_id: Option, + raw: bool, + has_bera_admin: bool, + bera_admin_status: Option, +) -> Result<()> { + std::fs::create_dir_all( + history_path.parent().map(ToOwned::to_owned).unwrap_or_else(|| PathBuf::from(".")), + )?; + + let modules = rpc.supported_modules().await.unwrap_or_default(); + let helper = CompletionHelper::new(aliases, &modules, has_bera_admin); + let mut editor: Editor = Editor::new()?; + editor.set_completion_type(CompletionType::List); + editor.set_helper(Some(helper)); + if history_path.exists() { + let _ = editor.load_history(&history_path); + } + + println!("bera-reth console :: {}", endpoint.raw); + print_startup_snapshot(rpc, chain_id, bera_admin_status.as_ref()).await; + println!("help: commands | ctrl-d / exit: quit"); + + let mut last_rpc_result = None; + loop { + let line = editor.readline("bera> "); + match line { + Ok(line) => { + if !line.trim().is_empty() { + let _ = editor.add_history_entry(line.as_str()); + } + match evaluate_line(rpc, aliases, &line, &mut last_rpc_result).await { + Ok(EvalOutcome::Noop) => {} + Ok(EvalOutcome::Exit) => break, + Ok(EvalOutcome::Help) => print_help(aliases, has_bera_admin), + Ok(EvalOutcome::Value(value)) => { + print_value_for_chain_raw(&value, chain_id, raw); + } + Err(err) => eprintln!("error: {err}"), + } + } + Err(ReadlineError::Interrupted) => {} + Err(ReadlineError::Eof) => break, + Err(err) => return Err(err.into()), + } + } + + let _ = editor.save_history(&history_path); + Ok(()) +} + +async fn print_startup_snapshot( + rpc: &RpcClient, + chain_id: Option, + bera_admin_status: Option<&Value>, +) { + if let Some(status) = bera_admin_status { + let client_version = status.get("client").and_then(as_string); + let network_id = status.get("networkId").and_then(as_string); + let head_number = status.get("head").and_then(as_string); + let peer_count_total = status.get("peerCountTotal").and_then(hex_or_decimal_to_u64); + let peer_count_inbound = status.get("peerCountInbound").and_then(hex_or_decimal_to_u64); + let peer_count_outbound = status.get("peerCountOutbound").and_then(hex_or_decimal_to_u64); + + let peers_str = if let (Some(in_count), Some(out_count)) = + (peer_count_inbound, peer_count_outbound) + { + format!("peers={} (in={} out={})", peer_count_total.unwrap_or(0), in_count, out_count) + } else { + format!("peers={}", peer_count_total.unwrap_or(0)) + }; + + println!( + "node :: {} | net={} 🐻⭐ | block={} | {}", + client_version.unwrap_or_else(|| "unavailable".to_owned()), + network_id.unwrap_or_else(|| "unavailable".to_owned()), + head_number.unwrap_or_else(|| "unavailable".to_owned()), + peers_str + ); + } else { + let version = + rpc.request_value("web3_clientVersion", None).await.ok().and_then(|v| as_string(&v)); + let block = rpc + .request_value("eth_blockNumber", None) + .await + .ok() + .and_then(|v| hex_or_decimal_to_u64(&v).map(|n| n.to_string())); + let peers = rpc + .request_value("net_peerCount", None) + .await + .ok() + .and_then(|v| hex_or_decimal_to_u64(&v).map(|n| n.to_string())); + let network = rpc.request_value("net_version", None).await.ok().and_then(|v| as_string(&v)); + + println!( + "node :: version={} | net={}{} | block={} | peers={}", + version.unwrap_or_else(|| "unavailable".to_owned()), + network.unwrap_or_else(|| "unavailable".to_owned()), + chain_emoji(chain_id), + block.unwrap_or_else(|| "unavailable".to_owned()), + peers.unwrap_or_else(|| "unavailable".to_owned()), + ); + } +} + +fn chain_emoji(chain_id: Option) -> &'static str { + match chain_id { + Some(80_069) | Some(80_094) => " 🐻", + _ => "", + } +} + +fn as_string(value: &Value) -> Option { + match value { + Value::String(s) => Some(s.clone()), + Value::Number(n) => Some(n.to_string()), + _ => None, + } +} + +fn hex_or_decimal_to_u64(value: &Value) -> Option { + match value { + Value::String(s) => { + if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + u64::from_str_radix(hex, 16).ok() + } else { + s.parse::().ok() + } + } + Value::Number(n) => n.as_u64(), + _ => None, + } +} + +fn print_help(aliases: &BTreeMap, has_bera_admin: bool) { + println!("Commands:"); + println!(" [json_params] (RPC call)"); + println!(" (e.g. eth.blockNumber)"); + println!(" TAB completion for aliases/methods"); + println!(" help | exit"); + println!("Queries (run against last RPC result):"); + println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); + if has_bera_admin { + println!("beraAdmin (when detected):"); + println!(" peers detailed peer table"); + println!(" status node identity and sync state"); + println!(" ban \"0xpeerId\" ban peer (~12h)"); + println!(" penalize \"0xpeerId\" -100 penalize peer by value"); + } + if !aliases.is_empty() { + println!("Aliases:"); + for (alias, method) in aliases { + println!(" {alias} -> {method}"); + } + } +} + +struct CompletionHelper { + words: Vec, +} + +impl CompletionHelper { + fn new( + aliases: &BTreeMap, + modules: &BTreeMap, + has_bera_admin: bool, + ) -> CompletionHelper { + let mut words = vec![ + "help".to_owned(), + "exit".to_owned(), + "quit".to_owned(), + ".count".to_owned(), + ".len".to_owned(), + ".first".to_owned(), + ".last".to_owned(), + ".map(".to_owned(), + ]; + words.extend(aliases.keys().cloned()); + for method in aliases.values() { + words.push(method.clone()); + if let Some(dot) = rpc_method_to_dot(method) { + words.push(dot); + } + } + for module in modules.keys() { + words.push(format!("{module}.")); + words.push(format!("{module}_")); + words.extend(super::rpc_completion::dot_completions_for_namespace(module)); + } + if has_bera_admin { + words.push("beraAdmin.".to_owned()); + words.push("beraAdmin_".to_owned()); + words.extend(super::rpc_completion::dot_completions_for_namespace("beraAdmin")); + } + words.sort(); + words.dedup(); + CompletionHelper { words } + } +} + +fn rpc_method_to_dot(method: &str) -> Option { + let (module, rest) = method.split_once('_')?; + if module.is_empty() || rest.is_empty() { + return None; + } + Some(format!("{module}.{rest}")) +} + +impl Helper for CompletionHelper {} +impl Validator for CompletionHelper {} +impl Highlighter for CompletionHelper {} +impl Hinter for CompletionHelper { + type Hint = String; +} + +impl Completer for CompletionHelper { + type Candidate = Pair; + + fn complete( + &self, + line: &str, + pos: usize, + _ctx: &Context<'_>, + ) -> rustyline::Result<(usize, Vec)> { + let safe_pos = pos.min(line.len()); + let up_to_cursor = &line[..safe_pos]; + let start = up_to_cursor.rfind(char::is_whitespace).map(|i| i + 1).unwrap_or(0); + let needle = &up_to_cursor[start..]; + let matches = self + .words + .iter() + .filter(|word| word.starts_with(needle)) + .map(|word| Pair { display: word.clone(), replacement: word.clone() }) + .collect(); + Ok((start, matches)) + } +} + +pub fn history_file_path() -> PathBuf { + default_datadir().join("bera-reth-console-history") +} + +#[cfg(test)] +mod tests { + use super::*; + use rustyline::{completion::Completer, history::DefaultHistory}; + use serde_json::json; + + #[test] + fn completion_includes_bera_admin_when_enabled() { + let aliases = BTreeMap::new(); + let modules = BTreeMap::new(); + let helper = CompletionHelper::new(&aliases, &modules, true); + assert!(helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); + } + + #[test] + fn completion_excludes_bera_admin_when_disabled() { + let aliases = BTreeMap::new(); + let modules = BTreeMap::new(); + let helper = CompletionHelper::new(&aliases, &modules, false); + assert!(!helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); + } + + #[test] + fn completion_matches_prefix() { + let aliases = BTreeMap::from([("bn".to_owned(), "eth_blockNumber".to_owned())]); + let modules = BTreeMap::from([("eth".to_owned(), "1.0".to_owned())]); + let helper = CompletionHelper::new(&aliases, &modules, false); + let history = DefaultHistory::new(); + let ctx = Context::new(&history); + let (_start, hits) = helper.complete("eth.getB", "eth.getB".len(), &ctx).unwrap(); + assert!(hits.iter().any(|p| p.replacement == "eth.getBalance")); + } + + #[test] + fn completion_includes_eth_namespace_methods() { + let aliases = BTreeMap::new(); + let modules = BTreeMap::from([("eth".to_owned(), "1.0".to_owned())]); + let helper = CompletionHelper::new(&aliases, &modules, false); + assert!(helper.words.iter().any(|w| w == "eth.getLogs")); + assert!(helper.words.iter().any(|w| w == "eth.getTransactionReceipt")); + } + + #[test] + fn parses_hex_or_decimal_numbers() { + assert_eq!(hex_or_decimal_to_u64(&json!("0x10")), Some(16)); + } +} diff --git a/src/console/rpc.rs b/src/console/rpc.rs new file mode 100644 index 00000000..eecdb6bc --- /dev/null +++ b/src/console/rpc.rs @@ -0,0 +1,396 @@ +use super::endpoint::{ResolvedEndpoint, Transport}; +use eyre::{Result, eyre}; +use http::{HeaderMap, HeaderName, HeaderValue}; +use jsonrpsee::{ + core::{ + client::ClientT, + params::{ArrayParams, ObjectParams}, + }, + http_client::{HttpClient, HttpClientBuilder}, + rpc_params, + ws_client::{WsClient, WsClientBuilder}, +}; +use serde_json::{Map, Value}; +use std::{ + collections::BTreeMap, + ffi::CString, + path::Path, + sync::atomic::{AtomicU64, Ordering}, +}; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + net::UnixStream, +}; + +#[derive(Debug)] +pub enum RpcClient { + Http(HttpClient), + Ws(WsClient), + Ipc(IpcClientLite), +} + +impl RpcClient { + pub async fn connect( + endpoint: &ResolvedEndpoint, + http_headers: &[(String, String)], + ) -> Result { + let headers = make_headers(http_headers)?; + match endpoint.transport { + Transport::Http => { + let client = + HttpClientBuilder::default().set_headers(headers).build(&endpoint.raw)?; + Ok(Self::Http(client)) + } + Transport::Ws => { + let client = + WsClientBuilder::default().set_headers(headers).build(&endpoint.raw).await?; + Ok(Self::Ws(client)) + } + Transport::Ipc => { + validate_ipc_endpoint(&endpoint.raw)?; + let client = IpcClientLite::new(endpoint.raw.clone()); + Ok(Self::Ipc(client)) + } + } + } + + pub async fn request_value(&self, method: &str, params: Option) -> Result { + let params = RpcParams::from_value(params)?; + match self { + Self::Http(client) => params.request(client, method).await, + Self::Ws(client) => params.request(client, method).await, + Self::Ipc(client) => client.request(method, params.into_value()).await, + } + } + + pub async fn supported_modules(&self) -> Result> { + let value = self.request_value("rpc_modules", None).await?; + let map = serde_json::from_value(value)?; + Ok(map) + } +} + +enum RpcParams { + None, + Array(ArrayParams, Vec), + Object(ObjectParams, Map), +} + +impl RpcParams { + fn from_value(value: Option) -> Result { + let Some(value) = value else { + return Ok(Self::None); + }; + match value { + Value::Null => Ok(Self::None), + Value::Array(values) => { + let mut out = ArrayParams::new(); + for v in &values { + out.insert(v).map_err(|e| eyre!("invalid rpc array params: {e}"))?; + } + Ok(Self::Array(out, values)) + } + Value::Object(values) => Ok(Self::Object(object_params(values.clone())?, values)), + _ => Err(eyre!("rpc params must be null, JSON array, or JSON object")), + } + } + + async fn request(&self, client: &C, method: &str) -> Result + where + C: ClientT, + { + let value = match self { + Self::None => client.request(method, rpc_params![]).await?, + Self::Array(params, _) => client.request(method, params.clone()).await?, + Self::Object(params, _) => client.request(method, params.clone()).await?, + }; + Ok(value) + } + + fn into_value(self) -> Value { + match self { + Self::None => Value::Array(vec![]), + Self::Array(_, values) => Value::Array(values), + Self::Object(_, values) => Value::Object(values), + } + } +} + +fn object_params(values: Map) -> Result { + let mut params = ObjectParams::new(); + for (k, v) in values { + params.insert(k.as_str(), v).map_err(|e| eyre!("invalid rpc object params: {e}"))?; + } + Ok(params) +} + +fn make_headers(headers: &[(String, String)]) -> Result { + let mut out = HeaderMap::new(); + for (k, v) in headers { + let key = HeaderName::from_bytes(k.as_bytes())?; + let value = HeaderValue::from_str(v)?; + out.insert(key, value); + } + Ok(out) +} + +fn validate_ipc_endpoint(path: &str) -> Result<()> { + let endpoint = Path::new(path); + if !endpoint.exists() { + return Err(eyre!("IPC endpoint not found: {path}")); + } + let metadata = std::fs::metadata(endpoint) + .map_err(|err| eyre!("failed to stat IPC endpoint {path}: {err}"))?; + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if !metadata.file_type().is_socket() { + return Err(eyre!("IPC endpoint is not a unix socket: {path}")); + } + let c_path = + CString::new(path).map_err(|_| eyre!("IPC endpoint contains invalid bytes: {path}"))?; + let read_ok = unsafe { libc::access(c_path.as_ptr(), libc::R_OK) == 0 }; + if !read_ok { + return Err(eyre!("IPC endpoint is not readable by current user: {path}")); + } + let write_ok = unsafe { libc::access(c_path.as_ptr(), libc::W_OK) == 0 }; + if !write_ok { + return Err(eyre!("IPC endpoint is not writable by current user: {path}")); + } + } + #[cfg(not(unix))] + { + let _ = metadata; + } + Ok(()) +} + +#[derive(Debug)] +pub(crate) struct IpcClientLite { + path: String, + next_id: AtomicU64, +} + +impl IpcClientLite { + fn new(path: String) -> Self { + Self { path, next_id: AtomicU64::new(1) } + } + + async fn request(&self, method: &str, params: Value) -> Result { + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + let req = serde_json::json!({ + "jsonrpc": "2.0", + "id": id, + "method": method, + "params": params, + }); + let mut stream = UnixStream::connect(&self.path) + .await + .map_err(|err| eyre!("failed to connect IPC endpoint {}: {err}", self.path))?; + let encoded = serde_json::to_string(&req)?; + stream.write_all(encoded.as_bytes()).await?; + stream.write_all(b"\n").await?; + + let mut reader = BufReader::new(stream); + let mut line = String::new(); + reader.read_line(&mut line).await?; + if line.trim().is_empty() { + return Err(eyre!("empty IPC response")); + } + + let resp: Value = serde_json::from_str(&line)?; + if let Some(err) = resp.get("error") { + return Err(eyre!("rpc error: {}", err)); + } + resp.get("result").cloned().ok_or_else(|| eyre!("missing result field in IPC response")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::console::endpoint::{ResolvedEndpoint, Transport}; + use jsonrpsee::{RpcModule, server::ServerBuilder}; + use serde_json::json; + use tempfile::tempdir; + use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + net::UnixListener, + }; + + #[test] + fn rpc_params_accept_none_and_null() { + let none_params = RpcParams::from_value(None).unwrap(); + assert!(matches!(none_params, RpcParams::None)); + + let null_params = RpcParams::from_value(Some(Value::Null)).unwrap(); + assert!(matches!(null_params, RpcParams::None)); + } + + #[test] + fn rpc_params_reject_scalar_values() { + let err = match RpcParams::from_value(Some(json!(true))) { + Ok(_) => panic!("expected scalar params to be rejected"), + Err(err) => err, + }; + assert!(err.to_string().contains("rpc params must be null, JSON array, or JSON object")); + } + + #[test] + fn rpc_params_preserve_array_and_object_shapes() { + let array = RpcParams::from_value(Some(json!([1, "x"]))).unwrap(); + assert_eq!(array.into_value(), json!([1, "x"])); + + let object = RpcParams::from_value(Some(json!({"a": 1, "b": "x"}))).unwrap(); + assert_eq!(object.into_value(), json!({"a": 1, "b": "x"})); + } + + #[test] + fn make_headers_valid_and_invalid() { + let headers = make_headers(&[ + ("Authorization".to_owned(), "Bearer token".to_owned()), + ("x-test".to_owned(), "1".to_owned()), + ]) + .unwrap(); + assert_eq!( + headers.get("authorization").expect("authorization header missing"), + "Bearer token" + ); + assert_eq!(headers.get("x-test").expect("x-test missing"), "1"); + + let invalid_name = make_headers(&[("\n".to_owned(), "value".to_owned())]).unwrap_err(); + assert!(invalid_name.to_string().contains("invalid HTTP header name")); + + let invalid_value = make_headers(&[("x-test".to_owned(), "\n".to_owned())]).unwrap_err(); + assert!(invalid_value.to_string().contains("failed to parse header value")); + } + + #[test] + fn validate_ipc_endpoint_errors_for_missing_and_non_socket() { + let missing = validate_ipc_endpoint("/definitely/missing/reth.ipc").unwrap_err(); + assert!(missing.to_string().contains("IPC endpoint not found")); + + let dir = tempdir().expect("tempdir"); + let file_path = dir.path().join("plain-file"); + std::fs::write(&file_path, b"not a socket").expect("write file"); + let err = validate_ipc_endpoint(file_path.to_string_lossy().as_ref()).unwrap_err(); + assert!(err.to_string().contains("not a unix socket")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn rpc_client_http_request_and_supported_modules() { + let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); + let addr = server.local_addr().expect("local addr"); + + let mut module = RpcModule::new(()); + module + .register_method("eth_blockNumber", |_params, _ctx, _ext| { + Ok::(json!("0x10")) + }) + .expect("register eth_blockNumber"); + module + .register_method("rpc_modules", |_params, _ctx, _ext| { + Ok::(json!({ + "eth": "1.0", + "net": "1.0" + })) + }) + .expect("register rpc_modules"); + + let handle = server.start(module); + let endpoint = + ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; + + let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + let block = client.request_value("eth_blockNumber", None).await.unwrap(); + assert_eq!(block, json!("0x10")); + + let modules = client.supported_modules().await.unwrap(); + assert_eq!(modules.get("eth"), Some(&"1.0".to_owned())); + assert_eq!(modules.get("net"), Some(&"1.0".to_owned())); + + handle.stop().expect("stop server"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn rpc_client_ws_request_path() { + let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); + let addr = server.local_addr().expect("local addr"); + + let mut module = RpcModule::new(()); + module + .register_method("web3_clientVersion", |_params, _ctx, _ext| { + Ok::(json!("reth/1.0.0")) + }) + .expect("register method"); + + let handle = server.start(module); + let endpoint = ResolvedEndpoint { raw: format!("ws://{addr}"), transport: Transport::Ws }; + + let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + let version = client.request_value("web3_clientVersion", None).await.unwrap(); + assert_eq!(version, json!("reth/1.0.0")); + + handle.stop().expect("stop server"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn supported_modules_errors_on_invalid_shape() { + let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); + let addr = server.local_addr().expect("local addr"); + + let mut module = RpcModule::new(()); + module + .register_method("rpc_modules", |_params, _ctx, _ext| { + Ok::(json!(["eth", "net"])) + }) + .expect("register rpc_modules"); + + let handle = server.start(module); + let endpoint = + ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; + let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + + let err = client.supported_modules().await.unwrap_err(); + assert!(err.to_string().contains("invalid type")); + + handle.stop().expect("stop server"); + } + + #[tokio::test(flavor = "multi_thread")] + async fn ipc_client_handles_empty_response_and_rpc_error() { + let dir = tempdir().expect("tempdir"); + let socket_path = dir.path().join("reth.ipc"); + let listener = UnixListener::bind(&socket_path).expect("bind socket"); + + let server_task = tokio::spawn(async move { + // First request: reply with an empty line. + let (stream1, _) = listener.accept().await.expect("accept first"); + let mut r1 = BufReader::new(stream1); + let mut req1 = String::new(); + let _ = r1.read_line(&mut req1).await.expect("read first"); + let mut s1 = r1.into_inner(); + s1.write_all(b"\n").await.expect("write empty response"); + + // Second request: reply with JSON-RPC error. + let (stream2, _) = listener.accept().await.expect("accept second"); + let mut r2 = BufReader::new(stream2); + let mut req2 = String::new(); + let _ = r2.read_line(&mut req2).await.expect("read second"); + let mut s2 = r2.into_inner(); + s2.write_all(br#"{"jsonrpc":"2.0","id":2,"error":{"code":-32000,"message":"boom"}}"#) + .await + .expect("write error"); + s2.write_all(b"\n").await.expect("write newline"); + }); + + let client = IpcClientLite::new(socket_path.to_string_lossy().to_string()); + let empty_err = client.request("eth_blockNumber", json!([])).await.unwrap_err(); + assert!(empty_err.to_string().contains("empty IPC response")); + + let rpc_err = client.request("eth_blockNumber", json!([])).await.unwrap_err(); + assert!(rpc_err.to_string().contains("rpc error")); + + server_task.await.expect("server task"); + } +} diff --git a/src/console/rpc_completion.rs b/src/console/rpc_completion.rs new file mode 100644 index 00000000..d50c8973 --- /dev/null +++ b/src/console/rpc_completion.rs @@ -0,0 +1,401 @@ +//! Static method-name hints for the embedded `console` REPL. +//! +//! The REPL completes `namespace.method` tokens (e.g. `eth.getLogs`). JSON-RPC uses +//! `namespace_method`; this module only stores the **suffix** after the namespace +//! (the part after `_` or `.`), taken from upstream reth’s `#[method(name = "...")]` +//! macros in `rpc-eth-api` and `rpc-api`, plus Berachain’s `beraAdmin_*` surface. +//! When upstream adds RPCs, refresh these tables from the same reth sources. +//! +//! Use [`RPC_NAMESPACE_TABLE`] to walk all namespaces, or [`method_suffixes`] / +//! [`dot_completions_for_namespace`] for one namespace. + +/// Berachain extension namespace (`beraAdmin_*` JSON-RPC). +pub const BERA_ADMIN_METHOD_SUFFIXES: &[&str] = + &["detailedPeers", "nodeStatus", "banPeer", "penalizePeer", "prepareCanary", "submitCanary"]; + +/// `eth_*` methods from reth. +pub const ETH_METHOD_SUFFIXES: &[&str] = &[ + "accounts", + "blobBaseFee", + "blockNumber", + "call", + "callBundle", + "callMany", + "cancelBundle", + "cancelPrivateTransaction", + "chainId", + "coinbase", + "config", + "createAccessList", + "estimateGas", + "feeHistory", + "fillTransaction", + "gasPrice", + "getAccount", + "getAccountInfo", + "getBalance", + "getBlockAccessListByBlockHash", + "getBlockAccessListByBlockNumber", + "getBlockByHash", + "getBlockByNumber", + "getBlockReceipts", + "getBlockTransactionCountByHash", + "getBlockTransactionCountByNumber", + "getCode", + "getFilterChanges", + "getFilterLogs", + "getHeaderByHash", + "getHeaderByNumber", + "getLogs", + "getProof", + "getRawTransactionByBlockHashAndIndex", + "getRawTransactionByBlockNumberAndIndex", + "getRawTransactionByHash", + "getStorageAt", + "getTransactionByBlockHashAndIndex", + "getTransactionByBlockNumberAndIndex", + "getTransactionByHash", + "getTransactionBySenderAndNonce", + "getTransactionCount", + "getTransactionReceipt", + "getUncleByBlockHashAndIndex", + "getUncleByBlockNumberAndIndex", + "getUncleCountByBlockHash", + "getUncleCountByBlockNumber", + "getWork", + "hashrate", + "maxPriorityFeePerGas", + "mining", + "newBlockFilter", + "newFilter", + "newPendingTransactionFilter", + "protocolVersion", + "sendBundle", + "sendPrivateRawTransaction", + "sendPrivateTransaction", + "sendRawTransaction", + "sendRawTransactionConditional", + "sendRawTransactionSync", + "sendTransaction", + "sign", + "signTransaction", + "signTypedData", + "simulateV1", + "submitHashrate", + "submitWork", + "syncing", + "uninstallFilter", +]; + +/// `net_*` methods from reth. +pub const NET_METHOD_SUFFIXES: &[&str] = &["listening", "peerCount", "version"]; + +/// `web3_*` methods from reth. +pub const WEB3_METHOD_SUFFIXES: &[&str] = &["clientVersion", "sha3"]; + +/// `txpool_*` methods from reth. +pub const TXPOOL_METHOD_SUFFIXES: &[&str] = &["content", "contentFrom", "inspect", "status"]; + +/// `rpc_*` methods from reth. +pub const RPC_API_METHOD_SUFFIXES: &[&str] = &["modules"]; + +/// `admin_*` methods from reth. +pub const ADMIN_METHOD_SUFFIXES: &[&str] = &[ + "addPeer", + "addTrustedPeer", + "clearTxpool", + "nodeInfo", + "peers", + "removePeer", + "removeTrustedPeer", +]; + +/// `trace_*` methods from reth. +pub const TRACE_METHOD_SUFFIXES: &[&str] = &[ + "block", + "blockOpcodeGas", + "call", + "callMany", + "filter", + "get", + "rawTransaction", + "replayBlockTransactions", + "replayTransaction", + "transaction", + "transactionOpcodeGas", +]; + +/// `debug_*` methods from reth. +pub const DEBUG_METHOD_SUFFIXES: &[&str] = &[ + "accountRange", + "backtraceAt", + "blockProfile", + "chainConfig", + "chaindbCompact", + "chaindbProperty", + "codeByHash", + "cpuProfile", + "dbAncient", + "dbAncients", + "dbGet", + "dumpBlock", + "executePayload", + "executionWitness", + "executionWitnessByBlockHash", + "freeOSMemory", + "freezeClient", + "gcStats", + "getAccessibleState", + "getBadBlocks", + "getBlockAccessList", + "getModifiedAccountsByHash", + "getModifiedAccountsByNumber", + "getRawBlock", + "getRawHeader", + "getRawReceipts", + "getRawTransaction", + "getRawTransactions", + "goTrace", + "intermediateRoots", + "memStats", + "mutexProfile", + "preimage", + "printBlock", + "seedHash", + "setBlockProfileRate", + "setGCPercent", + "setHead", + "setMutexProfileFraction", + "setTrieFlushInterval", + "stacks", + "standardTraceBadBlockToFile", + "standardTraceBlockToFile", + "startCPUProfile", + "startGoTrace", + "stateRootWithUpdates", + "stopCPUProfile", + "stopGoTrace", + "storageRangeAt", + "traceBadBlock", + "traceBlock", + "traceBlockByHash", + "traceBlockByNumber", + "traceCall", + "traceCallMany", + "traceChain", + "traceTransaction", + "verbosity", + "vmodule", + "writeBlockProfile", + "writeMemProfile", + "writeMutexProfile", +]; + +/// `engine_*` methods from reth. +pub const ENGINE_METHOD_SUFFIXES: &[&str] = &[ + "blockNumber", + "call", + "chainId", + "exchangeCapabilities", + "forkchoiceUpdatedV1", + "forkchoiceUpdatedV2", + "forkchoiceUpdatedV3", + "getBlobsV1", + "getBlobsV2", + "getBlobsV3", + "getBlockByHash", + "getBlockByNumber", + "getBlockReceipts", + "getClientVersionV1", + "getCode", + "getLogs", + "getPayloadBodiesByHashV1", + "getPayloadBodiesByHashV2", + "getPayloadBodiesByRangeV1", + "getPayloadBodiesByRangeV2", + "getPayloadV1", + "getPayloadV2", + "getPayloadV3", + "getPayloadV4", + "getPayloadV5", + "getPayloadV6", + "getProof", + "getTransactionReceipt", + "newPayloadV1", + "newPayloadV2", + "newPayloadV3", + "newPayloadV4", + "newPayloadV5", + "sendRawTransaction", + "syncing", +]; + +/// `reth_*` methods from reth. +pub const RETH_METHOD_SUFFIXES: &[&str] = &[ + "getBalanceChangesInBlock", + "subscribeChainNotifications", + "subscribeFinalizedChainNotifications", + "subscribePersistedBlock", +]; + +/// `ots_*` methods from reth. +pub const OTS_METHOD_SUFFIXES: &[&str] = &[ + "getApiLevel", + "getBlockDetails", + "getBlockDetailsByHash", + "getBlockTransactions", + "getContractCreator", + "getHeaderByNumber", + "getInternalOperations", + "getTransactionBySenderAndNonce", + "getTransactionError", + "hasCode", + "searchTransactionsAfter", + "searchTransactionsBefore", + "traceTransaction", +]; + +/// `miner_*` methods from reth. +pub const MINER_METHOD_SUFFIXES: &[&str] = &["setExtra", "setGasLimit", "setGasPrice"]; + +/// `mev_*` methods from reth. +pub const MEV_METHOD_SUFFIXES: &[&str] = &["sendBundle", "simBundle"]; + +/// `testing_*` methods from reth. +pub const TESTING_METHOD_SUFFIXES: &[&str] = &["buildBlockV1"]; + +/// `flashbots_*` methods from reth. +pub const FLASHBOTS_METHOD_SUFFIXES: &[&str] = &[ + "validateBuilderSubmissionV1", + "validateBuilderSubmissionV2", + "validateBuilderSubmissionV3", + "validateBuilderSubmissionV4", + "validateBuilderSubmissionV5", +]; + +/// `anvil_*` methods from reth. +pub const ANVIL_METHOD_SUFFIXES: &[&str] = &[ + "anvil_dropTransaction", + "autoImpersonateAccount", + "dumpState", + "enableTraces", + "getAutomine", + "impersonateAccount", + "increaseTime", + "loadState", + "metadata", + "mine", + "mine_detailed", + "nodeInfo", + "removeBlockTimestampInterval", + "removePoolTransactions", + "reset", + "revert", + "setAutomine", + "setBalance", + "setBlockGasLimit", + "setBlockTimestampInterval", + "setChainId", + "setCode", + "setCoinbase", + "setIntervalMining", + "setLoggingEnabled", + "setMinGasPrice", + "setNextBlockBaseFeePerGas", + "setNextBlockTimestamp", + "setNonce", + "setRpcUrl", + "setStorageAt", + "setTime", + "snapshot", + "stopImpersonatingAccount", +]; + +/// `hardhat_*` methods from reth. +pub const HARDHAT_METHOD_SUFFIXES: &[&str] = &[ + "getAutomine", + "hardhat_dropTransaction", + "impersonateAccount", + "metadata", + "mine", + "reset", + "setBalance", + "setCode", + "setCoinbase", + "setLoggingEnabled", + "setMinGasPrice", + "setNextBlockBaseFeePerGas", + "setNonce", + "setPrevRandao", + "setStorageAt", + "stopImpersonatingAccount", +]; + +/// All built-in namespaces and their method suffix slices. +pub const RPC_NAMESPACE_TABLE: &[(&str, &[&str])] = &[ + ("eth", ETH_METHOD_SUFFIXES), + ("net", NET_METHOD_SUFFIXES), + ("web3", WEB3_METHOD_SUFFIXES), + ("txpool", TXPOOL_METHOD_SUFFIXES), + ("rpc", RPC_API_METHOD_SUFFIXES), + ("admin", ADMIN_METHOD_SUFFIXES), + ("trace", TRACE_METHOD_SUFFIXES), + ("debug", DEBUG_METHOD_SUFFIXES), + ("engine", ENGINE_METHOD_SUFFIXES), + ("reth", RETH_METHOD_SUFFIXES), + ("ots", OTS_METHOD_SUFFIXES), + ("miner", MINER_METHOD_SUFFIXES), + ("mev", MEV_METHOD_SUFFIXES), + ("testing", TESTING_METHOD_SUFFIXES), + ("flashbots", FLASHBOTS_METHOD_SUFFIXES), + ("anvil", ANVIL_METHOD_SUFFIXES), + ("hardhat", HARDHAT_METHOD_SUFFIXES), + ("beraAdmin", BERA_ADMIN_METHOD_SUFFIXES), +]; + +/// Returns method suffixes for a namespace, or empty if unknown. +pub fn method_suffixes(namespace: &str) -> &[&str] { + for (name, suffixes) in RPC_NAMESPACE_TABLE { + if *name == namespace { + return suffixes; + } + } + &[] +} + +/// `namespace.method` strings for rustyline completion. +pub fn dot_completions_for_namespace(namespace: &str) -> Vec { + method_suffixes(namespace).iter().map(|suffix| format!("{namespace}.{suffix}")).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn eth_includes_common_methods() { + assert!(ETH_METHOD_SUFFIXES.contains(&"getLogs")); + assert!(ETH_METHOD_SUFFIXES.contains(&"blockNumber")); + } + + #[test] + fn unknown_namespace_empty() { + assert!(method_suffixes("not_a_real_ns").is_empty()); + } + + #[test] + fn table_covers_all_consts() { + let mut seen = 0usize; + for (name, suffixes) in RPC_NAMESPACE_TABLE { + assert!(!suffixes.is_empty(), "namespace {name} has no suffixes"); + seen += 1; + } + assert!(seen >= 10); + } + + #[test] + fn bera_admin_dot_forms() { + let v = dot_completions_for_namespace("beraAdmin"); + assert!(v.iter().any(|s| s == "beraAdmin.detailedPeers")); + } +} diff --git a/src/console/run.rs b/src/console/run.rs new file mode 100644 index 00000000..d04a8a88 --- /dev/null +++ b/src/console/run.rs @@ -0,0 +1,76 @@ +use super::{ + cli::ConsoleCommand, + endpoint::resolve_endpoint, + exec::run_exec, + repl::{history_file_path, run_repl}, + rpc::RpcClient, +}; +use eyre::Result; +use serde_json::Value; +use std::collections::BTreeMap; + +pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { + let endpoint = resolve_endpoint(cmd.endpoint.as_deref())?; + let rpc = RpcClient::connect(&endpoint, &[]).await?; + + let chain_id = + rpc.request_value("eth_chainId", None).await.ok().and_then(|v| parse_chain_id(&v)); + + let bera_admin_status = rpc.request_value("beraAdmin_nodeStatus", None).await.ok(); + let has_bera_admin = bera_admin_status.is_some(); + + let mut rpc_aliases = default_aliases(); + if has_bera_admin { + for (alias, method) in [ + ("peers", "beraAdmin_detailedPeers"), + ("status", "beraAdmin_nodeStatus"), + ("ban", "beraAdmin_banPeer"), + ("penalize", "beraAdmin_penalizePeer"), + ] { + rpc_aliases.insert(alias.to_owned(), method.to_owned()); + } + } + + if let Some(script) = cmd.exec.as_deref() { + run_exec(&rpc, script, &rpc_aliases).await?; + } else { + run_repl( + &rpc, + history_file_path(), + endpoint, + &rpc_aliases, + chain_id, + cmd.raw, + has_bera_admin, + bera_admin_status, + ) + .await?; + } + + Ok(()) +} + +fn default_aliases() -> BTreeMap { + [ + ("eth.blockNumber", "eth_blockNumber"), + ("net.version", "net_version"), + ("web3.clientVersion", "web3_clientVersion"), + ] + .into_iter() + .map(|(k, v)| (k.to_owned(), v.to_owned())) + .collect() +} + +fn parse_chain_id(value: &Value) -> Option { + match value { + Value::String(s) => { + if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + u64::from_str_radix(hex, 16).ok() + } else { + s.parse().ok() + } + } + Value::Number(n) => n.as_u64(), + _ => None, + } +} diff --git a/src/lib.rs b/src/lib.rs index 30b4118d..cc3d3e4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,10 @@ //! //! Built on Reth SDK with Ethereum compatibility plus Prague1 hardfork for minimum base fee. +pub mod berachain_cli; pub mod chainspec; pub mod consensus; +pub mod console; pub mod engine; pub mod evm; pub mod genesis; diff --git a/src/main.rs b/src/main.rs index d07ba485..77a8161c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator(); use bera_reth::{ + berachain_cli::BerachainSubcommands, chainspec::{BerachainChainSpec, BerachainChainSpecParser}, consensus::BerachainBeaconConsensus, evm::BerachainEvmFactory, @@ -13,9 +14,10 @@ use bera_reth::{ use clap::Parser; use reth::CliRunner; use reth_cli_commands::node::NoArgs; -use reth_ethereum_cli::Cli; +use reth_ethereum_cli::interface::{Cli, Commands}; use reth_node_builder::NodeHandle; -use std::sync::Arc; +use reth_rpc_server_types::DefaultRpcModuleValidator; +use std::{marker::PhantomData, sync::Arc}; use tracing::info; /// Persist every canonical block to disk immediately rather than buffering. @@ -47,20 +49,47 @@ fn main() { ) }; - if let Err(err) = Cli::::parse() - .with_runner_and_components::( - CliRunner::try_default_runtime().expect("Failed to create default runtime"), - cli_components_builder, - async move |builder, _| { - info!(target: "reth::cli", "Launching Berachain node"); - let NodeHandle { node: _node, node_exit_future } = - builder.node(BerachainNode::default()).launch_with_debug_capabilities().await?; + let cli = Cli::< + BerachainChainSpecParser, + NoArgs, + DefaultRpcModuleValidator, + BerachainSubcommands, + >::parse(); - node_exit_future.await - }, - ) - { - eprintln!("Error: {err:?}"); - std::process::exit(1); + let reth_ethereum_cli::interface::Cli { command, logs, traces, _phantom } = cli; + + match command { + Commands::Ext(BerachainSubcommands::Console(console_cmd)) => { + let runner = + CliRunner::try_default_runtime().expect("Failed to create default runtime"); + if let Err(err) = console_cmd.run(runner) { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } + } + other => { + let cli = Cli { + command: other, + logs, + traces, + _phantom: PhantomData::, + }; + if let Err(err) = cli.with_runner_and_components::( + CliRunner::try_default_runtime().expect("Failed to create default runtime"), + cli_components_builder, + async move |builder, _| { + info!(target: "reth::cli", "Launching Berachain node"); + let NodeHandle { node: _node, node_exit_future } = builder + .node(BerachainNode::default()) + .launch_with_debug_capabilities() + .await?; + + node_exit_future.await + }, + ) { + eprintln!("Error: {err:?}"); + std::process::exit(1); + } + } } } From 894d6aadc57f7fdb1d0719282fa859a4813e1bdc Mon Sep 17 00:00:00 2001 From: Camembear Date: Thu, 16 Apr 2026 17:55:53 -0400 Subject: [PATCH 02/21] chore: remove http_headers dead code from console --- Cargo.lock | 1 - Cargo.toml | 1 - src/console/rpc.rs | 49 ++++++---------------------------------------- src/console/run.rs | 2 +- 4 files changed, 7 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ad7a6f1..6c8be4b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1473,7 +1473,6 @@ dependencies = [ "derive_more", "dirs", "eyre", - "http", "jsonrpsee", "jsonrpsee-core", "jsonrpsee-proc-macros", diff --git a/Cargo.toml b/Cargo.toml index 1707eef4..f84870b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ clap = { version = "4.5.40", features = ["derive"] } derive_more = "2.0.1" dirs = "6.0.0" eyre = "0.6.12" -http = "1.3.1" libc = "0.2.177" rustyline = "17.0.2" sha2 = "0.10" diff --git a/src/console/rpc.rs b/src/console/rpc.rs index eecdb6bc..9897b580 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -1,6 +1,5 @@ use super::endpoint::{ResolvedEndpoint, Transport}; use eyre::{Result, eyre}; -use http::{HeaderMap, HeaderName, HeaderValue}; use jsonrpsee::{ core::{ client::ClientT, @@ -30,20 +29,14 @@ pub enum RpcClient { } impl RpcClient { - pub async fn connect( - endpoint: &ResolvedEndpoint, - http_headers: &[(String, String)], - ) -> Result { - let headers = make_headers(http_headers)?; + pub async fn connect(endpoint: &ResolvedEndpoint) -> Result { match endpoint.transport { Transport::Http => { - let client = - HttpClientBuilder::default().set_headers(headers).build(&endpoint.raw)?; + let client = HttpClientBuilder::default().build(&endpoint.raw)?; Ok(Self::Http(client)) } Transport::Ws => { - let client = - WsClientBuilder::default().set_headers(headers).build(&endpoint.raw).await?; + let client = WsClientBuilder::default().build(&endpoint.raw).await?; Ok(Self::Ws(client)) } Transport::Ipc => { @@ -124,16 +117,6 @@ fn object_params(values: Map) -> Result { Ok(params) } -fn make_headers(headers: &[(String, String)]) -> Result { - let mut out = HeaderMap::new(); - for (k, v) in headers { - let key = HeaderName::from_bytes(k.as_bytes())?; - let value = HeaderValue::from_str(v)?; - out.insert(key, value); - } - Ok(out) -} - fn validate_ipc_endpoint(path: &str) -> Result<()> { let endpoint = Path::new(path); if !endpoint.exists() { @@ -245,26 +228,6 @@ mod tests { assert_eq!(object.into_value(), json!({"a": 1, "b": "x"})); } - #[test] - fn make_headers_valid_and_invalid() { - let headers = make_headers(&[ - ("Authorization".to_owned(), "Bearer token".to_owned()), - ("x-test".to_owned(), "1".to_owned()), - ]) - .unwrap(); - assert_eq!( - headers.get("authorization").expect("authorization header missing"), - "Bearer token" - ); - assert_eq!(headers.get("x-test").expect("x-test missing"), "1"); - - let invalid_name = make_headers(&[("\n".to_owned(), "value".to_owned())]).unwrap_err(); - assert!(invalid_name.to_string().contains("invalid HTTP header name")); - - let invalid_value = make_headers(&[("x-test".to_owned(), "\n".to_owned())]).unwrap_err(); - assert!(invalid_value.to_string().contains("failed to parse header value")); - } - #[test] fn validate_ipc_endpoint_errors_for_missing_and_non_socket() { let missing = validate_ipc_endpoint("/definitely/missing/reth.ipc").unwrap_err(); @@ -301,7 +264,7 @@ mod tests { let endpoint = ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; - let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + let client = RpcClient::connect(&endpoint).await.unwrap(); let block = client.request_value("eth_blockNumber", None).await.unwrap(); assert_eq!(block, json!("0x10")); @@ -327,7 +290,7 @@ mod tests { let handle = server.start(module); let endpoint = ResolvedEndpoint { raw: format!("ws://{addr}"), transport: Transport::Ws }; - let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + let client = RpcClient::connect(&endpoint).await.unwrap(); let version = client.request_value("web3_clientVersion", None).await.unwrap(); assert_eq!(version, json!("reth/1.0.0")); @@ -349,7 +312,7 @@ mod tests { let handle = server.start(module); let endpoint = ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; - let client = RpcClient::connect(&endpoint, &[]).await.unwrap(); + let client = RpcClient::connect(&endpoint).await.unwrap(); let err = client.supported_modules().await.unwrap_err(); assert!(err.to_string().contains("invalid type")); diff --git a/src/console/run.rs b/src/console/run.rs index d04a8a88..7ad86a2e 100644 --- a/src/console/run.rs +++ b/src/console/run.rs @@ -11,7 +11,7 @@ use std::collections::BTreeMap; pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { let endpoint = resolve_endpoint(cmd.endpoint.as_deref())?; - let rpc = RpcClient::connect(&endpoint, &[]).await?; + let rpc = RpcClient::connect(&endpoint).await?; let chain_id = rpc.request_value("eth_chainId", None).await.ok().and_then(|v| parse_chain_id(&v)); From a71e401e180dc20170675b0cf8de64c6704f837f Mon Sep 17 00:00:00 2001 From: Camembear Date: Thu, 16 Apr 2026 18:29:07 -0400 Subject: [PATCH 03/21] fix: address PR review findings - Remove undocumented removeAllPeers alias (destructive, not in public contract) - Drop anvil/hardhat completion tables (not relevant to bera-reth operators) - Handle string-encoded hex/decimal in node status formatter (chainId, headNumber, peer counts) - Deduplicate hex_or_decimal_to_u64 into output module --- src/console/engine.rs | 41 ------------------------ src/console/output.rs | 24 +++++++++++--- src/console/repl.rs | 16 +--------- src/console/rpc_completion.rs | 60 ----------------------------------- 4 files changed, 20 insertions(+), 121 deletions(-) diff --git a/src/console/engine.rs b/src/console/engine.rs index f25330a5..d5253d6e 100644 --- a/src/console/engine.rs +++ b/src/console/engine.rs @@ -42,10 +42,6 @@ pub async fn evaluate_line( Ok(EvalOutcome::Value(result)) } InputCommand::Alias(alias) => { - if is_remove_all_peers_alias(alias.as_str()) { - return run_remove_all_peers(rpc, last_rpc_result).await; - } - let method = resolve_alias_method(aliases, &alias); let value = rpc.request_value(&method, None).await?; *last_rpc_result = Some(value.clone()); @@ -68,43 +64,6 @@ fn resolve_alias_method(aliases: &BTreeMap, alias: &str) -> Stri aliases.get(alias).cloned().unwrap_or_else(|| alias.replace('.', "_")) } -fn is_remove_all_peers_alias(alias: &str) -> bool { - matches!(alias, "removeAllPeers" | "admin.removeAllPeers") -} - -async fn run_remove_all_peers( - rpc: &RpcClient, - last_rpc_result: &mut Option, -) -> Result { - let peers = rpc.request_value("admin_peers", None).await?; - let arr = peers.as_array().ok_or_else(|| eyre!("admin.peers did not return an array"))?; - let total = arr.len() as u64; - let mut removed = 0u64; - let mut failed = 0u64; - for peer in arr { - let enode = peer - .get("enode") - .and_then(Value::as_str) - .ok_or_else(|| eyre!("peer entry missing enode"))?; - match rpc.request_value("admin_removePeer", Some(serde_json::json!([enode]))).await { - Ok(Value::Bool(true)) => removed += 1, - Ok(other) => { - eprintln!("admin.removePeer({enode}): unexpected response: {other}"); - failed += 1; - } - Err(err) => { - eprintln!("admin.removePeer({enode}): {err}"); - failed += 1; - } - } - } - *last_rpc_result = Some(peers); - eprintln!("Removed {removed}/{total} peer(s)."); - Ok(EvalOutcome::Value( - serde_json::json!({ "removed": removed, "failed": failed, "total": total }), - )) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/console/output.rs b/src/console/output.rs index 96246a63..9272ff56 100644 --- a/src/console/output.rs +++ b/src/console/output.rs @@ -2,6 +2,20 @@ use serde_json::Value; const DEFAULT_NATIVE_SYMBOL: &str = "ETH"; +pub(crate) fn hex_or_decimal_to_u64(value: &Value) -> Option { + match value { + Value::String(s) => { + if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + u64::from_str_radix(hex, 16).ok() + } else { + s.parse::().ok() + } + } + Value::Number(n) => n.as_u64(), + _ => None, + } +} + pub fn print_value_for_chain(value: &Value, chain_id: Option) { print_value_with_symbol(value, native_symbol_for_chain_id(chain_id), false); } @@ -256,7 +270,7 @@ fn try_format_node_status(value: &Value) -> Option { .or_else(|| obj.get("chain_id")) .or_else(|| obj.get("networkId")) .or_else(|| obj.get("network_id")) - .and_then(|v| v.as_u64()) + .and_then(hex_or_decimal_to_u64) .unwrap_or(0); let genesis = obj .get("genesisHash") @@ -271,7 +285,7 @@ fn try_format_node_status(value: &Value) -> Option { let head = obj .get("headNumber") .or_else(|| obj.get("head_number")) - .and_then(|v| v.as_u64()) + .and_then(hex_or_decimal_to_u64) .unwrap_or(0); let head_hash = obj .get("headHash") @@ -288,17 +302,17 @@ fn try_format_node_status(value: &Value) -> Option { let peers_total = obj .get("peerCountTotal") .or_else(|| obj.get("peer_count_total")) - .and_then(|v| v.as_u64()) + .and_then(hex_or_decimal_to_u64) .unwrap_or(0); let peers_in = obj .get("peerCountInbound") .or_else(|| obj.get("peer_count_inbound")) - .and_then(|v| v.as_u64()) + .and_then(hex_or_decimal_to_u64) .unwrap_or(0); let peers_out = obj .get("peerCountOutbound") .or_else(|| obj.get("peer_count_outbound")) - .and_then(|v| v.as_u64()) + .and_then(hex_or_decimal_to_u64) .unwrap_or(0); let client = obj .get("clientVersion") diff --git a/src/console/repl.rs b/src/console/repl.rs index 264ec41f..66a60b2d 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -1,7 +1,7 @@ use super::{ endpoint::{ResolvedEndpoint, default_datadir}, engine::{EvalOutcome, evaluate_line}, - output::print_value_for_chain_raw, + output::{hex_or_decimal_to_u64, print_value_for_chain_raw}, rpc::RpcClient, }; use eyre::Result; @@ -143,20 +143,6 @@ fn as_string(value: &Value) -> Option { } } -fn hex_or_decimal_to_u64(value: &Value) -> Option { - match value { - Value::String(s) => { - if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { - u64::from_str_radix(hex, 16).ok() - } else { - s.parse::().ok() - } - } - Value::Number(n) => n.as_u64(), - _ => None, - } -} - fn print_help(aliases: &BTreeMap, has_bera_admin: bool) { println!("Commands:"); println!(" [json_params] (RPC call)"); diff --git a/src/console/rpc_completion.rs b/src/console/rpc_completion.rs index d50c8973..3e688a8f 100644 --- a/src/console/rpc_completion.rs +++ b/src/console/rpc_completion.rs @@ -273,64 +273,6 @@ pub const FLASHBOTS_METHOD_SUFFIXES: &[&str] = &[ "validateBuilderSubmissionV5", ]; -/// `anvil_*` methods from reth. -pub const ANVIL_METHOD_SUFFIXES: &[&str] = &[ - "anvil_dropTransaction", - "autoImpersonateAccount", - "dumpState", - "enableTraces", - "getAutomine", - "impersonateAccount", - "increaseTime", - "loadState", - "metadata", - "mine", - "mine_detailed", - "nodeInfo", - "removeBlockTimestampInterval", - "removePoolTransactions", - "reset", - "revert", - "setAutomine", - "setBalance", - "setBlockGasLimit", - "setBlockTimestampInterval", - "setChainId", - "setCode", - "setCoinbase", - "setIntervalMining", - "setLoggingEnabled", - "setMinGasPrice", - "setNextBlockBaseFeePerGas", - "setNextBlockTimestamp", - "setNonce", - "setRpcUrl", - "setStorageAt", - "setTime", - "snapshot", - "stopImpersonatingAccount", -]; - -/// `hardhat_*` methods from reth. -pub const HARDHAT_METHOD_SUFFIXES: &[&str] = &[ - "getAutomine", - "hardhat_dropTransaction", - "impersonateAccount", - "metadata", - "mine", - "reset", - "setBalance", - "setCode", - "setCoinbase", - "setLoggingEnabled", - "setMinGasPrice", - "setNextBlockBaseFeePerGas", - "setNonce", - "setPrevRandao", - "setStorageAt", - "stopImpersonatingAccount", -]; - /// All built-in namespaces and their method suffix slices. pub const RPC_NAMESPACE_TABLE: &[(&str, &[&str])] = &[ ("eth", ETH_METHOD_SUFFIXES), @@ -348,8 +290,6 @@ pub const RPC_NAMESPACE_TABLE: &[(&str, &[&str])] = &[ ("mev", MEV_METHOD_SUFFIXES), ("testing", TESTING_METHOD_SUFFIXES), ("flashbots", FLASHBOTS_METHOD_SUFFIXES), - ("anvil", ANVIL_METHOD_SUFFIXES), - ("hardhat", HARDHAT_METHOD_SUFFIXES), ("beraAdmin", BERA_ADMIN_METHOD_SUFFIXES), ]; From 03ba0b65e52da39fa4cb0d621a00dc5d11c77dc0 Mon Sep 17 00:00:00 2001 From: Camembear Date: Thu, 16 Apr 2026 18:35:33 -0400 Subject: [PATCH 04/21] fix: tighten wei heuristic, add IPC timeout, harden completions Caps wei annotation at 10^28 (10B tokens). Wraps IPC requests in a 30-second timeout so a stalled daemon cannot freeze the REPL. Uses char_indices for UTF-8-safe completion word boundaries. Renames misleading test. --- src/console/engine.rs | 4 +--- src/console/output.rs | 13 ++++++++++--- src/console/repl.rs | 11 ++++++++--- src/console/rpc.rs | 7 +++++++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/console/engine.rs b/src/console/engine.rs index d5253d6e..7a92615b 100644 --- a/src/console/engine.rs +++ b/src/console/engine.rs @@ -93,11 +93,9 @@ mod tests { } #[test] - fn rpc_with_query_stores_raw_result_in_last() { + fn apply_query_on_array_returns_count() { let peers = json!([{"id": "a"}, {"id": "b"}]); let count = apply_query(".count", &peers).unwrap(); - let last: Option = Some(peers.clone()); assert_eq!(count, json!(2)); - assert_eq!(last, Some(peers)); } } diff --git a/src/console/output.rs b/src/console/output.rs index 9272ff56..0b20421e 100644 --- a/src/console/output.rs +++ b/src/console/output.rs @@ -2,6 +2,14 @@ use serde_json::Value; const DEFAULT_NATIVE_SYMBOL: &str = "ETH"; +/// Berachain mainnet (80094) and bepolia testnet (80069) chain IDs. +pub(crate) const BERACHAIN_CHAIN_IDS: &[u64] = &[80_069, 80_094]; + +/// ~0.001 native tokens in wei — below this, unlikely to be a balance worth annotating. +const WEI_HEURISTIC_MIN: u128 = 1_000_000_000_000_000; +/// ~10 billion native tokens in wei — above this, not a plausible balance. +const WEI_HEURISTIC_MAX: u128 = 10_000_000_000_000_000_000_000_000_000; + pub(crate) fn hex_or_decimal_to_u64(value: &Value) -> Option { match value { Value::String(s) => { @@ -30,7 +38,7 @@ pub fn print_value_for_chain_raw(value: &Value, chain_id: Option, raw: bool pub fn native_symbol_for_chain_id(chain_id: Option) -> &'static str { match chain_id { - Some(80_069) | Some(80_094) => "BERA", + Some(id) if BERACHAIN_CHAIN_IDS.contains(&id) => "BERA", _ => DEFAULT_NATIVE_SYMBOL, } } @@ -133,8 +141,7 @@ fn decimal_like_wei(input: &str) -> Option { } fn looks_like_wei(value: u128) -> bool { - // Heuristic: large integer range typically used for wei amounts. - (1_000_000_000_000_000u128..=1_000_000_000_000_000_000_000_000_000_000_000u128).contains(&value) + (WEI_HEURISTIC_MIN..=WEI_HEURISTIC_MAX).contains(&value) } fn format_eth(wei: u128) -> String { diff --git a/src/console/repl.rs b/src/console/repl.rs index 66a60b2d..3a0f1e9f 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -1,7 +1,7 @@ use super::{ endpoint::{ResolvedEndpoint, default_datadir}, engine::{EvalOutcome, evaluate_line}, - output::{hex_or_decimal_to_u64, print_value_for_chain_raw}, + output::{BERACHAIN_CHAIN_IDS, hex_or_decimal_to_u64, print_value_for_chain_raw}, rpc::RpcClient, }; use eyre::Result; @@ -130,7 +130,7 @@ async fn print_startup_snapshot( fn chain_emoji(chain_id: Option) -> &'static str { match chain_id { - Some(80_069) | Some(80_094) => " 🐻", + Some(id) if BERACHAIN_CHAIN_IDS.contains(&id) => " 🐻", _ => "", } } @@ -235,7 +235,12 @@ impl Completer for CompletionHelper { ) -> rustyline::Result<(usize, Vec)> { let safe_pos = pos.min(line.len()); let up_to_cursor = &line[..safe_pos]; - let start = up_to_cursor.rfind(char::is_whitespace).map(|i| i + 1).unwrap_or(0); + let start = up_to_cursor + .char_indices() + .rev() + .find(|(_, c)| c.is_whitespace()) + .map(|(i, c)| i + c.len_utf8()) + .unwrap_or(0); let needle = &up_to_cursor[start..]; let matches = self .words diff --git a/src/console/rpc.rs b/src/console/rpc.rs index 9897b580..83821a58 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -160,6 +160,13 @@ impl IpcClientLite { } async fn request(&self, method: &str, params: Value) -> Result { + const IPC_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30); + tokio::time::timeout(IPC_TIMEOUT, self.request_inner(method, params)) + .await + .map_err(|_| eyre!("IPC request timed out after {IPC_TIMEOUT:?}"))? + } + + async fn request_inner(&self, method: &str, params: Value) -> Result { let id = self.next_id.fetch_add(1, Ordering::Relaxed); let req = serde_json::json!({ "jsonrpc": "2.0", From d2587f6e0d65dcf64f19900ffb0b0d86f02d0222 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 11:48:15 -0400 Subject: [PATCH 05/21] fix(console): detect endpoint transport case-insensitively Compare schemes against the ASCII-lowercased endpoint so HTTP:// and http:// both resolve to HTTP while preserving the original string for errors and ResolvedEndpoint::raw. Adds a regression test for an uppercase scheme. --- src/console/endpoint.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/console/endpoint.rs b/src/console/endpoint.rs index 2e9a60a6..65900869 100644 --- a/src/console/endpoint.rs +++ b/src/console/endpoint.rs @@ -35,13 +35,14 @@ pub fn resolve_endpoint(endpoint: Option<&str>) -> Result { } fn detect_transport(endpoint: &str) -> Result { - if endpoint.starts_with("http://") || endpoint.starts_with("https://") { + let lower = endpoint.to_ascii_lowercase(); + if lower.starts_with("http://") || lower.starts_with("https://") { return Ok(Transport::Http); } - if endpoint.starts_with("ws://") || endpoint.starts_with("wss://") { + if lower.starts_with("ws://") || lower.starts_with("wss://") { return Ok(Transport::Ws); } - if endpoint.contains("://") { + if lower.contains("://") { bail!("unsupported endpoint scheme in {endpoint:?}"); } Ok(Transport::Ipc) @@ -64,6 +65,14 @@ mod tests { assert_eq!(got.transport, Transport::Http); } + #[test] + fn parses_http_uppercase_scheme_preserves_raw() { + let raw = "HTTP://127.0.0.1:8545"; + let got = resolve_endpoint(Some(raw)).unwrap(); + assert_eq!(got.transport, Transport::Http); + assert_eq!(got.raw, raw); + } + #[test] fn parses_ws() { let got = resolve_endpoint(Some("ws://127.0.0.1:8546")).unwrap(); From d2d0a5ee8ca4adf27d039be4a16bdfd52a0f2ba8 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 11:48:20 -0400 Subject: [PATCH 06/21] test(console): cover IPC paths without URL schemes Assert Windows-style named pipes and colon-containing relative paths stay Transport::Ipc so scheme detection does not regress. --- src/console/endpoint.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/console/endpoint.rs b/src/console/endpoint.rs index 65900869..13f8da68 100644 --- a/src/console/endpoint.rs +++ b/src/console/endpoint.rs @@ -84,4 +84,20 @@ mod tests { let err = resolve_endpoint(Some("ftp://example.test")).unwrap_err(); assert!(err.to_string().contains("unsupported endpoint scheme")); } + + #[test] + fn windows_named_pipe_path_is_ipc() { + let raw = r"\\.\pipe\reth.ipc"; + let got = resolve_endpoint(Some(raw)).unwrap(); + assert_eq!(got.transport, Transport::Ipc); + assert_eq!(got.raw, raw); + } + + #[test] + fn relative_path_with_colons_is_ipc() { + let raw = "relative:with:segments/reth.ipc"; + let got = resolve_endpoint(Some(raw)).unwrap(); + assert_eq!(got.transport, Transport::Ipc); + assert_eq!(got.raw, raw); + } } From eeeb239a4187390b71c5581effc1bfafd08e7fd4 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 11:48:21 -0400 Subject: [PATCH 07/21] fix(console): parse .map() selector with balanced parentheses Track parenthesis depth from the opening (.map( so the closing ) matches the map call, not the first ) in the expression. --- src/console/query.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/console/query.rs b/src/console/query.rs index f4eb5768..471cccb3 100644 --- a/src/console/query.rs +++ b/src/console/query.rs @@ -55,7 +55,22 @@ fn parse_map(expr: &str) -> Result> { if !expr.starts_with(".map(") { return Ok(None); } - let close = expr.find(')').ok_or_else(|| eyre!("invalid .map(...) expression"))?; + let mut depth = 1usize; + let mut close = None; + for (i, b) in expr.bytes().enumerate().skip(5) { + match b { + b'(' => depth += 1, + b')' => { + depth -= 1; + if depth == 0 { + close = Some(i); + break; + } + } + _ => {} + } + } + let close = close.ok_or_else(|| eyre!("invalid .map(...) expression"))?; let inner = &expr[5..close]; if !inner.starts_with('.') { bail!("map selector must start with '.'"); From 8c6a901f1b7c1464c68a7cda4e81abb5534ac004 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 11:48:21 -0400 Subject: [PATCH 08/21] fix(console): clarify rpc_modules JSON parse failures Wrap serde_json::from_value errors with context when decoding the rpc_modules response. --- src/console/rpc.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/console/rpc.rs b/src/console/rpc.rs index 83821a58..6923c6d9 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -58,8 +58,9 @@ impl RpcClient { pub async fn supported_modules(&self) -> Result> { let value = self.request_value("rpc_modules", None).await?; - let map = serde_json::from_value(value)?; - Ok(map) + serde_json::from_value(value).map_err(|e| { + eyre!("failed to parse rpc_modules response as a JSON object: {e}") + }) } } From 855974643615a9873d17a7da08baf4fbc6df9875 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 11:48:23 -0400 Subject: [PATCH 09/21] fix(console): show empty state for detailed peers table Return a short placeholder when the peer list is empty so output matches other table formatters instead of raw []. --- src/console/output.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/output.rs b/src/console/output.rs index 0b20421e..1b1d6036 100644 --- a/src/console/output.rs +++ b/src/console/output.rs @@ -161,7 +161,7 @@ fn format_eth(wei: u128) -> String { fn try_format_detailed_peers(value: &Value) -> Option { let peers = value.as_array()?; if peers.is_empty() { - return None; + return Some("-- no peers connected --".to_string()); } let first = peers.first()?; From 2d1bcfb7d60fe35c0053213980d24eadca31aff2 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:10:27 -0400 Subject: [PATCH 10/21] refactor(console): remove named alias table and shortcuts Drop default and beraAdmin shortcut mappings (peers/status/ban/penalize). REPL completion still uses rpc_modules namespaces. Single-token input remains MethodToken with dot-to-underscore normalization (e.g. eth.blockNumber). Help text updated accordingly. --- src/console/command.rs | 15 ++++++----- src/console/engine.rs | 22 ++-------------- src/console/exec.rs | 12 +++------ src/console/repl.rs | 60 ++++++++---------------------------------- src/console/run.rs | 27 +------------------ 5 files changed, 25 insertions(+), 111 deletions(-) diff --git a/src/console/command.rs b/src/console/command.rs index 48521a82..7062466d 100644 --- a/src/console/command.rs +++ b/src/console/command.rs @@ -9,7 +9,8 @@ pub enum InputCommand { Query(String), Rpc { method: String, params: Option }, RpcWithQuery { method: String, params: Option, query: String }, - Alias(String), + /// Single token treated as a method name (`eth.blockNumber` → `eth_blockNumber`). + MethodToken(String), } pub fn parse_input(line: &str) -> Result { @@ -34,7 +35,7 @@ pub fn parse_input(line: &str) -> Result { if looks_like_implicit_rpc(line) { return parse_rpc(line); } - Ok(InputCommand::Alias(line.to_owned())) + Ok(InputCommand::MethodToken(line.to_owned())) } fn split_rpc_query_tail(line: &str) -> Option<(&str, String)> { @@ -88,7 +89,7 @@ fn looks_like_implicit_rpc(line: &str) -> bool { // Accept direct method calls like: // - eth_getBalance ["0x...", "latest"] // - eth.getBalance ["0x...", "latest"] - // Keep simple alias calls like eth.blockNumber as aliases. + // Single-token `eth.blockNumber`-style input is handled as MethodToken. line.contains(char::is_whitespace) || (line.contains('(') && line.ends_with(')')) || (line.contains('_') && !line.contains(' ')) @@ -218,10 +219,10 @@ mod tests { } #[test] - fn keeps_dot_alias_as_alias_when_no_params() { + fn keeps_dot_method_token_when_no_params() { assert_eq!( parse_input("eth.blockNumber").unwrap(), - InputCommand::Alias("eth.blockNumber".to_owned()) + InputCommand::MethodToken("eth.blockNumber".to_owned()) ); } @@ -290,10 +291,10 @@ mod tests { } #[test] - fn no_chain_on_plain_alias() { + fn no_chain_on_plain_method_token() { assert_eq!( parse_input("admin.peers").unwrap(), - InputCommand::Alias("admin.peers".to_owned()) + InputCommand::MethodToken("admin.peers".to_owned()) ); } } diff --git a/src/console/engine.rs b/src/console/engine.rs index 7a92615b..472723c2 100644 --- a/src/console/engine.rs +++ b/src/console/engine.rs @@ -5,7 +5,6 @@ use super::{ }; use eyre::{Result, eyre}; use serde_json::Value; -use std::collections::BTreeMap; pub enum EvalOutcome { Noop, @@ -16,7 +15,6 @@ pub enum EvalOutcome { pub async fn evaluate_line( rpc: &RpcClient, - aliases: &BTreeMap, line: &str, last_rpc_result: &mut Option, ) -> Result { @@ -41,8 +39,8 @@ pub async fn evaluate_line( let result = apply_query(&query, &value)?; Ok(EvalOutcome::Value(result)) } - InputCommand::Alias(alias) => { - let method = resolve_alias_method(aliases, &alias); + InputCommand::MethodToken(token) => { + let method = normalize_rpc_method(&token); let value = rpc.request_value(&method, None).await?; *last_rpc_result = Some(value.clone()); Ok(EvalOutcome::Value(value)) @@ -60,10 +58,6 @@ fn normalize_rpc_method(method: &str) -> String { method.replace('.', "_") } -fn resolve_alias_method(aliases: &BTreeMap, alias: &str) -> String { - aliases.get(alias).cloned().unwrap_or_else(|| alias.replace('.', "_")) -} - #[cfg(test)] mod tests { use super::*; @@ -74,18 +68,6 @@ mod tests { assert_eq!(normalize_rpc_method("eth.getBalance"), "eth_getBalance"); } - #[test] - fn resolves_alias_from_map() { - let aliases = BTreeMap::from([("bn".to_owned(), "eth_blockNumber".to_owned())]); - assert_eq!(resolve_alias_method(&aliases, "bn"), "eth_blockNumber"); - } - - #[test] - fn resolves_alias_by_dot_fallback() { - let aliases = BTreeMap::new(); - assert_eq!(resolve_alias_method(&aliases, "net.peerCount"), "net_peerCount"); - } - #[test] fn query_without_last_result_errors() { let err = apply_query_to_last_rpc(".count", &None).unwrap_err(); diff --git a/src/console/exec.rs b/src/console/exec.rs index 7a64b45e..0c90959c 100644 --- a/src/console/exec.rs +++ b/src/console/exec.rs @@ -4,15 +4,10 @@ use super::{ }; use eyre::Result; use serde_json::Value; -use std::collections::BTreeMap; -pub async fn run_exec( - rpc: &RpcClient, - script: &str, - aliases: &BTreeMap, -) -> Result<()> { +pub async fn run_exec(rpc: &RpcClient, script: &str) -> Result<()> { let mut last = None; - match evaluate_line(rpc, aliases, script, &mut last).await? { + match evaluate_line(rpc, script, &mut last).await? { EvalOutcome::Value(value) => print_raw_json(&value), EvalOutcome::Help => print_help(), EvalOutcome::Noop | EvalOutcome::Exit => {} @@ -26,7 +21,6 @@ fn print_raw_json(value: &Value) { fn print_help() { println!("Usage:"); - println!(" [json_params] (RPC call)"); - println!(" (e.g. eth.blockNumber)"); + println!(" [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)"); println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); } diff --git a/src/console/repl.rs b/src/console/repl.rs index 3a0f1e9f..5cc52989 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -23,7 +23,6 @@ pub async fn run_repl( rpc: &RpcClient, history_path: PathBuf, endpoint: ResolvedEndpoint, - aliases: &BTreeMap, chain_id: Option, raw: bool, has_bera_admin: bool, @@ -34,7 +33,7 @@ pub async fn run_repl( )?; let modules = rpc.supported_modules().await.unwrap_or_default(); - let helper = CompletionHelper::new(aliases, &modules, has_bera_admin); + let helper = CompletionHelper::new(&modules, has_bera_admin); let mut editor: Editor = Editor::new()?; editor.set_completion_type(CompletionType::List); editor.set_helper(Some(helper)); @@ -54,10 +53,10 @@ pub async fn run_repl( if !line.trim().is_empty() { let _ = editor.add_history_entry(line.as_str()); } - match evaluate_line(rpc, aliases, &line, &mut last_rpc_result).await { + match evaluate_line(rpc, &line, &mut last_rpc_result).await { Ok(EvalOutcome::Noop) => {} Ok(EvalOutcome::Exit) => break, - Ok(EvalOutcome::Help) => print_help(aliases, has_bera_admin), + Ok(EvalOutcome::Help) => print_help(), Ok(EvalOutcome::Value(value)) => { print_value_for_chain_raw(&value, chain_id, raw); } @@ -143,27 +142,13 @@ fn as_string(value: &Value) -> Option { } } -fn print_help(aliases: &BTreeMap, has_bera_admin: bool) { +fn print_help() { println!("Commands:"); - println!(" [json_params] (RPC call)"); - println!(" (e.g. eth.blockNumber)"); - println!(" TAB completion for aliases/methods"); + println!(" [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)"); + println!(" TAB completion for RPC namespaces/methods"); println!(" help | exit"); println!("Queries (run against last RPC result):"); println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); - if has_bera_admin { - println!("beraAdmin (when detected):"); - println!(" peers detailed peer table"); - println!(" status node identity and sync state"); - println!(" ban \"0xpeerId\" ban peer (~12h)"); - println!(" penalize \"0xpeerId\" -100 penalize peer by value"); - } - if !aliases.is_empty() { - println!("Aliases:"); - for (alias, method) in aliases { - println!(" {alias} -> {method}"); - } - } } struct CompletionHelper { @@ -171,11 +156,7 @@ struct CompletionHelper { } impl CompletionHelper { - fn new( - aliases: &BTreeMap, - modules: &BTreeMap, - has_bera_admin: bool, - ) -> CompletionHelper { + fn new(modules: &BTreeMap, has_bera_admin: bool) -> CompletionHelper { let mut words = vec![ "help".to_owned(), "exit".to_owned(), @@ -186,13 +167,6 @@ impl CompletionHelper { ".last".to_owned(), ".map(".to_owned(), ]; - words.extend(aliases.keys().cloned()); - for method in aliases.values() { - words.push(method.clone()); - if let Some(dot) = rpc_method_to_dot(method) { - words.push(dot); - } - } for module in modules.keys() { words.push(format!("{module}.")); words.push(format!("{module}_")); @@ -209,14 +183,6 @@ impl CompletionHelper { } } -fn rpc_method_to_dot(method: &str) -> Option { - let (module, rest) = method.split_once('_')?; - if module.is_empty() || rest.is_empty() { - return None; - } - Some(format!("{module}.{rest}")) -} - impl Helper for CompletionHelper {} impl Validator for CompletionHelper {} impl Highlighter for CompletionHelper {} @@ -264,25 +230,22 @@ mod tests { #[test] fn completion_includes_bera_admin_when_enabled() { - let aliases = BTreeMap::new(); let modules = BTreeMap::new(); - let helper = CompletionHelper::new(&aliases, &modules, true); + let helper = CompletionHelper::new(&modules, true); assert!(helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); } #[test] fn completion_excludes_bera_admin_when_disabled() { - let aliases = BTreeMap::new(); let modules = BTreeMap::new(); - let helper = CompletionHelper::new(&aliases, &modules, false); + let helper = CompletionHelper::new(&modules, false); assert!(!helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); } #[test] fn completion_matches_prefix() { - let aliases = BTreeMap::from([("bn".to_owned(), "eth_blockNumber".to_owned())]); let modules = BTreeMap::from([("eth".to_owned(), "1.0".to_owned())]); - let helper = CompletionHelper::new(&aliases, &modules, false); + let helper = CompletionHelper::new(&modules, false); let history = DefaultHistory::new(); let ctx = Context::new(&history); let (_start, hits) = helper.complete("eth.getB", "eth.getB".len(), &ctx).unwrap(); @@ -291,9 +254,8 @@ mod tests { #[test] fn completion_includes_eth_namespace_methods() { - let aliases = BTreeMap::new(); let modules = BTreeMap::from([("eth".to_owned(), "1.0".to_owned())]); - let helper = CompletionHelper::new(&aliases, &modules, false); + let helper = CompletionHelper::new(&modules, false); assert!(helper.words.iter().any(|w| w == "eth.getLogs")); assert!(helper.words.iter().any(|w| w == "eth.getTransactionReceipt")); } diff --git a/src/console/run.rs b/src/console/run.rs index 7ad86a2e..0c16effc 100644 --- a/src/console/run.rs +++ b/src/console/run.rs @@ -7,7 +7,6 @@ use super::{ }; use eyre::Result; use serde_json::Value; -use std::collections::BTreeMap; pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { let endpoint = resolve_endpoint(cmd.endpoint.as_deref())?; @@ -19,26 +18,13 @@ pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { let bera_admin_status = rpc.request_value("beraAdmin_nodeStatus", None).await.ok(); let has_bera_admin = bera_admin_status.is_some(); - let mut rpc_aliases = default_aliases(); - if has_bera_admin { - for (alias, method) in [ - ("peers", "beraAdmin_detailedPeers"), - ("status", "beraAdmin_nodeStatus"), - ("ban", "beraAdmin_banPeer"), - ("penalize", "beraAdmin_penalizePeer"), - ] { - rpc_aliases.insert(alias.to_owned(), method.to_owned()); - } - } - if let Some(script) = cmd.exec.as_deref() { - run_exec(&rpc, script, &rpc_aliases).await?; + run_exec(&rpc, script).await?; } else { run_repl( &rpc, history_file_path(), endpoint, - &rpc_aliases, chain_id, cmd.raw, has_bera_admin, @@ -50,17 +36,6 @@ pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { Ok(()) } -fn default_aliases() -> BTreeMap { - [ - ("eth.blockNumber", "eth_blockNumber"), - ("net.version", "net_version"), - ("web3.clientVersion", "web3_clientVersion"), - ] - .into_iter() - .map(|(k, v)| (k.to_owned(), v.to_owned())) - .collect() -} - fn parse_chain_id(value: &Value) -> Option { match value { Value::String(s) => { From 732a4e502a038c3ec5823a441940df1af9985e81 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:11:47 -0400 Subject: [PATCH 11/21] feat(console): restore removeAllPeers batch admin.removePeer flow Reintroduce removeAllPeers / admin.removeAllPeers as explicit tokens: list admin_peers, remove each enode via admin_removePeer, summarize counts. Document in help; stderr logs per-peer failures. --- src/console/engine.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ src/console/repl.rs | 2 ++ 2 files changed, 44 insertions(+) diff --git a/src/console/engine.rs b/src/console/engine.rs index 472723c2..3156d830 100644 --- a/src/console/engine.rs +++ b/src/console/engine.rs @@ -40,6 +40,9 @@ pub async fn evaluate_line( Ok(EvalOutcome::Value(result)) } InputCommand::MethodToken(token) => { + if is_remove_all_peers_token(token.as_str()) { + return run_remove_all_peers(rpc, last_rpc_result).await; + } let method = normalize_rpc_method(&token); let value = rpc.request_value(&method, None).await?; *last_rpc_result = Some(value.clone()); @@ -48,6 +51,45 @@ pub async fn evaluate_line( } } +fn is_remove_all_peers_token(s: &str) -> bool { + matches!(s, "removeAllPeers" | "admin.removeAllPeers") +} + +async fn run_remove_all_peers( + rpc: &RpcClient, + last_rpc_result: &mut Option, +) -> Result { + let peers = rpc.request_value("admin_peers", None).await?; + let arr = peers.as_array().ok_or_else(|| eyre!("admin.peers did not return an array"))?; + let total = arr.len() as u64; + let mut removed = 0u64; + let mut failed = 0u64; + for peer in arr { + let enode = peer + .get("enode") + .and_then(Value::as_str) + .ok_or_else(|| eyre!("peer entry missing enode"))?; + match rpc.request_value("admin_removePeer", Some(serde_json::json!([enode]))).await { + Ok(Value::Bool(true)) => removed += 1, + Ok(other) => { + eprintln!("admin.removePeer({enode}): unexpected response: {other}"); + failed += 1; + } + Err(err) => { + eprintln!("admin.removePeer({enode}): {err}"); + failed += 1; + } + } + } + *last_rpc_result = Some(peers.clone()); + eprintln!("Removed {removed}/{total} peer(s)."); + Ok(EvalOutcome::Value(serde_json::json!({ + "removed": removed, + "failed": failed, + "total": total + }))) +} + fn apply_query_to_last_rpc(expr: &str, last_rpc_result: &Option) -> Result { let value = last_rpc_result.as_ref().ok_or_else(|| eyre!("no last rpc result available for query"))?; diff --git a/src/console/repl.rs b/src/console/repl.rs index 5cc52989..bc4fd77b 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -149,6 +149,8 @@ fn print_help() { println!(" help | exit"); println!("Queries (run against last RPC result):"); println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); + println!("Destructive (calls admin.removePeer for each connected peer):"); + println!(" removeAllPeers | admin.removeAllPeers"); } struct CompletionHelper { From 9b1363e5496667e6e4c429bef6c9d5750ec1a2f8 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:12:32 -0400 Subject: [PATCH 12/21] docs(console): mention removeAllPeers in --exec help --- src/console/exec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/console/exec.rs b/src/console/exec.rs index 0c90959c..2e97386f 100644 --- a/src/console/exec.rs +++ b/src/console/exec.rs @@ -23,4 +23,5 @@ fn print_help() { println!("Usage:"); println!(" [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)"); println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); + println!("Destructive: removeAllPeers | admin.removeAllPeers (batch admin.removePeer)"); } From 36071472a27314bbb0e3c55d17731c9a0e9c9ef1 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:27:55 -0400 Subject: [PATCH 13/21] fix(console): Unicode-safe peer/reason truncation in output formatting Align output.rs with feat/console: chars_prefix and ellipsis_char_ends for direction, client, peer id, genesis/head hash, and ban reason strings. --- src/console/output.rs | 69 +++++++++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/src/console/output.rs b/src/console/output.rs index 1b1d6036..ce20d294 100644 --- a/src/console/output.rs +++ b/src/console/output.rs @@ -24,6 +24,33 @@ pub(crate) fn hex_or_decimal_to_u64(value: &Value) -> Option { } } +/// First `max_chars` Unicode scalars (never splits a codepoint). +fn chars_prefix(s: &str, max_chars: usize) -> &str { + if max_chars == 0 { + return ""; + } + let mut end_byte = 0usize; + let mut iter = s.char_indices(); + for _ in 0..max_chars { + match iter.next() { + Some((i, ch)) => end_byte = i + ch.len_utf8(), + None => return s, + } + } + &s[..end_byte] +} + +/// `prefix..suffix` with lengths in characters; returns `s` unchanged if it fits. +fn ellipsis_char_ends(s: &str, prefix_chars: usize, suffix_chars: usize) -> String { + let n = s.chars().count(); + if n <= prefix_chars + suffix_chars { + return s.to_string(); + } + let prefix: String = s.chars().take(prefix_chars).collect(); + let suffix: String = s.chars().skip(n - suffix_chars).collect(); + format!("{prefix}..{suffix}") +} + pub fn print_value_for_chain(value: &Value, chain_id: Option) { print_value_with_symbol(value, native_symbol_for_chain_id(chain_id), false); } @@ -196,7 +223,7 @@ fn try_format_detailed_peers(value: &Value) -> Option { let direction = peer_obj .get("direction") .and_then(|v| v.as_str()) - .map(|s| &s[..s.len().min(3)]) + .map(|s| chars_prefix(s, 3)) .unwrap_or("-"); let reputation = peer_obj .get("reputation") @@ -214,8 +241,8 @@ fn try_format_detailed_peers(value: &Value) -> Option { .or_else(|| peer_obj.get("client_version")) .and_then(|v| v.as_str()) .unwrap_or("-"); - let client_short = if client.len() > 14 { - format!("{}..{}", &client[..8], &client[client.len() - 3..]) + let client_short = if client.chars().count() > 14 { + ellipsis_char_ends(client, 8, 3) } else { client.to_string() }; @@ -242,8 +269,8 @@ fn try_format_detailed_peers(value: &Value) -> Option { "-".to_string() }; - let peer_short = if peer_id.len() > 12 { - format!("{}..{}", &peer_id[..6], &peer_id[peer_id.len() - 4..]) + let peer_short = if peer_id.chars().count() > 12 { + ellipsis_char_ends(peer_id, 6, 4) } else { peer_id.to_string() }; @@ -284,8 +311,8 @@ fn try_format_node_status(value: &Value) -> Option { .or_else(|| obj.get("genesis_hash")) .and_then(|v| v.as_str()) .unwrap_or("unknown"); - let genesis_short = if genesis.len() > 12 { - format!("{}..{}", &genesis[..6], &genesis[genesis.len() - 4..]) + let genesis_short = if genesis.chars().count() > 12 { + ellipsis_char_ends(genesis, 6, 4) } else { genesis.to_string() }; @@ -300,8 +327,8 @@ fn try_format_node_status(value: &Value) -> Option { .or_else(|| obj.get("head")) .and_then(|v| v.as_str()) .unwrap_or("unknown"); - let head_hash_short = if head_hash.len() > 12 { - format!("{}..{}", &head_hash[..6], &head_hash[head_hash.len() - 4..]) + let head_hash_short = if head_hash.chars().count() > 12 { + ellipsis_char_ends(head_hash, 6, 4) } else { head_hash.to_string() }; @@ -392,13 +419,13 @@ fn try_format_peer_scores(value: &Value) -> Option { .map(|p| p.len().to_string()) .unwrap_or_else(|| "0".to_string()); - let peer_short = if peer_id.len() > 12 { - format!("{}..{}", &peer_id[..6], &peer_id[peer_id.len() - 4..]) + let peer_short = if peer_id.chars().count() > 12 { + ellipsis_char_ends(peer_id, 6, 4) } else { peer_id.to_string() }; - let reason_short = if reason.len() > 14 { - format!("{}..{}", &reason[..8], &reason[reason.len() - 3..]) + let reason_short = if reason.chars().count() > 14 { + ellipsis_char_ends(reason, 8, 3) } else { reason.to_string() }; @@ -456,8 +483,8 @@ fn try_format_banned_subnets(value: &Value) -> Option { .map(|n| n.len().to_string()) .unwrap_or_else(|| "?".to_string()); - let reason_short = if reason.len() > 14 { - format!("{}..{}", &reason[..8], &reason[reason.len() - 3..]) + let reason_short = if reason.chars().count() > 14 { + ellipsis_char_ends(reason, 8, 3) } else { reason.to_string() }; @@ -574,4 +601,16 @@ mod tests { let formatted = try_format_banned_subnets(&empty_subnets); assert_eq!(formatted, Some("-- no subnets banned --".to_string())); } + + #[test] + fn chars_prefix_never_splits_codepoints() { + assert_eq!(chars_prefix("μμμbound", 3), "μμμ"); + assert_eq!(chars_prefix("μμμbound", 0), ""); + } + + #[test] + fn ellipsis_char_ends_counts_chars_not_bytes() { + let out = ellipsis_char_ends("abcdefghijklmnopqrs", 6, 4); + assert_eq!(out, "abcdef..pqrs"); + } } From 3af49a4b1f47ee4df7ca01c9ddf1f65850b9c4cb Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:34:52 -0400 Subject: [PATCH 14/21] refactor(console): replace rustyline with reedline Use Reedline with FileBackedHistory, ColumnarMenu tab completion, and a custom bera> prompt. Map Ctrl+C to continue and Ctrl+D to exit like the previous readline behavior. --- Cargo.lock | 424 +++++++++++++++++----------------- Cargo.toml | 95 ++++---- src/console/repl.rs | 171 +++++++++----- src/console/rpc_completion.rs | 2 +- 4 files changed, 379 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6c8be4b2..dea280fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1464,6 +1464,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "alloy-serde", + "alloy-signer", "alloy-signer-local", "alloy-sol-macro", "alloy-sol-types", @@ -1473,11 +1474,14 @@ dependencies = [ "derive_more", "dirs", "eyre", + "futures", "jsonrpsee", "jsonrpsee-core", "jsonrpsee-proc-macros", "libc", "modular-bitfield", + "rand 0.8.5", + "reedline", "reth", "reth-basic-payload-builder", "reth-chainspec", @@ -1493,13 +1497,18 @@ dependencies = [ "reth-engine-local", "reth-engine-primitives", "reth-errors", + "reth-eth-wire-types", "reth-ethereum-cli", "reth-ethereum-engine-primitives", "reth-ethereum-payload-builder", "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", + "reth-metrics", + "reth-network", + "reth-network-api", "reth-network-peers", + "reth-network-types", "reth-node-api", "reth-node-builder", "reth-node-core", @@ -1518,7 +1527,7 @@ dependencies = [ "reth-transaction-pool", "reth-trie-common", "revm-inspectors", - "rustyline", + "rusqlite", "serde", "serde_json", "sha2", @@ -1527,6 +1536,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tracing", + "tracing-subscriber 0.3.22", "vergen", "vergen-git2", ] @@ -2129,15 +2139,6 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - [[package]] name = "coins-bip32" version = "0.12.0" @@ -2439,6 +2440,7 @@ dependencies = [ "mio", "parking_lot", "rustix", + "serde", "signal-hook", "signal-hook-mio", "winapi", @@ -3030,12 +3032,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "endian-type" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" - [[package]] name = "enr" version = "0.13.0" @@ -3124,12 +3120,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - [[package]] name = "ethereum_hashing" version = "0.7.0" @@ -3218,6 +3208,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fast-float2" version = "0.2.3" @@ -3435,9 +3437,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -3450,9 +3452,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -3473,15 +3475,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -3490,9 +3492,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -3509,9 +3511,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -3520,15 +3522,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" @@ -3542,9 +3544,9 @@ dependencies = [ [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -3554,7 +3556,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -3883,15 +3884,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "http" version = "1.4.0" @@ -4825,6 +4817,17 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.23" @@ -5222,27 +5225,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nibble_vec" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" -dependencies = [ - "smallvec", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -6288,16 +6270,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - [[package]] name = "rand" version = "0.8.5" @@ -6526,6 +6498,26 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "reedline" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2066729dce9fecd28d1c6850a159ee68719130f149b22467c362353e16994e90" +dependencies = [ + "chrono", + "crossterm", + "fd-lock", + "itertools 0.13.0", + "nu-ansi-term", + "serde", + "strip-ansi-escapes", + "strum", + "thiserror 2.0.18", + "unicase", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -6640,7 +6632,7 @@ checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "reth" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -6680,7 +6672,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6704,7 +6696,7 @@ dependencies = [ [[package]] name = "reth-chain-state" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6736,7 +6728,7 @@ dependencies = [ [[package]] name = "reth-chainspec" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -6756,7 +6748,7 @@ dependencies = [ [[package]] name = "reth-cli" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-genesis", "clap", @@ -6770,7 +6762,7 @@ dependencies = [ [[package]] name = "reth-cli-commands" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -6850,7 +6842,7 @@ dependencies = [ [[package]] name = "reth-cli-runner" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "reth-tasks", "tokio", @@ -6860,7 +6852,7 @@ dependencies = [ [[package]] name = "reth-cli-util" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -6878,7 +6870,7 @@ dependencies = [ [[package]] name = "reth-codecs" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6898,7 +6890,7 @@ dependencies = [ [[package]] name = "reth-codecs-derive" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "proc-macro2", "quote", @@ -6908,7 +6900,7 @@ dependencies = [ [[package]] name = "reth-config" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "eyre", "humantime-serde", @@ -6924,7 +6916,7 @@ dependencies = [ [[package]] name = "reth-consensus" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -6937,7 +6929,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6949,7 +6941,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6975,7 +6967,7 @@ dependencies = [ [[package]] name = "reth-db" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "derive_more", @@ -7002,7 +6994,7 @@ dependencies = [ [[package]] name = "reth-db-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7031,7 +7023,7 @@ dependencies = [ [[package]] name = "reth-db-common" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7061,7 +7053,7 @@ dependencies = [ [[package]] name = "reth-db-models" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7076,7 +7068,7 @@ dependencies = [ [[package]] name = "reth-discv4" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7101,7 +7093,7 @@ dependencies = [ [[package]] name = "reth-discv5" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7125,7 +7117,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "dashmap", @@ -7149,7 +7141,7 @@ dependencies = [ [[package]] name = "reth-downloaders" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7184,7 +7176,7 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7242,7 +7234,7 @@ dependencies = [ [[package]] name = "reth-ecies" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "aes", "alloy-primitives", @@ -7270,7 +7262,7 @@ dependencies = [ [[package]] name = "reth-engine-local" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7293,7 +7285,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7318,7 +7310,7 @@ dependencies = [ [[package]] name = "reth-engine-service" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "futures", "pin-project", @@ -7340,7 +7332,7 @@ dependencies = [ [[package]] name = "reth-engine-tree" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eip7928", @@ -7397,7 +7389,7 @@ dependencies = [ [[package]] name = "reth-engine-util" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -7425,7 +7417,7 @@ dependencies = [ [[package]] name = "reth-era" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7440,7 +7432,7 @@ dependencies = [ [[package]] name = "reth-era-downloader" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "bytes", @@ -7456,7 +7448,7 @@ dependencies = [ [[package]] name = "reth-era-utils" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7478,7 +7470,7 @@ dependencies = [ [[package]] name = "reth-errors" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "reth-consensus", "reth-execution-errors", @@ -7489,7 +7481,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7517,7 +7509,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7538,7 +7530,7 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "clap", "eyre", @@ -7561,7 +7553,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7577,7 +7569,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7595,7 +7587,7 @@ dependencies = [ [[package]] name = "reth-ethereum-forks" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -7608,7 +7600,7 @@ dependencies = [ [[package]] name = "reth-ethereum-payload-builder" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7637,7 +7629,7 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7657,7 +7649,7 @@ dependencies = [ [[package]] name = "reth-etl" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "rayon", "reth-db-api", @@ -7667,7 +7659,7 @@ dependencies = [ [[package]] name = "reth-evm" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7691,7 +7683,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7713,7 +7705,7 @@ dependencies = [ [[package]] name = "reth-execution-errors" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-evm", "alloy-primitives", @@ -7726,7 +7718,7 @@ dependencies = [ [[package]] name = "reth-execution-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7744,7 +7736,7 @@ dependencies = [ [[package]] name = "reth-exex" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7782,7 +7774,7 @@ dependencies = [ [[package]] name = "reth-exex-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7796,7 +7788,7 @@ dependencies = [ [[package]] name = "reth-fs-util" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "serde", "serde_json", @@ -7806,7 +7798,7 @@ dependencies = [ [[package]] name = "reth-invalid-block-hooks" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7834,7 +7826,7 @@ dependencies = [ [[package]] name = "reth-ipc" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "bytes", "futures", @@ -7854,7 +7846,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "bitflags 2.10.0", "byteorder", @@ -7870,7 +7862,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "bindgen", "cc", @@ -7879,7 +7871,7 @@ dependencies = [ [[package]] name = "reth-metrics" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "futures", "metrics", @@ -7891,7 +7883,7 @@ dependencies = [ [[package]] name = "reth-net-banlist" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "ipnet", @@ -7900,7 +7892,7 @@ dependencies = [ [[package]] name = "reth-net-nat" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "futures-util", "if-addrs", @@ -7914,7 +7906,7 @@ dependencies = [ [[package]] name = "reth-network" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7971,7 +7963,7 @@ dependencies = [ [[package]] name = "reth-network-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7996,7 +7988,7 @@ dependencies = [ [[package]] name = "reth-network-p2p" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8019,7 +8011,7 @@ dependencies = [ [[package]] name = "reth-network-peers" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8034,7 +8026,7 @@ dependencies = [ [[package]] name = "reth-network-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8048,7 +8040,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "anyhow", "bincode 1.3.3", @@ -8065,7 +8057,7 @@ dependencies = [ [[package]] name = "reth-node-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8089,7 +8081,7 @@ dependencies = [ [[package]] name = "reth-node-builder" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8158,7 +8150,7 @@ dependencies = [ [[package]] name = "reth-node-core" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8213,7 +8205,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-network", @@ -8251,7 +8243,7 @@ dependencies = [ [[package]] name = "reth-node-ethstats" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8275,7 +8267,7 @@ dependencies = [ [[package]] name = "reth-node-events" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8299,7 +8291,7 @@ dependencies = [ [[package]] name = "reth-node-metrics" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "bytes", "eyre", @@ -8323,7 +8315,7 @@ dependencies = [ [[package]] name = "reth-node-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "reth-chainspec", "reth-db-api", @@ -8335,7 +8327,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8356,7 +8348,7 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "pin-project", "reth-payload-primitives", @@ -8368,7 +8360,7 @@ dependencies = [ [[package]] name = "reth-payload-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8391,7 +8383,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8401,7 +8393,7 @@ dependencies = [ [[package]] name = "reth-primitives" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8415,7 +8407,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8449,7 +8441,7 @@ dependencies = [ [[package]] name = "reth-provider" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8494,7 +8486,7 @@ dependencies = [ [[package]] name = "reth-prune" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8523,7 +8515,7 @@ dependencies = [ [[package]] name = "reth-prune-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "arbitrary", @@ -8539,7 +8531,7 @@ dependencies = [ [[package]] name = "reth-revm" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "reth-primitives-traits", @@ -8552,7 +8544,7 @@ dependencies = [ [[package]] name = "reth-rpc" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -8629,7 +8621,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eip7928", "alloy-eips", @@ -8659,7 +8651,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-network", "alloy-provider", @@ -8700,7 +8692,7 @@ dependencies = [ [[package]] name = "reth-rpc-convert" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-evm", @@ -8721,7 +8713,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8751,7 +8743,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -8795,7 +8787,7 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8843,7 +8835,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-rpc-types-engine", "http", @@ -8857,7 +8849,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8873,7 +8865,7 @@ dependencies = [ [[package]] name = "reth-stages" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8923,7 +8915,7 @@ dependencies = [ [[package]] name = "reth-stages-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8950,7 +8942,7 @@ dependencies = [ [[package]] name = "reth-stages-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "arbitrary", @@ -8964,7 +8956,7 @@ dependencies = [ [[package]] name = "reth-static-file" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "parking_lot", @@ -8984,7 +8976,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "clap", @@ -8999,7 +8991,7 @@ dependencies = [ [[package]] name = "reth-storage-api" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9023,7 +9015,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9040,7 +9032,7 @@ dependencies = [ [[package]] name = "reth-tasks" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "auto_impl", "dyn-clone", @@ -9058,7 +9050,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9074,7 +9066,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "tokio", "tokio-stream", @@ -9084,7 +9076,7 @@ dependencies = [ [[package]] name = "reth-tracing" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "clap", "eyre", @@ -9101,7 +9093,7 @@ dependencies = [ [[package]] name = "reth-tracing-otlp" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "clap", "eyre", @@ -9119,7 +9111,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9163,7 +9155,7 @@ dependencies = [ [[package]] name = "reth-trie" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9189,7 +9181,7 @@ dependencies = [ [[package]] name = "reth-trie-common" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9216,7 +9208,7 @@ dependencies = [ [[package]] name = "reth-trie-db" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "metrics", @@ -9236,7 +9228,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9261,7 +9253,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -9280,7 +9272,7 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" version = "1.11.3" -source = "git+https://github.com/paradigmxyz/reth?tag=v1.11.3#d6324d63e27ef6b7c49cdc9b1977c1b808234c7b" +source = "git+https://github.com/camembera/reth?branch=pog%2Fprovenance-callback#ab0c215ee7276dab1f9cc944258b530a7cef466a" dependencies = [ "zstd", ] @@ -9643,6 +9635,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.10.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-hash" version = "2.1.1" @@ -9791,28 +9797,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "rustyline" -version = "17.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e902948a25149d50edc1a8e0141aad50f54e22ba83ff988cf8f7c9ef07f50564" -dependencies = [ - "bitflags 2.10.0", - "cfg-if", - "clipboard-win", - "fd-lock", - "home", - "libc", - "log", - "memchr", - "nix", - "radix_trie", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "windows-sys 0.60.2", -] - [[package]] name = "ryu" version = "1.0.22" @@ -10372,6 +10356,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -11453,6 +11446,15 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + [[package]] name = "wait-timeout" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index f84870b6..f2c343b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ alloy-rlp = "0.3.13" alloy-rpc-types = "1.6.3" alloy-rpc-types-eth = "1.6.3" alloy-serde = "1.6.3" +alloy-signer = "1.6.3" alloy-signer-local = "1.6.3" alloy-sol-macro = "1.5.6" alloy-sol-types = "1.5.6" @@ -24,7 +25,7 @@ derive_more = "2.0.1" dirs = "6.0.0" eyre = "0.6.12" libc = "0.2.177" -rustyline = "17.0.2" +reedline = "0.47" sha2 = "0.10" # rpc @@ -34,47 +35,56 @@ jsonrpsee-proc-macros = "0.26.0" async-trait = "0.1.88" modular-bitfield = "0.13.1" -reth = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-basic-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-chainspec = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-commands = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-runner = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-cli-util = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-codecs = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-consensus-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-db = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-db-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-engine-local = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-errors = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-cli = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-engine-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-payload-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-ethereum-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-evm-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-network-peers = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-core = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-node-ethereum = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-primitives = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-payload-validator = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-primitives-traits = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-convert = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-engine-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-eth-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-eth-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-server-types = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-storage-api = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-transaction-pool = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } +rand = "0.8" +reth = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-basic-payload-builder = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-chainspec = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-cli = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-cli-commands = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-cli-runner = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-cli-util = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-codecs = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-consensus-common = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-db = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-db-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-engine-local = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-engine-primitives = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-errors = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-eth-wire-types = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-ethereum-cli = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-ethereum-engine-primitives = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-ethereum-payload-builder = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-ethereum-primitives = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-evm = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-evm-ethereum = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-metrics = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-network = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-network-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-network-peers = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-network-types = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-node-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-node-builder = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-node-core = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-node-ethereum = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-payload-primitives = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-payload-validator = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-primitives-traits = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-convert = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-engine-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-eth-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-eth-types = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-server-types = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-storage-api = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-transaction-pool = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +rusqlite = { version = "0.32", features = ["bundled"] } serde = { version = "1.0", features = ["derive"], default-features = false } serde_json = "1.0" thiserror = "2.0" tokio = { version = "1.46.0", features = ["macros", "rt-multi-thread", "net", "io-util"] } tracing = "0.1.41" +tracing-subscriber = { version = "0.3", default-features = false, features = ["registry"] } +futures = "0.3.32" [dev-dependencies] alloy-provider = "1.6.3" @@ -82,11 +92,12 @@ alloy-rpc-client = "1.6.3" alloy-rpc-types-trace = "1.6.3" eyre = "0.6.12" jsonrpsee = { version = "0.26.0", features = ["server"] } -reth-e2e-test-utils = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-rpc-builder = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } -reth-trie-common = { git = "https://github.com/paradigmxyz/reth", tag = "v1.11.3" } +reth-e2e-test-utils = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-rpc-builder = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } +reth-trie-common = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } revm-inspectors = "0.34.2" -tempfile = "3.15.0" +serde_json = "1.0" +tempfile = "3" test-fuzz = "7" [build-dependencies] @@ -117,7 +128,7 @@ opt-level = 3 lto = "thin" [package.metadata.cargo-machete] -ignored = ["modular-bitfield"] +ignored = ["modular-bitfield", "reth-cli-commands"] # [patch."https://github.com/paradigmxyz/reth"] # reth = { path = "../reth/bin/reth" } diff --git a/src/console/repl.rs b/src/console/repl.rs index bc4fd77b..d1d82bea 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -5,18 +5,60 @@ use super::{ rpc::RpcClient, }; use eyre::Result; -use rustyline::{ - CompletionType, Context, Editor, Helper, - completion::{Completer, Pair}, - config::Configurer, - error::ReadlineError, - highlight::Highlighter, - hint::Hinter, - history::DefaultHistory, - validate::Validator, +use reedline::{ + ColumnarMenu, Completer, Emacs, FileBackedHistory, KeyCode, KeyModifiers, MenuBuilder, Prompt, + PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, Reedline, ReedlineEvent, + ReedlineMenu, Signal, Span, Suggestion, default_emacs_keybindings, HISTORY_SIZE, }; use serde_json::Value; -use std::{collections::BTreeMap, path::PathBuf}; +use std::{borrow::Cow, collections::BTreeMap, path::PathBuf}; + +const COMPLETION_MENU_NAME: &str = "completion_menu"; + +fn completion_keybindings() -> reedline::Keybindings { + let mut kb = default_emacs_keybindings(); + kb.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu(COMPLETION_MENU_NAME.to_string()), + ReedlineEvent::MenuNext, + ]), + ); + kb +} + +/// Single-line prompt matching the previous `rustyline` `readline("bera> ")` UX. +struct BeraPrompt; + +impl Prompt for BeraPrompt { + fn render_prompt_left(&self) -> Cow<'_, str> { + Cow::Borrowed("bera> ") + } + + fn render_prompt_right(&self) -> Cow<'_, str> { + Cow::Borrowed("") + } + + fn render_prompt_indicator(&self, _prompt_mode: PromptEditMode) -> Cow<'_, str> { + Cow::Borrowed("") + } + + fn render_prompt_multiline_indicator(&self) -> Cow<'_, str> { + Cow::Borrowed(".. ") + } + + fn render_prompt_history_search_indicator( + &self, + history_search: PromptHistorySearch, + ) -> Cow<'_, str> { + let prefix = match history_search.status { + PromptHistorySearchStatus::Passing => "", + PromptHistorySearchStatus::Failing => "failing ", + }; + Cow::Owned(format!("({}reverse-search: {}) ", prefix, history_search.term)) + } +} #[allow(clippy::too_many_arguments)] pub async fn run_repl( @@ -34,12 +76,21 @@ pub async fn run_repl( let modules = rpc.supported_modules().await.unwrap_or_default(); let helper = CompletionHelper::new(&modules, has_bera_admin); - let mut editor: Editor = Editor::new()?; - editor.set_completion_type(CompletionType::List); - editor.set_helper(Some(helper)); - if history_path.exists() { - let _ = editor.load_history(&history_path); - } + + let history = FileBackedHistory::with_file(HISTORY_SIZE, history_path.clone()) + .map_err(|e| eyre::eyre!("failed to load console history: {e}"))?; + + let completion_menu = Box::new(ColumnarMenu::default().with_name(COMPLETION_MENU_NAME)); + let edit_mode = Box::new(Emacs::new(completion_keybindings())); + + let mut editor = Reedline::create() + .with_history(Box::new(history)) + .with_completer(Box::new(helper)) + .with_menu(ReedlineMenu::EngineCompleter(completion_menu)) + .with_edit_mode(edit_mode) + .with_quick_completions(true); + + let prompt = BeraPrompt; println!("bera-reth console :: {}", endpoint.raw); print_startup_snapshot(rpc, chain_id, bera_admin_status.as_ref()).await; @@ -47,12 +98,8 @@ pub async fn run_repl( let mut last_rpc_result = None; loop { - let line = editor.readline("bera> "); - match line { - Ok(line) => { - if !line.trim().is_empty() { - let _ = editor.add_history_entry(line.as_str()); - } + match editor.read_line(&prompt) { + Ok(Signal::Success(line)) => { match evaluate_line(rpc, &line, &mut last_rpc_result).await { Ok(EvalOutcome::Noop) => {} Ok(EvalOutcome::Exit) => break, @@ -63,13 +110,13 @@ pub async fn run_repl( Err(err) => eprintln!("error: {err}"), } } - Err(ReadlineError::Interrupted) => {} - Err(ReadlineError::Eof) => break, + Ok(Signal::CtrlC) => {} + Ok(Signal::CtrlD) => break, + Ok(_) => {} Err(err) => return Err(err.into()), } } - let _ = editor.save_history(&history_path); Ok(()) } @@ -79,12 +126,31 @@ async fn print_startup_snapshot( bera_admin_status: Option<&Value>, ) { if let Some(status) = bera_admin_status { - let client_version = status.get("client").and_then(as_string); - let network_id = status.get("networkId").and_then(as_string); - let head_number = status.get("head").and_then(as_string); - let peer_count_total = status.get("peerCountTotal").and_then(hex_or_decimal_to_u64); - let peer_count_inbound = status.get("peerCountInbound").and_then(hex_or_decimal_to_u64); - let peer_count_outbound = status.get("peerCountOutbound").and_then(hex_or_decimal_to_u64); + let client_version = status + .get("clientVersion") + .or_else(|| status.get("client_version")) + .and_then(as_string); + let network_id = status + .get("networkId") + .or_else(|| status.get("network_id")) + .and_then(as_string); + let head_number = status + .get("headNumber") + .or_else(|| status.get("head_number")) + .and_then(|v| hex_or_decimal_to_u64(v).map(|n| n.to_string())) + .or_else(|| status.get("head").and_then(as_string)); + let peer_count_total = status + .get("peerCountTotal") + .or_else(|| status.get("peer_count_total")) + .and_then(hex_or_decimal_to_u64); + let peer_count_inbound = status + .get("peerCountInbound") + .or_else(|| status.get("peer_count_inbound")) + .and_then(hex_or_decimal_to_u64); + let peer_count_outbound = status + .get("peerCountOutbound") + .or_else(|| status.get("peer_count_outbound")) + .and_then(hex_or_decimal_to_u64); let peers_str = if let (Some(in_count), Some(out_count)) = (peer_count_inbound, peer_count_outbound) @@ -95,9 +161,10 @@ async fn print_startup_snapshot( }; println!( - "node :: {} | net={} 🐻⭐ | block={} | {}", + "node :: {} | net={}{} | block={} | {}", client_version.unwrap_or_else(|| "unavailable".to_owned()), network_id.unwrap_or_else(|| "unavailable".to_owned()), + chain_emoji(chain_id), head_number.unwrap_or_else(|| "unavailable".to_owned()), peers_str ); @@ -185,22 +252,8 @@ impl CompletionHelper { } } -impl Helper for CompletionHelper {} -impl Validator for CompletionHelper {} -impl Highlighter for CompletionHelper {} -impl Hinter for CompletionHelper { - type Hint = String; -} - impl Completer for CompletionHelper { - type Candidate = Pair; - - fn complete( - &self, - line: &str, - pos: usize, - _ctx: &Context<'_>, - ) -> rustyline::Result<(usize, Vec)> { + fn complete(&mut self, line: &str, pos: usize) -> Vec { let safe_pos = pos.min(line.len()); let up_to_cursor = &line[..safe_pos]; let start = up_to_cursor @@ -210,13 +263,16 @@ impl Completer for CompletionHelper { .map(|(i, c)| i + c.len_utf8()) .unwrap_or(0); let needle = &up_to_cursor[start..]; - let matches = self - .words + self.words .iter() .filter(|word| word.starts_with(needle)) - .map(|word| Pair { display: word.clone(), replacement: word.clone() }) - .collect(); - Ok((start, matches)) + .map(|word| Suggestion { + value: word.clone(), + span: Span::new(start, safe_pos), + append_whitespace: false, + ..Default::default() + }) + .collect() } } @@ -227,8 +283,6 @@ pub fn history_file_path() -> PathBuf { #[cfg(test)] mod tests { use super::*; - use rustyline::{completion::Completer, history::DefaultHistory}; - use serde_json::json; #[test] fn completion_includes_bera_admin_when_enabled() { @@ -247,11 +301,9 @@ mod tests { #[test] fn completion_matches_prefix() { let modules = BTreeMap::from([("eth".to_owned(), "1.0".to_owned())]); - let helper = CompletionHelper::new(&modules, false); - let history = DefaultHistory::new(); - let ctx = Context::new(&history); - let (_start, hits) = helper.complete("eth.getB", "eth.getB".len(), &ctx).unwrap(); - assert!(hits.iter().any(|p| p.replacement == "eth.getBalance")); + let mut helper = CompletionHelper::new(&modules, false); + let hits = helper.complete("eth.getB", "eth.getB".len()); + assert!(hits.iter().any(|s| s.value == "eth.getBalance")); } #[test] @@ -264,6 +316,7 @@ mod tests { #[test] fn parses_hex_or_decimal_numbers() { + use serde_json::json; assert_eq!(hex_or_decimal_to_u64(&json!("0x10")), Some(16)); } } diff --git a/src/console/rpc_completion.rs b/src/console/rpc_completion.rs index 3e688a8f..f4628c63 100644 --- a/src/console/rpc_completion.rs +++ b/src/console/rpc_completion.rs @@ -303,7 +303,7 @@ pub fn method_suffixes(namespace: &str) -> &[&str] { &[] } -/// `namespace.method` strings for rustyline completion. +/// `namespace.method` strings for reedline tab completion. pub fn dot_completions_for_namespace(namespace: &str) -> Vec { method_suffixes(namespace).iter().map(|suffix| format!("{namespace}.{suffix}")).collect() } From 7681683cfd3d4744526c24c3ed511e7837df2d85 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:49:22 -0400 Subject: [PATCH 15/21] chore(ci): fmt, dprint, rustdoc, and machete allowlist - Apply nightly rustfmt and dprint for TOML - Fix rpc_completion module docs (no broken intra-doc links) - Expand cargo-machete ignored deps for macro/test/build usage --- Cargo.toml | 23 +++++++++++++++++++++-- src/console/command.rs | 11 +++++++++-- src/console/exec.rs | 4 +++- src/console/repl.rs | 16 ++++++++-------- src/console/rpc.rs | 5 ++--- src/console/rpc_completion.rs | 4 ++-- 6 files changed, 45 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f2c343b0..63e11bfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ jsonrpsee-core = { version = "0.26.0", features = ["server"] } jsonrpsee-proc-macros = "0.26.0" async-trait = "0.1.88" +futures = "0.3.32" modular-bitfield = "0.13.1" rand = "0.8" reth = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } @@ -84,7 +85,6 @@ thiserror = "2.0" tokio = { version = "1.46.0", features = ["macros", "rt-multi-thread", "net", "io-util"] } tracing = "0.1.41" tracing-subscriber = { version = "0.3", default-features = false, features = ["registry"] } -futures = "0.3.32" [dev-dependencies] alloy-provider = "1.6.3" @@ -128,7 +128,26 @@ opt-level = 3 lto = "thin" [package.metadata.cargo-machete] -ignored = ["modular-bitfield", "reth-cli-commands"] +# cargo-machete misses macro/build.rs/test-only uses; keep explicit allowlist in sync when deps change. +ignored = [ + "alloy-provider", + "alloy-rpc-client", + "alloy-signer", + "futures", + "modular-bitfield", + "rand", + "reth-eth-wire-types", + "reth-metrics", + "reth-network", + "reth-network-api", + "reth-network-types", + "reth-rpc-builder", + "rusqlite", + "test-fuzz", + "tracing-subscriber", + "vergen", + "vergen-git2", +] # [patch."https://github.com/paradigmxyz/reth"] # reth = { path = "../reth/bin/reth" } diff --git a/src/console/command.rs b/src/console/command.rs index 7062466d..a3353349 100644 --- a/src/console/command.rs +++ b/src/console/command.rs @@ -7,8 +7,15 @@ pub enum InputCommand { Help, Exit, Query(String), - Rpc { method: String, params: Option }, - RpcWithQuery { method: String, params: Option, query: String }, + Rpc { + method: String, + params: Option, + }, + RpcWithQuery { + method: String, + params: Option, + query: String, + }, /// Single token treated as a method name (`eth.blockNumber` → `eth_blockNumber`). MethodToken(String), } diff --git a/src/console/exec.rs b/src/console/exec.rs index 2e97386f..fb7ab3fe 100644 --- a/src/console/exec.rs +++ b/src/console/exec.rs @@ -21,7 +21,9 @@ fn print_raw_json(value: &Value) { fn print_help() { println!("Usage:"); - println!(" [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)"); + println!( + " [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)" + ); println!(" .count | .len | .first | .last | .[0] | .[0].field | .map(.field)"); println!("Destructive: removeAllPeers | admin.removeAllPeers (batch admin.removePeer)"); } diff --git a/src/console/repl.rs b/src/console/repl.rs index d1d82bea..45a03e5c 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -6,9 +6,9 @@ use super::{ }; use eyre::Result; use reedline::{ - ColumnarMenu, Completer, Emacs, FileBackedHistory, KeyCode, KeyModifiers, MenuBuilder, Prompt, - PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, Reedline, ReedlineEvent, - ReedlineMenu, Signal, Span, Suggestion, default_emacs_keybindings, HISTORY_SIZE, + ColumnarMenu, Completer, Emacs, FileBackedHistory, HISTORY_SIZE, KeyCode, KeyModifiers, + MenuBuilder, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, Reedline, + ReedlineEvent, ReedlineMenu, Signal, Span, Suggestion, default_emacs_keybindings, }; use serde_json::Value; use std::{borrow::Cow, collections::BTreeMap, path::PathBuf}; @@ -130,10 +130,8 @@ async fn print_startup_snapshot( .get("clientVersion") .or_else(|| status.get("client_version")) .and_then(as_string); - let network_id = status - .get("networkId") - .or_else(|| status.get("network_id")) - .and_then(as_string); + let network_id = + status.get("networkId").or_else(|| status.get("network_id")).and_then(as_string); let head_number = status .get("headNumber") .or_else(|| status.get("head_number")) @@ -211,7 +209,9 @@ fn as_string(value: &Value) -> Option { fn print_help() { println!("Commands:"); - println!(" [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)"); + println!( + " [json_params] (RPC call; dots become underscores, e.g. eth.blockNumber)" + ); println!(" TAB completion for RPC namespaces/methods"); println!(" help | exit"); println!("Queries (run against last RPC result):"); diff --git a/src/console/rpc.rs b/src/console/rpc.rs index 6923c6d9..1429074e 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -58,9 +58,8 @@ impl RpcClient { pub async fn supported_modules(&self) -> Result> { let value = self.request_value("rpc_modules", None).await?; - serde_json::from_value(value).map_err(|e| { - eyre!("failed to parse rpc_modules response as a JSON object: {e}") - }) + serde_json::from_value(value) + .map_err(|e| eyre!("failed to parse rpc_modules response as a JSON object: {e}")) } } diff --git a/src/console/rpc_completion.rs b/src/console/rpc_completion.rs index f4628c63..350e1087 100644 --- a/src/console/rpc_completion.rs +++ b/src/console/rpc_completion.rs @@ -6,8 +6,8 @@ //! macros in `rpc-eth-api` and `rpc-api`, plus Berachain’s `beraAdmin_*` surface. //! When upstream adds RPCs, refresh these tables from the same reth sources. //! -//! Use [`RPC_NAMESPACE_TABLE`] to walk all namespaces, or [`method_suffixes`] / -//! [`dot_completions_for_namespace`] for one namespace. +//! Use `RPC_NAMESPACE_TABLE` to walk all namespaces, or `method_suffixes` / +//! `dot_completions_for_namespace` for one namespace. /// Berachain extension namespace (`beraAdmin_*` JSON-RPC). pub const BERA_ADMIN_METHOD_SUFFIXES: &[&str] = From 08609bf53de02cf4daa7909fca136b19cdc5a409 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 16:05:58 -0400 Subject: [PATCH 16/21] fix(deps): bump rand for RUSTSEC-2026-0097 Require rand >=0.8.6 and update lockfile (0.9.x -> 0.9.3) per advisory. --- Cargo.lock | 98 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 3 +- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dea280fc..07a932ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,7 +128,7 @@ dependencies = [ "either", "k256", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "serde_json", @@ -178,7 +178,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "crc", - "rand 0.8.5", + "rand 0.8.6", "serde", "thiserror 2.0.18", ] @@ -193,7 +193,7 @@ dependencies = [ "alloy-rlp", "arbitrary", "borsh", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -208,7 +208,7 @@ dependencies = [ "arbitrary", "borsh", "k256", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_with", "thiserror 2.0.18", @@ -393,7 +393,7 @@ dependencies = [ "paste", "proptest", "proptest-derive", - "rand 0.9.2", + "rand 0.9.3", "rapidhash", "ruint", "rustc-hash", @@ -610,7 +610,7 @@ dependencies = [ "ethereum_ssz 0.9.1", "ethereum_ssz_derive 0.9.1", "jsonwebtoken", - "rand 0.8.5", + "rand 0.8.6", "serde", "strum", ] @@ -719,7 +719,7 @@ dependencies = [ "coins-bip32", "coins-bip39", "k256", - "rand 0.8.5", + "rand 0.8.6", "thiserror 2.0.18", "zeroize", ] @@ -1258,7 +1258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1268,7 +1268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1278,7 +1278,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -1480,7 +1480,7 @@ dependencies = [ "jsonrpsee-proc-macros", "libc", "modular-bitfield", - "rand 0.8.5", + "rand 0.8.6", "reedline", "reth", "reth-basic-payload-builder", @@ -1734,7 +1734,7 @@ dependencies = [ "num_enum", "paste", "portable-atomic", - "rand 0.9.2", + "rand 0.9.3", "regress", "rustc-hash", "ryu-js", @@ -2166,7 +2166,7 @@ dependencies = [ "hmac", "once_cell", "pbkdf2", - "rand 0.8.5", + "rand 0.8.6", "sha2", "thiserror 1.0.69", ] @@ -2664,7 +2664,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.114", + "syn 1.0.109", ] [[package]] @@ -2884,7 +2884,7 @@ dependencies = [ "more-asserts", "multiaddr", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "socket2 0.5.10", "tokio", @@ -3045,7 +3045,7 @@ dependencies = [ "hex", "k256", "log", - "rand 0.8.5", + "rand 0.8.6", "secp256k1 0.30.0", "serde", "sha3", @@ -3326,7 +3326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -3834,7 +3834,7 @@ dependencies = [ "idna", "ipnet", "once_cell", - "rand 0.9.2", + "rand 0.9.3", "ring", "serde", "thiserror 2.0.18", @@ -3857,7 +3857,7 @@ dependencies = [ "moka", "once_cell", "parking_lot", - "rand 0.9.2", + "rand 0.9.3", "resolv-conf", "serde", "smallvec", @@ -4521,7 +4521,7 @@ dependencies = [ "jsonrpsee-types", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.3", "rustc-hash", "serde", "serde_json", @@ -5074,7 +5074,7 @@ dependencies = [ "hashbrown 0.16.1", "metrics", "quanta", - "rand 0.9.2", + "rand 0.9.3", "rand_xoshiro", "sketches-ddsketch", ] @@ -5712,7 +5712,7 @@ dependencies = [ "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.2", + "rand 0.9.3", "thiserror 2.0.18", ] @@ -6111,7 +6111,7 @@ dependencies = [ "bit-vec", "bitflags 2.10.0", "num-traits", - "rand 0.9.2", + "rand 0.9.3", "rand_chacha 0.9.0", "rand_xorshift", "regex-syntax", @@ -6223,7 +6223,7 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.3", "ring", "rustc-hash", "rustls", @@ -6272,9 +6272,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -6284,9 +6284,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -6356,7 +6356,7 @@ version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" dependencies = [ - "rand 0.9.2", + "rand 0.9.3", "rustversion", ] @@ -6707,7 +6707,7 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.9.2", + "rand 0.9.3", "rayon", "reth-chainspec", "reth-errors", @@ -6859,7 +6859,7 @@ dependencies = [ "cfg-if", "eyre", "libc", - "rand 0.8.5", + "rand 0.8.6", "reth-fs-util", "secp256k1 0.30.0", "serde", @@ -7076,7 +7076,7 @@ dependencies = [ "enr", "itertools 0.14.0", "parking_lot", - "rand 0.8.5", + "rand 0.8.6", "reth-ethereum-forks", "reth-net-banlist", "reth-net-nat", @@ -7103,7 +7103,7 @@ dependencies = [ "futures", "itertools 0.14.0", "metrics", - "rand 0.9.2", + "rand 0.9.3", "reth-chainspec", "reth-ethereum-forks", "reth-metrics", @@ -7248,7 +7248,7 @@ dependencies = [ "futures", "hmac", "pin-project", - "rand 0.8.5", + "rand 0.8.6", "reth-network-peers", "secp256k1 0.30.0", "sha2", @@ -7922,8 +7922,8 @@ dependencies = [ "metrics", "parking_lot", "pin-project", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.3", "rayon", "reth-chainspec", "reth-consensus", @@ -8163,7 +8163,7 @@ dependencies = [ "futures", "humantime", "ipnet", - "rand 0.9.2", + "rand 0.9.3", "reth-chainspec", "reth-cli-util", "reth-config", @@ -8804,7 +8804,7 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "metrics", - "rand 0.9.2", + "rand 0.9.3", "reqwest", "reth-chain-state", "reth-chainspec", @@ -9056,8 +9056,8 @@ dependencies = [ "alloy-eips", "alloy-genesis", "alloy-primitives", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.3", "reth-ethereum-primitives", "reth-primitives-traits", "secp256k1 0.30.0", @@ -9125,7 +9125,7 @@ dependencies = [ "parking_lot", "paste", "pin-project", - "rand 0.9.2", + "rand 0.9.3", "reth-chain-state", "reth-chainspec", "reth-eth-wire-types", @@ -9620,8 +9620,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.3", "rlp", "ruint-macro", "serde_core", @@ -9655,7 +9655,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" dependencies = [ - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -9890,7 +9890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ "bitcoin_hashes", - "rand 0.8.5", + "rand 0.8.6", "secp256k1-sys 0.10.1", "serde", ] @@ -9902,7 +9902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" dependencies = [ "bitcoin_hashes", - "rand 0.9.2", + "rand 0.9.3", "secp256k1-sys 0.11.0", ] @@ -10330,7 +10330,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand 0.8.6", "sha1", ] @@ -11180,7 +11180,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.3", "rustls", "rustls-pki-types", "sha1", @@ -11199,7 +11199,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.9.2", + "rand 0.9.3", "sha1", "thiserror 2.0.18", "utf-8", diff --git a/Cargo.toml b/Cargo.toml index 63e11bfb..256e4b82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,8 @@ jsonrpsee-proc-macros = "0.26.0" async-trait = "0.1.88" futures = "0.3.32" modular-bitfield = "0.13.1" -rand = "0.8" +# RUSTSEC-2026-0097: use >=0.8.6 on the 0.8 release line. +rand = "0.8.6" reth = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } reth-basic-payload-builder = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } reth-chainspec = { git = "https://github.com/camembera/reth", branch = "pog/provenance-callback" } From 54e4656cd8bdafedf1b2866660e474125615ce90 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:22:00 -0400 Subject: [PATCH 17/21] fix(console): align beraAdmin startup snapshot with node status JSON Read clientVersion/client_version, headNumber/head_number (with hex_or_decimal), and network snake_case fallbacks like try_format_node_status. Use chain_emoji for Berachain chain IDs instead of a fixed bear+star suffix. --- src/console/repl.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/console/repl.rs b/src/console/repl.rs index 45a03e5c..8bda0774 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -130,8 +130,10 @@ async fn print_startup_snapshot( .get("clientVersion") .or_else(|| status.get("client_version")) .and_then(as_string); - let network_id = - status.get("networkId").or_else(|| status.get("network_id")).and_then(as_string); + let network_id = status + .get("networkId") + .or_else(|| status.get("network_id")) + .and_then(as_string); let head_number = status .get("headNumber") .or_else(|| status.get("head_number")) From dd05c9767de9e9967f730c0f2b3feb8c0d490f50 Mon Sep 17 00:00:00 2001 From: Camembear Date: Fri, 17 Apr 2026 15:26:01 -0400 Subject: [PATCH 18/21] fix(console): ignore null JSON-RPC error field on IPC responses Some responses include "error": null; treat that as success and only fail when error is present and non-null. --- src/console/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/rpc.rs b/src/console/rpc.rs index 1429074e..d731f232 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -189,7 +189,7 @@ impl IpcClientLite { } let resp: Value = serde_json::from_str(&line)?; - if let Some(err) = resp.get("error") { + if let Some(err) = resp.get("error").filter(|e| !e.is_null()) { return Err(eyre!("rpc error: {}", err)); } resp.get("result").cloned().ok_or_else(|| eyre!("missing result field in IPC response")) From 24934db6d1ef77d9b5a6798b471cf415fcd9cfa7 Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 22 Apr 2026 16:29:33 -0400 Subject: [PATCH 19/21] fix(console): use beradmin_* JSON-RPC namespace The beradmin API is registered as lowercase beradmin (jsonrpsee namespace). The REPL normalized beraAdmin.* to beraAdmin_* which returns -32601 Method not found. Align probe, completions, and rpc_completion table with beradmin; add sealedBlockAttribution hint. --- src/console/repl.rs | 10 +++++----- src/console/rpc_completion.rs | 22 +++++++++++++++------- src/console/run.rs | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/console/repl.rs b/src/console/repl.rs index 8bda0774..6542ee1c 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -244,9 +244,9 @@ impl CompletionHelper { words.extend(super::rpc_completion::dot_completions_for_namespace(module)); } if has_bera_admin { - words.push("beraAdmin.".to_owned()); - words.push("beraAdmin_".to_owned()); - words.extend(super::rpc_completion::dot_completions_for_namespace("beraAdmin")); + words.push("beradmin.".to_owned()); + words.push("beradmin_".to_owned()); + words.extend(super::rpc_completion::dot_completions_for_namespace("beradmin")); } words.sort(); words.dedup(); @@ -290,14 +290,14 @@ mod tests { fn completion_includes_bera_admin_when_enabled() { let modules = BTreeMap::new(); let helper = CompletionHelper::new(&modules, true); - assert!(helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); + assert!(helper.words.iter().any(|w| w == "beradmin.detailedPeers")); } #[test] fn completion_excludes_bera_admin_when_disabled() { let modules = BTreeMap::new(); let helper = CompletionHelper::new(&modules, false); - assert!(!helper.words.iter().any(|w| w == "beraAdmin.detailedPeers")); + assert!(!helper.words.iter().any(|w| w == "beradmin.detailedPeers")); } #[test] diff --git a/src/console/rpc_completion.rs b/src/console/rpc_completion.rs index 350e1087..d674b2c5 100644 --- a/src/console/rpc_completion.rs +++ b/src/console/rpc_completion.rs @@ -3,15 +3,22 @@ //! The REPL completes `namespace.method` tokens (e.g. `eth.getLogs`). JSON-RPC uses //! `namespace_method`; this module only stores the **suffix** after the namespace //! (the part after `_` or `.`), taken from upstream reth’s `#[method(name = "...")]` -//! macros in `rpc-eth-api` and `rpc-api`, plus Berachain’s `beraAdmin_*` surface. +//! macros in `rpc-eth-api` and `rpc-api`, plus Berachain’s `beradmin_*` surface. //! When upstream adds RPCs, refresh these tables from the same reth sources. //! //! Use `RPC_NAMESPACE_TABLE` to walk all namespaces, or `method_suffixes` / //! `dot_completions_for_namespace` for one namespace. -/// Berachain extension namespace (`beraAdmin_*` JSON-RPC). -pub const BERA_ADMIN_METHOD_SUFFIXES: &[&str] = - &["detailedPeers", "nodeStatus", "banPeer", "penalizePeer", "prepareCanary", "submitCanary"]; +/// Berachain extension namespace (`beradmin_*` JSON-RPC; matches `#[rpc(namespace = "beradmin")]`). +pub const BERA_ADMIN_METHOD_SUFFIXES: &[&str] = &[ + "detailedPeers", + "nodeStatus", + "banPeer", + "penalizePeer", + "prepareCanary", + "submitCanary", + "sealedBlockAttribution", +]; /// `eth_*` methods from reth. pub const ETH_METHOD_SUFFIXES: &[&str] = &[ @@ -290,7 +297,7 @@ pub const RPC_NAMESPACE_TABLE: &[(&str, &[&str])] = &[ ("mev", MEV_METHOD_SUFFIXES), ("testing", TESTING_METHOD_SUFFIXES), ("flashbots", FLASHBOTS_METHOD_SUFFIXES), - ("beraAdmin", BERA_ADMIN_METHOD_SUFFIXES), + ("beradmin", BERA_ADMIN_METHOD_SUFFIXES), ]; /// Returns method suffixes for a namespace, or empty if unknown. @@ -335,7 +342,8 @@ mod tests { #[test] fn bera_admin_dot_forms() { - let v = dot_completions_for_namespace("beraAdmin"); - assert!(v.iter().any(|s| s == "beraAdmin.detailedPeers")); + let v = dot_completions_for_namespace("beradmin"); + assert!(v.iter().any(|s| s == "beradmin.detailedPeers")); + assert!(v.iter().any(|s| s == "beradmin.sealedBlockAttribution")); } } diff --git a/src/console/run.rs b/src/console/run.rs index 0c16effc..d7f77a7d 100644 --- a/src/console/run.rs +++ b/src/console/run.rs @@ -15,7 +15,7 @@ pub async fn run_console(cmd: ConsoleCommand) -> Result<()> { let chain_id = rpc.request_value("eth_chainId", None).await.ok().and_then(|v| parse_chain_id(&v)); - let bera_admin_status = rpc.request_value("beraAdmin_nodeStatus", None).await.ok(); + let bera_admin_status = rpc.request_value("beradmin_nodeStatus", None).await.ok(); let has_bera_admin = bera_admin_status.is_some(); if let Some(script) = cmd.exec.as_deref() { From d6e0b9830407f98ce7ada5e116731d95eb61a5db Mon Sep 17 00:00:00 2001 From: Camembear Date: Thu, 23 Apr 2026 21:45:30 -0400 Subject: [PATCH 20/21] refactor(console): remove HTTP and WebSocket transport support The `bera-reth console` command now exclusively connects via IPC/Unix Domain Sockets. This removes the `http://` and `ws://` schemes from endpoint resolution, drops the unused `HttpClient` and `WsClient` from the RPC initialization, and removes the associated dependencies from the jsonrpsee library. --- src/console/cli.rs | 5 +- src/console/endpoint.rs | 28 -------- src/console/rpc.rs | 143 +++------------------------------------- 3 files changed, 10 insertions(+), 166 deletions(-) diff --git a/src/console/cli.rs b/src/console/cli.rs index 822341aa..dc961335 100644 --- a/src/console/cli.rs +++ b/src/console/cli.rs @@ -1,11 +1,10 @@ use clap::Args; use reth_cli_runner::CliRunner; -/// JSON-RPC console (IPC, HTTP, or WebSocket). +/// JSON-RPC console over IPC. #[derive(Debug, Clone, Args)] pub struct ConsoleCommand { - /// IPC path, or `http(s)://…`, or `ws(s)://…`. If omitted, uses the platform default - /// datadir with `reth.ipc`. + /// IPC path. If omitted, uses the platform default datadir with `reth.ipc`. #[arg(value_name = "ENDPOINT")] pub endpoint: Option, diff --git a/src/console/endpoint.rs b/src/console/endpoint.rs index 13f8da68..6fa0955e 100644 --- a/src/console/endpoint.rs +++ b/src/console/endpoint.rs @@ -3,8 +3,6 @@ use std::path::PathBuf; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Transport { - Http, - Ws, Ipc, } @@ -36,12 +34,6 @@ pub fn resolve_endpoint(endpoint: Option<&str>) -> Result { fn detect_transport(endpoint: &str) -> Result { let lower = endpoint.to_ascii_lowercase(); - if lower.starts_with("http://") || lower.starts_with("https://") { - return Ok(Transport::Http); - } - if lower.starts_with("ws://") || lower.starts_with("wss://") { - return Ok(Transport::Ws); - } if lower.contains("://") { bail!("unsupported endpoint scheme in {endpoint:?}"); } @@ -59,26 +51,6 @@ mod tests { assert!(got.raw.ends_with("reth.ipc")); } - #[test] - fn parses_http() { - let got = resolve_endpoint(Some("http://127.0.0.1:8545")).unwrap(); - assert_eq!(got.transport, Transport::Http); - } - - #[test] - fn parses_http_uppercase_scheme_preserves_raw() { - let raw = "HTTP://127.0.0.1:8545"; - let got = resolve_endpoint(Some(raw)).unwrap(); - assert_eq!(got.transport, Transport::Http); - assert_eq!(got.raw, raw); - } - - #[test] - fn parses_ws() { - let got = resolve_endpoint(Some("ws://127.0.0.1:8546")).unwrap(); - assert_eq!(got.transport, Transport::Ws); - } - #[test] fn rejects_unknown_scheme() { let err = resolve_endpoint(Some("ftp://example.test")).unwrap_err(); diff --git a/src/console/rpc.rs b/src/console/rpc.rs index d731f232..db5b4a01 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -1,14 +1,5 @@ use super::endpoint::{ResolvedEndpoint, Transport}; use eyre::{Result, eyre}; -use jsonrpsee::{ - core::{ - client::ClientT, - params::{ArrayParams, ObjectParams}, - }, - http_client::{HttpClient, HttpClientBuilder}, - rpc_params, - ws_client::{WsClient, WsClientBuilder}, -}; use serde_json::{Map, Value}; use std::{ collections::BTreeMap, @@ -23,22 +14,12 @@ use tokio::{ #[derive(Debug)] pub enum RpcClient { - Http(HttpClient), - Ws(WsClient), Ipc(IpcClientLite), } impl RpcClient { pub async fn connect(endpoint: &ResolvedEndpoint) -> Result { match endpoint.transport { - Transport::Http => { - let client = HttpClientBuilder::default().build(&endpoint.raw)?; - Ok(Self::Http(client)) - } - Transport::Ws => { - let client = WsClientBuilder::default().build(&endpoint.raw).await?; - Ok(Self::Ws(client)) - } Transport::Ipc => { validate_ipc_endpoint(&endpoint.raw)?; let client = IpcClientLite::new(endpoint.raw.clone()); @@ -50,8 +31,6 @@ impl RpcClient { pub async fn request_value(&self, method: &str, params: Option) -> Result { let params = RpcParams::from_value(params)?; match self { - Self::Http(client) => params.request(client, method).await, - Self::Ws(client) => params.request(client, method).await, Self::Ipc(client) => client.request(method, params.into_value()).await, } } @@ -65,8 +44,8 @@ impl RpcClient { enum RpcParams { None, - Array(ArrayParams, Vec), - Object(ObjectParams, Map), + Array(Vec), + Object(Map), } impl RpcParams { @@ -76,47 +55,21 @@ impl RpcParams { }; match value { Value::Null => Ok(Self::None), - Value::Array(values) => { - let mut out = ArrayParams::new(); - for v in &values { - out.insert(v).map_err(|e| eyre!("invalid rpc array params: {e}"))?; - } - Ok(Self::Array(out, values)) - } - Value::Object(values) => Ok(Self::Object(object_params(values.clone())?, values)), + Value::Array(values) => Ok(Self::Array(values)), + Value::Object(values) => Ok(Self::Object(values)), _ => Err(eyre!("rpc params must be null, JSON array, or JSON object")), } } - async fn request(&self, client: &C, method: &str) -> Result - where - C: ClientT, - { - let value = match self { - Self::None => client.request(method, rpc_params![]).await?, - Self::Array(params, _) => client.request(method, params.clone()).await?, - Self::Object(params, _) => client.request(method, params.clone()).await?, - }; - Ok(value) - } - fn into_value(self) -> Value { match self { Self::None => Value::Array(vec![]), - Self::Array(_, values) => Value::Array(values), - Self::Object(_, values) => Value::Object(values), + Self::Array(values) => Value::Array(values), + Self::Object(values) => Value::Object(values), } } } -fn object_params(values: Map) -> Result { - let mut params = ObjectParams::new(); - for (k, v) in values { - params.insert(k.as_str(), v).map_err(|e| eyre!("invalid rpc object params: {e}"))?; - } - Ok(params) -} - fn validate_ipc_endpoint(path: &str) -> Result<()> { let endpoint = Path::new(path); if !endpoint.exists() { @@ -199,8 +152,8 @@ impl IpcClientLite { #[cfg(test)] mod tests { use super::*; - use crate::console::endpoint::{ResolvedEndpoint, Transport}; - use jsonrpsee::{RpcModule, server::ServerBuilder}; + + use serde_json::json; use tempfile::tempdir; use tokio::{ @@ -247,86 +200,6 @@ mod tests { assert!(err.to_string().contains("not a unix socket")); } - #[tokio::test(flavor = "multi_thread")] - async fn rpc_client_http_request_and_supported_modules() { - let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); - let addr = server.local_addr().expect("local addr"); - - let mut module = RpcModule::new(()); - module - .register_method("eth_blockNumber", |_params, _ctx, _ext| { - Ok::(json!("0x10")) - }) - .expect("register eth_blockNumber"); - module - .register_method("rpc_modules", |_params, _ctx, _ext| { - Ok::(json!({ - "eth": "1.0", - "net": "1.0" - })) - }) - .expect("register rpc_modules"); - - let handle = server.start(module); - let endpoint = - ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; - - let client = RpcClient::connect(&endpoint).await.unwrap(); - let block = client.request_value("eth_blockNumber", None).await.unwrap(); - assert_eq!(block, json!("0x10")); - - let modules = client.supported_modules().await.unwrap(); - assert_eq!(modules.get("eth"), Some(&"1.0".to_owned())); - assert_eq!(modules.get("net"), Some(&"1.0".to_owned())); - - handle.stop().expect("stop server"); - } - - #[tokio::test(flavor = "multi_thread")] - async fn rpc_client_ws_request_path() { - let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); - let addr = server.local_addr().expect("local addr"); - - let mut module = RpcModule::new(()); - module - .register_method("web3_clientVersion", |_params, _ctx, _ext| { - Ok::(json!("reth/1.0.0")) - }) - .expect("register method"); - - let handle = server.start(module); - let endpoint = ResolvedEndpoint { raw: format!("ws://{addr}"), transport: Transport::Ws }; - - let client = RpcClient::connect(&endpoint).await.unwrap(); - let version = client.request_value("web3_clientVersion", None).await.unwrap(); - assert_eq!(version, json!("reth/1.0.0")); - - handle.stop().expect("stop server"); - } - - #[tokio::test(flavor = "multi_thread")] - async fn supported_modules_errors_on_invalid_shape() { - let server = ServerBuilder::default().build("127.0.0.1:0").await.expect("server starts"); - let addr = server.local_addr().expect("local addr"); - - let mut module = RpcModule::new(()); - module - .register_method("rpc_modules", |_params, _ctx, _ext| { - Ok::(json!(["eth", "net"])) - }) - .expect("register rpc_modules"); - - let handle = server.start(module); - let endpoint = - ResolvedEndpoint { raw: format!("http://{addr}"), transport: Transport::Http }; - let client = RpcClient::connect(&endpoint).await.unwrap(); - - let err = client.supported_modules().await.unwrap_err(); - assert!(err.to_string().contains("invalid type")); - - handle.stop().expect("stop server"); - } - #[tokio::test(flavor = "multi_thread")] async fn ipc_client_handles_empty_response_and_rpc_error() { let dir = tempdir().expect("tempdir"); From ddc11ab551ae741100f02b563c84eb0f148a0177 Mon Sep 17 00:00:00 2001 From: Camembear Date: Wed, 29 Apr 2026 17:13:41 -0400 Subject: [PATCH 21/21] cargo fmt --- src/chainspec/mod.rs | 12 ++++++------ src/consensus/mod.rs | 12 ++++++------ src/console/command.rs | 6 +++--- src/console/endpoint.rs | 4 ++-- src/console/output.rs | 16 ++++++++-------- src/console/repl.rs | 6 ++---- src/console/rpc.rs | 3 +-- src/engine/validator.rs | 4 ++-- src/evm/mod.rs | 8 ++++---- src/genesis/mod.rs | 8 ++++---- src/hardforks/mod.rs | 4 ++-- src/pool/mod.rs | 4 ++-- src/rpc/api.rs | 14 +++++++------- src/rpc/receipt.rs | 24 ++++++++++++------------ src/transaction/mod.rs | 14 +++++++------- 15 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/chainspec/mod.rs b/src/chainspec/mod.rs index 8322691f..b5523f3c 100644 --- a/src/chainspec/mod.rs +++ b/src/chainspec/mod.rs @@ -127,8 +127,8 @@ impl BerachainChainSpec { // We filter out TTD-based forks w/o a pre-known block since those do not show up in // the fork filter. Some(match condition { - ForkCondition::Block(block) | - ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), + ForkCondition::Block(block) + | ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), ForkCondition::Timestamp(time) => ForkFilterKey::Time(time), _ => return None, }) @@ -160,8 +160,8 @@ impl BerachainChainSpec { for (_, cond) in self.inner.hardforks.forks_iter() { // handle block based forks and the sepolia merge netsplit block edge case (TTD // ForkCondition with Some(block)) - if let ForkCondition::Block(block) | - ForkCondition::TTD { fork_block: Some(block), .. } = cond + if let ForkCondition::Block(block) + | ForkCondition::TTD { fork_block: Some(block), .. } = cond { if head.number >= block { // skip duplicated hardforks: hardforks enabled at genesis block @@ -573,8 +573,8 @@ impl From for BerachainChainSpec { } // Validate Prague3 ordering if configured (Prague3 must come at or after Prague2) - if let Some(prague3_config) = prague3_config_opt.as_ref() && - prague3_config.time < prague2_config.time + if let Some(prague3_config) = prague3_config_opt.as_ref() + && prague3_config.time < prague2_config.time { panic!( "Prague3 hardfork must activate at or after Prague2 hardfork. Prague2 time: {}, Prague3 time: {}.", diff --git a/src/consensus/mod.rs b/src/consensus/mod.rs index a4ec9704..2f155998 100644 --- a/src/consensus/mod.rs +++ b/src/consensus/mod.rs @@ -135,8 +135,8 @@ impl FullConsensus for BerachainBeaconConsensus { for receipt in &result.receipts { for log in &receipt.logs { // Check if this is a Transfer event (first topic is the event signature) - if log.topics().first() == Some(&TRANSFER_EVENT_SIGNATURE) && - log.topics().len() >= 3 + if log.topics().first() == Some(&TRANSFER_EVENT_SIGNATURE) + && log.topics().len() >= 3 { // Transfer event has indexed from (topics[1]) and to (topics[2]) addresses let from_addr = Address::from_word(log.topics()[1]); @@ -144,8 +144,8 @@ impl FullConsensus for BerachainBeaconConsensus { // Check if BEX vault is involved in the transfer (block all BEX vault // transfers) - if let Some(bex_vault) = bex_vault_address && - (from_addr == bex_vault || to_addr == bex_vault) + if let Some(bex_vault) = bex_vault_address + && (from_addr == bex_vault || to_addr == bex_vault) { return Err(ConsensusError::Other( BerachainExecutionError::Prague3BexVaultTransfer { @@ -197,8 +197,8 @@ impl FullConsensus for BerachainBeaconConsensus { for log in &receipt.logs { // Check if this log is from the BEX vault and is an InternalBalanceChanged // event - if log.address == bex_vault_address && - log.topics().first() == Some(&INTERNAL_BALANCE_CHANGED_SIGNATURE) + if log.address == bex_vault_address + && log.topics().first() == Some(&INTERNAL_BALANCE_CHANGED_SIGNATURE) { return Err(ConsensusError::Other( BerachainExecutionError::Prague3BexVaultEvent { diff --git a/src/console/command.rs b/src/console/command.rs index a3353349..084b78fe 100644 --- a/src/console/command.rs +++ b/src/console/command.rs @@ -97,9 +97,9 @@ fn looks_like_implicit_rpc(line: &str) -> bool { // - eth_getBalance ["0x...", "latest"] // - eth.getBalance ["0x...", "latest"] // Single-token `eth.blockNumber`-style input is handled as MethodToken. - line.contains(char::is_whitespace) || - (line.contains('(') && line.ends_with(')')) || - (line.contains('_') && !line.contains(' ')) + line.contains(char::is_whitespace) + || (line.contains('(') && line.ends_with(')')) + || (line.contains('_') && !line.contains(' ')) } fn split_method_and_params(input: &str) -> Result<(String, Option<&str>)> { diff --git a/src/console/endpoint.rs b/src/console/endpoint.rs index 6fa0955e..936b3361 100644 --- a/src/console/endpoint.rs +++ b/src/console/endpoint.rs @@ -15,8 +15,8 @@ pub struct ResolvedEndpoint { const DEFAULT_IPC_FILENAME: &str = "reth.ipc"; pub fn default_datadir() -> PathBuf { - if cfg!(target_os = "macos") && - let Some(home) = dirs::home_dir() + if cfg!(target_os = "macos") + && let Some(home) = dirs::home_dir() { return home.join("Library").join("Application Support").join("reth"); } diff --git a/src/console/output.rs b/src/console/output.rs index ce20d294..5130ea01 100644 --- a/src/console/output.rs +++ b/src/console/output.rs @@ -138,8 +138,8 @@ fn walk(path: &str, value: &Value, native_symbol: &str, out: &mut Vec) { } } Value::Number(n) => { - if let Some(wei) = n.as_u64().map(u128::from) && - looks_like_wei(wei) + if let Some(wei) = n.as_u64().map(u128::from) + && looks_like_wei(wei) { out.push(format!("{path}: {wei} wei -> {} {native_symbol}", format_eth(wei))); } @@ -289,12 +289,12 @@ fn try_format_detailed_peers(value: &Value) -> Option { fn try_format_node_status(value: &Value) -> Option { let obj = value.as_object()?; - if !obj.contains_key("chainId") && - !obj.contains_key("chain_id") && - !obj.contains_key("genesisHash") && - !obj.contains_key("genesis_hash") && - !obj.contains_key("headNumber") && - !obj.contains_key("head_number") + if !obj.contains_key("chainId") + && !obj.contains_key("chain_id") + && !obj.contains_key("genesisHash") + && !obj.contains_key("genesis_hash") + && !obj.contains_key("headNumber") + && !obj.contains_key("head_number") { return None; } diff --git a/src/console/repl.rs b/src/console/repl.rs index 6542ee1c..058fabb4 100644 --- a/src/console/repl.rs +++ b/src/console/repl.rs @@ -130,10 +130,8 @@ async fn print_startup_snapshot( .get("clientVersion") .or_else(|| status.get("client_version")) .and_then(as_string); - let network_id = status - .get("networkId") - .or_else(|| status.get("network_id")) - .and_then(as_string); + let network_id = + status.get("networkId").or_else(|| status.get("network_id")).and_then(as_string); let head_number = status .get("headNumber") .or_else(|| status.get("head_number")) diff --git a/src/console/rpc.rs b/src/console/rpc.rs index db5b4a01..33263596 100644 --- a/src/console/rpc.rs +++ b/src/console/rpc.rs @@ -152,8 +152,7 @@ impl IpcClientLite { #[cfg(test)] mod tests { use super::*; - - + use serde_json::json; use tempfile::tempdir; use tokio::{ diff --git a/src/engine/validator.rs b/src/engine/validator.rs index 5c8419a1..a183e418 100644 --- a/src/engine/validator.rs +++ b/src/engine/validator.rs @@ -162,8 +162,8 @@ where payload_or_attrs: PayloadOrAttributes<'_, Types::ExecutionData, Types::PayloadAttributes>, ) -> Result<(), EngineObjectValidationError> { // Validate execution requests if present in the payload - if let PayloadOrAttributes::ExecutionPayload(payload) = &payload_or_attrs && - let Some(requests) = payload.sidecar.requests() + if let PayloadOrAttributes::ExecutionPayload(payload) = &payload_or_attrs + && let Some(requests) = payload.sidecar.requests() { validate_execution_requests(requests)?; } diff --git a/src/evm/mod.rs b/src/evm/mod.rs index 34e441d9..dc829520 100644 --- a/src/evm/mod.rs +++ b/src/evm/mod.rs @@ -403,14 +403,14 @@ mod tests { assert!(result_without_tracer.is_ok()); // Both should have gas_used = 0 - if let Ok(result) = &result_with_tracer && - let ExecutionResult::Success { gas_used, .. } = &result.result + if let Ok(result) = &result_with_tracer + && let ExecutionResult::Success { gas_used, .. } = &result.result { assert_eq!(*gas_used, 0); } - if let Ok(result) = &result_without_tracer && - let ExecutionResult::Success { gas_used, .. } = &result.result + if let Ok(result) = &result_without_tracer + && let ExecutionResult::Success { gas_used, .. } = &result.result { assert_eq!(*gas_used, 0); } diff --git a/src/genesis/mod.rs b/src/genesis/mod.rs index a5e7a9ee..eb068c1d 100644 --- a/src/genesis/mod.rs +++ b/src/genesis/mod.rs @@ -98,12 +98,12 @@ impl TryFrom<&OtherFields> for BerachainGenesisConfig { (Some(prague1_config), Some(prague2_config)) => { // Both configured - validate Prague2 comes at or after Prague1 if prague2_config.time < prague1_config.time { - return Err(BerachainConfigError::InvalidConfig(serde_json::Error::io( - std::io::Error::new( + return Err(BerachainConfigError::InvalidConfig( + serde_json::Error::io(std::io::Error::new( std::io::ErrorKind::InvalidData, "Prague2 hardfork must activate at or after Prague1 hardfork", - ), - ))); + )), + )); } } _ => { diff --git a/src/hardforks/mod.rs b/src/hardforks/mod.rs index e42b9653..e80d963d 100644 --- a/src/hardforks/mod.rs +++ b/src/hardforks/mod.rs @@ -34,8 +34,8 @@ pub trait BerachainHardforks: EthereumHardforks { /// Checks if Prague3 hardfork is active at given timestamp /// Prague3 is active between its activation time and Prague4 activation fn is_prague3_active_at_timestamp(&self, timestamp: u64) -> bool { - self.berachain_fork_activation(BerachainHardfork::Prague3).active_at_timestamp(timestamp) && - !self.is_prague4_active_at_timestamp(timestamp) + self.berachain_fork_activation(BerachainHardfork::Prague3).active_at_timestamp(timestamp) + && !self.is_prague4_active_at_timestamp(timestamp) } /// Checks if Prague4 hardfork is active at given timestamp diff --git a/src/pool/mod.rs b/src/pool/mod.rs index c3e491f0..f7da9247 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -39,8 +39,8 @@ where ) -> eyre::Result { let pool_config = ctx.pool_config(); - let blobs_disabled = ctx.config().txpool.disable_blobs_support || - ctx.config().txpool.blobpool_max_count == 0; + let blobs_disabled = ctx.config().txpool.disable_blobs_support + || ctx.config().txpool.blobpool_max_count == 0; let blob_cache_size = if let Some(blob_cache_size) = pool_config.blob_cache_size { Some(blob_cache_size) diff --git a/src/rpc/api.rs b/src/rpc/api.rs index 35817e31..59563d5e 100644 --- a/src/rpc/api.rs +++ b/src/rpc/api.rs @@ -226,16 +226,16 @@ impl TransactionBuilder for TransactionRequest { } fn can_submit(&self) -> bool { - self.from.is_some() && - self.to.is_some() && - self.gas.is_some() && - (self.gas_price.is_some() || self.max_fee_per_gas.is_some()) + self.from.is_some() + && self.to.is_some() + && self.gas.is_some() + && (self.gas_price.is_some() || self.max_fee_per_gas.is_some()) } fn can_build(&self) -> bool { - self.to.is_some() && - self.gas.is_some() && - (self.gas_price.is_some() || self.max_fee_per_gas.is_some()) + self.to.is_some() + && self.gas.is_some() + && (self.gas_price.is_some() || self.max_fee_per_gas.is_some()) } fn output_tx_type(&self) -> ::TxType { diff --git a/src/rpc/receipt.rs b/src/rpc/receipt.rs index b8c0a736..5d58e59b 100644 --- a/src/rpc/receipt.rs +++ b/src/rpc/receipt.rs @@ -74,24 +74,24 @@ impl BerachainReceiptEnvelope { /// Returns inner receipt reference pub const fn as_receipt(&self) -> &Receipt { match self { - Self::Legacy(receipt) | - Self::Eip2930(receipt) | - Self::Eip1559(receipt) | - Self::Eip4844(receipt) | - Self::Eip7702(receipt) | - Self::Berachain(receipt) => &receipt.receipt, + Self::Legacy(receipt) + | Self::Eip2930(receipt) + | Self::Eip1559(receipt) + | Self::Eip4844(receipt) + | Self::Eip7702(receipt) + | Self::Berachain(receipt) => &receipt.receipt, } } /// Returns the bloom filter for this receipt pub const fn bloom(&self) -> &Bloom { match self { - Self::Legacy(receipt) | - Self::Eip2930(receipt) | - Self::Eip1559(receipt) | - Self::Eip4844(receipt) | - Self::Eip7702(receipt) | - Self::Berachain(receipt) => &receipt.logs_bloom, + Self::Legacy(receipt) + | Self::Eip2930(receipt) + | Self::Eip1559(receipt) + | Self::Eip4844(receipt) + | Self::Eip7702(receipt) + | Self::Berachain(receipt) => &receipt.logs_bloom, } } } diff --git a/src/transaction/mod.rs b/src/transaction/mod.rs index 3387508d..97a2965e 100644 --- a/src/transaction/mod.rs +++ b/src/transaction/mod.rs @@ -138,13 +138,13 @@ impl PoLTx { } fn rlp_payload_length(&self) -> usize { - self.chain_id.length() + - self.from.length() + - self.to.length() + - self.nonce.length() + - self.gas_limit.length() + - self.gas_price.length() + - self.input.length() + self.chain_id.length() + + self.from.length() + + self.to.length() + + self.nonce.length() + + self.gas_limit.length() + + self.gas_price.length() + + self.input.length() } fn rlp_encoded_length(&self) -> usize {