From 33394862d6addc007056dca082d4af401f68a43f Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:03:25 +0200 Subject: [PATCH 01/11] feat(cli): refactor --- bolt-cli/Cargo.lock | 252 ++++++++++++++++-- bolt-cli/Cargo.toml | 2 + bolt-cli/src/cli.rs | 93 ++++--- .../{delegation.rs => commands/delegate.rs} | 85 +++++- bolt-cli/src/commands/mod.rs | 11 + bolt-cli/src/{ => commands}/pubkeys.rs | 39 ++- bolt-cli/src/commands/send.rs | 1 + bolt-cli/src/{utils => common}/dirk.rs | 0 bolt-cli/src/{utils => common}/keystore.rs | 0 bolt-cli/src/{utils => common}/mod.rs | 0 bolt-cli/src/{utils => common}/signing.rs | 6 +- bolt-cli/src/main.rs | 109 +------- 12 files changed, 429 insertions(+), 169 deletions(-) rename bolt-cli/src/{delegation.rs => commands/delegate.rs} (79%) create mode 100644 bolt-cli/src/commands/mod.rs rename bolt-cli/src/{ => commands}/pubkeys.rs (54%) create mode 100644 bolt-cli/src/commands/send.rs rename bolt-cli/src/{utils => common}/dirk.rs (100%) rename bolt-cli/src/{utils => common}/keystore.rs (100%) rename bolt-cli/src/{utils => common}/mod.rs (100%) rename bolt-cli/src/{utils => common}/signing.rs (94%) diff --git a/bolt-cli/Cargo.lock b/bolt-cli/Cargo.lock index 6fd80d826..e1c9d6df7 100644 --- a/bolt-cli/Cargo.lock +++ b/bolt-cli/Cargo.lock @@ -455,7 +455,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -494,7 +494,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools 0.10.5", @@ -526,6 +526,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -605,6 +611,8 @@ dependencies = [ "ethereum-consensus", "eyre", "hex", + "inquire", + "nu-ansi-term 0.50.1", "prost", "reqwest", "rustls", @@ -827,6 +835,31 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -1001,6 +1034,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "ecdsa" version = "0.14.8" @@ -1389,6 +1428,24 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1740,6 +1797,24 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.6.0", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "tempfile", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -1875,7 +1950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1945,6 +2020,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -2031,6 +2118,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "nom" version = "7.1.3" @@ -2051,6 +2147,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2146,7 +2251,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -2236,7 +2341,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2431,7 +2536,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", @@ -2562,7 +2667,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.6.0", ] [[package]] @@ -2776,7 +2881,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2920,7 +3025,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -3087,6 +3192,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3268,7 +3394,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] @@ -3374,7 +3500,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -3606,7 +3732,7 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "nu-ansi-term", + "nu-ansi-term 0.46.0", "sharded-slab", "smallvec", "thread_local", @@ -3682,6 +3808,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3894,7 +4032,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3903,7 +4041,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3913,7 +4051,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", ] [[package]] @@ -3922,7 +4069,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -3931,7 +4078,22 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3940,28 +4102,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3974,24 +4154,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/bolt-cli/Cargo.toml b/bolt-cli/Cargo.toml index 526ffeec9..5898e813c 100644 --- a/bolt-cli/Cargo.toml +++ b/bolt-cli/Cargo.toml @@ -10,7 +10,9 @@ tokio = { version = "1.41.0", features = ["full"] } # cli clap = { version = "4.5.4", features = ["derive", "env"] } serde = { version = "1.0.197", features = ["derive"] } +inquire = { version = "0.7", features = ["editor"] } serde_json = "1.0.115" +nu-ansi-term = "0.50" # grpc rustls = { version = "0.23.15", features = ["ring"] } diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 1f387988c..8458acd20 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -1,11 +1,14 @@ -use clap::{Parser, Subcommand, ValueEnum}; +use clap::{ + builder::styling::{AnsiColor, Color, Style}, + Parser, Subcommand, ValueEnum, +}; use serde::Deserialize; -use crate::utils::keystore::DEFAULT_KEYSTORE_PASSWORD; +use crate::common::keystore::DEFAULT_KEYSTORE_PASSWORD; -/// A CLI tool to interact with Bolt Protocol ✨ +/// `bolt` is a CLI tool to interact with Bolt Protocol ✨ #[derive(Parser, Debug, Clone, Deserialize)] -#[command(author, version, about, long_about = None)] +#[command(author, version, styles = cli_styles(), about, arg_required_else_help(true))] pub struct Opts { /// The subcommand to run. #[clap(subcommand)] @@ -15,39 +18,47 @@ pub struct Opts { #[derive(Subcommand, Debug, Clone, Deserialize)] pub enum Commands { /// Generate BLS delegation or revocation messages. - Delegate { - /// The BLS public key to which the delegation message should be signed. - #[clap(long, env = "DELEGATEE_PUBKEY")] - delegatee_pubkey: String, - - /// The output file for the delegations. - #[clap(long, env = "OUTPUT_FILE_PATH", default_value = "delegations.json")] - out: String, - - /// The chain for which the delegation message is intended. - #[clap(long, env = "CHAIN", default_value = "mainnet")] - chain: Chain, - - /// The action to perform. The tool can be used to generate - /// delegation or revocation messages (default: delegate). - #[clap(long, env = "ACTION", default_value = "delegate")] - action: Action, - - /// The source of the private key. - #[clap(subcommand)] - source: KeySource, - }, + Delegate(DelegateCommand), /// Output a list of pubkeys in JSON format. - Pubkeys { - /// The output file for the pubkeys. - #[clap(long, env = "OUTPUT_FILE_PATH", default_value = "pubkeys.json")] - out: String, - - /// The source of the private keys from which to extract the pubkeys. - #[clap(subcommand)] - source: KeySource, - }, + Pubkeys(PubkeysCommand), +} + +/// Command for generating BLS delegation or revocation messages. +#[derive(Debug, Clone, Deserialize, Parser)] +pub struct DelegateCommand { + /// The BLS public key to which the delegation message should be signed. + #[clap(long, env = "DELEGATEE_PUBKEY")] + pub delegatee_pubkey: String, + + /// The output file for the delegations. + #[clap(long, env = "OUTPUT_FILE_PATH", default_value = "delegations.json")] + pub out: String, + + /// The chain for which the delegation message is intended. + #[clap(long, env = "CHAIN", default_value = "mainnet")] + pub chain: Chain, + + /// The action to perform. The tool can be used to generate + /// delegation or revocation messages (default: delegate). + #[clap(long, env = "ACTION", default_value = "delegate")] + pub action: Action, + + /// The source of the private key. + #[clap(subcommand)] + pub source: KeySource, +} + +/// Command for outputting a list of pubkeys in JSON format. +#[derive(Debug, Clone, Deserialize, Parser)] +pub struct PubkeysCommand { + /// The output file for the pubkeys. + #[clap(long, env = "OUTPUT_FILE_PATH", default_value = "pubkeys.json")] + pub out: String, + + /// The source of the private keys from which to extract the pubkeys. + #[clap(subcommand)] + pub source: KeySource, } /// The action to perform. @@ -169,6 +180,18 @@ impl Chain { } } +/// Styles for the CLI application. +const fn cli_styles() -> clap::builder::Styles { + clap::builder::Styles::styled() + .usage(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Yellow)))) + .header(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Yellow)))) + .literal(Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)))) + .invalid(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) + .error(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) + .valid(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Green)))) + .placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::White)))) +} + #[cfg(test)] mod tests { use super::Opts; diff --git a/bolt-cli/src/delegation.rs b/bolt-cli/src/commands/delegate.rs similarity index 79% rename from bolt-cli/src/delegation.rs rename to bolt-cli/src/commands/delegate.rs index 239c6871c..e821332ae 100644 --- a/bolt-cli/src/delegation.rs +++ b/bolt-cli/src/commands/delegate.rs @@ -9,16 +9,89 @@ use serde::Serialize; use tracing::{debug, warn}; use crate::{ - cli::{Action, Chain}, - utils::{ + cli::{Action, Chain, DelegateCommand, KeySource}, + common::{ dirk::Dirk, keystore::{keystore_paths, KeystoreError, KeystoreSecret}, + parse_bls_public_key, signing::{ compute_commit_boost_signing_root, compute_domain_from_mask, verify_commit_boost_root, }, + write_to_file, }, }; +impl DelegateCommand { + /// Run the `delegate` command. + pub async fn run(self) -> Result<()> { + match self.source { + KeySource::SecretKeys { secret_keys } => { + let delegatee_pubkey = parse_bls_public_key(&self.delegatee_pubkey)?; + let signed_messages = generate_from_local_keys( + &secret_keys, + delegatee_pubkey, + self.chain, + self.action, + )?; + debug!("Signed {} messages with local keys", signed_messages.len()); + + // Verify signatures + for message in &signed_messages { + verify_message_signature(message, self.chain)?; + } + + write_to_file(&self.out, &signed_messages)?; + println!("Signed delegation messages generated and saved to {}", self.out); + } + KeySource::LocalKeystore { opts } => { + let keystore_secret = KeystoreSecret::from_keystore_options(&opts)?; + let delegatee_pubkey = parse_bls_public_key(&self.delegatee_pubkey)?; + let signed_messages = generate_from_keystore( + &opts.path, + keystore_secret, + delegatee_pubkey, + self.chain, + self.action, + )?; + debug!("Signed {} messages with keystore", signed_messages.len()); + + // Verify signatures + for message in &signed_messages { + verify_message_signature(message, self.chain)?; + } + + write_to_file(&self.out, &signed_messages)?; + println!("Signed delegation messages generated and saved to {}", self.out); + } + KeySource::Dirk { opts } => { + let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; + + let delegatee_pubkey = parse_bls_public_key(&self.delegatee_pubkey)?; + let signed_messages = generate_from_dirk( + &mut dirk, + delegatee_pubkey, + opts.wallet_path, + opts.passphrases, + self.chain, + self.action, + ) + .await?; + debug!("Signed {} messages with Dirk", signed_messages.len()); + + // Verify signatures + for message in &signed_messages { + verify_message_signature(message, self.chain)?; + } + + write_to_file(&self.out, &signed_messages)?; + println!("Signed delegation messages generated and saved to {}", self.out); + } + } + + Ok(()) + } +} + /// Generate signed delegations/revocations using local BLS private keys /// /// - Use the provided private keys from either CLI or env variable @@ -28,7 +101,7 @@ use crate::{ pub fn generate_from_local_keys( secret_keys: &[String], delegatee_pubkey: BlsPublicKey, - chain: &Chain, + chain: Chain, action: Action, ) -> Result> { let mut signed_messages = Vec::with_capacity(secret_keys.len()); @@ -39,14 +112,14 @@ pub fn generate_from_local_keys( match action { Action::Delegate => { let message = DelegationMessage::new(sk.public_key(), delegatee_pubkey.clone()); - let signing_root = compute_commit_boost_signing_root(message.digest(), chain)?; + let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?; let signature = sk.sign(signing_root.0.as_ref()); let signed = SignedDelegation { message, signature }; signed_messages.push(SignedMessage::Delegation(signed)) } Action::Revoke => { let message = RevocationMessage::new(sk.public_key(), delegatee_pubkey.clone()); - let signing_root = compute_commit_boost_signing_root(message.digest(), chain)?; + let signing_root = compute_commit_boost_signing_root(message.digest(), &chain)?; let signature = sk.sign(signing_root.0.as_ref()); let signed = SignedRevocation { message, signature }; signed_messages.push(SignedMessage::Revocation(signed)); @@ -288,7 +361,7 @@ pub fn verify_message_signature(message: &SignedMessage, chain: Chain) -> Result mod tests { use crate::{ cli::{Action, Chain}, - utils::{keystore::KeystoreSecret, parse_bls_public_key}, + common::{keystore::KeystoreSecret, parse_bls_public_key}, }; use super::{generate_from_keystore, verify_message_signature}; diff --git a/bolt-cli/src/commands/mod.rs b/bolt-cli/src/commands/mod.rs new file mode 100644 index 000000000..e2467b1c9 --- /dev/null +++ b/bolt-cli/src/commands/mod.rs @@ -0,0 +1,11 @@ +/// Module for the bolt `delegate` command to create +/// signed delegation and revocation messages. +pub mod delegate; + +/// Module for the bolt `pubkeys` command to generate +/// lists of public keys from different key sources. +pub mod pubkeys; + +/// Module for the bolt `send` command to create and +/// broadcast preconfirmations in Bolt. +pub mod send; diff --git a/bolt-cli/src/pubkeys.rs b/bolt-cli/src/commands/pubkeys.rs similarity index 54% rename from bolt-cli/src/pubkeys.rs rename to bolt-cli/src/commands/pubkeys.rs index c06eef8de..ba10ff38a 100644 --- a/bolt-cli/src/pubkeys.rs +++ b/bolt-cli/src/commands/pubkeys.rs @@ -3,10 +3,47 @@ use eyre::Result; use lighthouse_eth2_keystore::Keystore; use crate::{ + cli::{KeySource, PubkeysCommand}, + common::{ + dirk::Dirk, + keystore::{keystore_paths, KeystoreError, KeystoreSecret}, + write_to_file, + }, pb::eth2_signer_api::Account, - utils::keystore::{keystore_paths, KeystoreError, KeystoreSecret}, }; +impl PubkeysCommand { + pub async fn run(self) -> Result<()> { + match self.source { + KeySource::SecretKeys { secret_keys } => { + let pubkeys = list_from_local_keys(&secret_keys)?; + + write_to_file(&self.out, &pubkeys)?; + println!("Pubkeys generated and saved to {}", self.out); + } + KeySource::LocalKeystore { opts } => { + let keystore_secret = KeystoreSecret::from_keystore_options(&opts)?; + let pubkeys = list_from_keystore(&opts.path, keystore_secret)?; + + write_to_file(&self.out, &pubkeys)?; + println!("Pubkeys generated and saved to {}", self.out); + } + KeySource::Dirk { opts } => { + // Note: we don't need to unlock wallets to list pubkeys + let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; + + let accounts = dirk.list_accounts(opts.wallet_path).await?; + let pubkeys = list_from_dirk_accounts(&accounts)?; + + write_to_file(&self.out, &pubkeys)?; + println!("Pubkeys generated and saved to {}", self.out); + } + } + + Ok(()) + } +} + /// Derive public keys from the provided secret keys. pub fn list_from_local_keys(secret_keys: &[String]) -> Result> { let mut pubkeys = Vec::with_capacity(secret_keys.len()); diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/bolt-cli/src/commands/send.rs @@ -0,0 +1 @@ + diff --git a/bolt-cli/src/utils/dirk.rs b/bolt-cli/src/common/dirk.rs similarity index 100% rename from bolt-cli/src/utils/dirk.rs rename to bolt-cli/src/common/dirk.rs diff --git a/bolt-cli/src/utils/keystore.rs b/bolt-cli/src/common/keystore.rs similarity index 100% rename from bolt-cli/src/utils/keystore.rs rename to bolt-cli/src/common/keystore.rs diff --git a/bolt-cli/src/utils/mod.rs b/bolt-cli/src/common/mod.rs similarity index 100% rename from bolt-cli/src/utils/mod.rs rename to bolt-cli/src/common/mod.rs diff --git a/bolt-cli/src/utils/signing.rs b/bolt-cli/src/common/signing.rs similarity index 94% rename from bolt-cli/src/utils/signing.rs rename to bolt-cli/src/common/signing.rs index daee353e1..07d3a6590 100644 --- a/bolt-cli/src/utils/signing.rs +++ b/bolt-cli/src/common/signing.rs @@ -4,7 +4,7 @@ use ethereum_consensus::{ crypto::PublicKey as BlsPublicKey, deneb::{compute_fork_data_root, compute_signing_root, Root}, }; -use eyre::Result; +use eyre::{eyre, Result}; use crate::cli::Chain; @@ -19,7 +19,7 @@ pub fn compute_commit_boost_signing_root(message: [u8; 32], chain: &Chain) -> Re compute_signing_root(&message, compute_domain_from_mask(chain.fork_version())) // Ethereum-consensus uses a different version of alloy so we need to do this cast .map(|r| B256::from_slice(r.to_vec().as_slice())) - .map_err(|e| eyre::eyre!("Failed to compute signing root: {}", e)) + .map_err(|e| eyre!("Failed to compute signing root: {}", e)) } /// Compute the commit boost domain from the fork version @@ -62,6 +62,6 @@ pub fn verify_root( if res == BLST_ERROR::BLST_SUCCESS { Ok(()) } else { - Err(eyre::eyre!("bls verification failed")) + Err(eyre!("bls verification failed")) } } diff --git a/bolt-cli/src/main.rs b/bolt-cli/src/main.rs index 6bdfecfc7..da5e9076f 100644 --- a/bolt-cli/src/main.rs +++ b/bolt-cli/src/main.rs @@ -1,20 +1,15 @@ use clap::Parser; use eyre::Result; -/// CLI commands and options. +/// CLI command definitions and options. mod cli; -use cli::{Commands, KeySource, Opts}; +use cli::{Commands, Opts}; -/// Module for generating delegation/revocation messages for BLS keys. -mod delegation; +/// CLI command implementations. +mod commands; -/// Module for listing BLS pubkeys from various sources. -mod pubkeys; - -/// Utility functions and helpers for the CLI. -mod utils; -use tracing::debug; -use utils::{dirk::Dirk, keystore::KeystoreSecret, parse_bls_public_key, write_to_file}; +/// Shared utility functions and helpers for the CLI. +mod common; /// Protocol Buffers definitions generated by `prost`. mod pb; @@ -23,99 +18,13 @@ mod pb; async fn main() -> Result<()> { let _ = dotenvy::dotenv(); let _ = tracing_subscriber::fmt::try_init(); + let _ = rustls::crypto::ring::default_provider().install_default(); let cli = Opts::parse(); - // Init the default rustls provider for Dirk - let _ = rustls::crypto::ring::default_provider().install_default(); - match cli.command { - Commands::Delegate { delegatee_pubkey, out, chain, source, action } => match source { - KeySource::SecretKeys { secret_keys } => { - let delegatee_pubkey = parse_bls_public_key(&delegatee_pubkey)?; - let signed_messages = delegation::generate_from_local_keys( - &secret_keys, - delegatee_pubkey, - &chain, - action, - )?; - - // Verify signatures - for message in &signed_messages { - delegation::verify_message_signature(message, chain)?; - } - - write_to_file(&out, &signed_messages)?; - println!("Signed delegation messages generated and saved to {}", out); - } - KeySource::LocalKeystore { opts } => { - let keystore_secret = KeystoreSecret::from_keystore_options(&opts)?; - let delegatee_pubkey = parse_bls_public_key(&delegatee_pubkey)?; - let signed_messages = delegation::generate_from_keystore( - &opts.path, - keystore_secret, - delegatee_pubkey, - chain, - action, - )?; - - // Verify signatures - for message in &signed_messages { - delegation::verify_message_signature(message, chain)?; - } - - write_to_file(&out, &signed_messages)?; - println!("Signed delegation messages generated and saved to {}", out); - } - KeySource::Dirk { opts } => { - let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; - let delegatee_pubkey = parse_bls_public_key(&delegatee_pubkey)?; - - let signed_messages = delegation::generate_from_dirk( - &mut dirk, - delegatee_pubkey, - opts.wallet_path, - opts.passphrases, - chain, - action, - ) - .await?; - debug!("Signed {} messages with Dirk", signed_messages.len()); - - // Verify signatures - for message in &signed_messages { - delegation::verify_message_signature(message, chain)?; - } - - write_to_file(&out, &signed_messages)?; - println!("Signed delegation messages generated and saved to {}", out); - } - }, - - Commands::Pubkeys { source, out } => match source { - KeySource::SecretKeys { secret_keys } => { - let pubkeys = pubkeys::list_from_local_keys(&secret_keys)?; - - write_to_file(&out, &pubkeys)?; - println!("Pubkeys generated and saved to {}", out); - } - KeySource::LocalKeystore { opts } => { - let keystore_secret = KeystoreSecret::from_keystore_options(&opts)?; - let pubkeys = pubkeys::list_from_keystore(&opts.path, keystore_secret)?; - - write_to_file(&out, &pubkeys)?; - println!("Pubkeys generated and saved to {}", out); - } - KeySource::Dirk { opts } => { - // Note: we don't need to unlock wallets to list pubkeys - let mut dirk = Dirk::connect(opts.url, opts.tls_credentials).await?; - let accounts = dirk.list_accounts(opts.wallet_path).await?; - let pubkeys = pubkeys::list_from_dirk_accounts(&accounts)?; - - write_to_file(&out, &pubkeys)?; - println!("Pubkeys generated and saved to {}", out); - } - }, + Commands::Delegate(cmd) => cmd.run().await?, + Commands::Pubkeys(cmd) => cmd.run().await?, } Ok(()) From 066f3c6976ceacc86664bb76d08ccd38401c2d57 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:42:39 +0200 Subject: [PATCH 02/11] chore: stash --- bolt-cli/Cargo.toml | 2 +- bolt-cli/src/cli.rs | 15 +++++++++++++++ bolt-cli/src/commands/send.rs | 9 +++++++++ bolt-cli/src/main.rs | 1 + 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bolt-cli/Cargo.toml b/bolt-cli/Cargo.toml index 5898e813c..747b99575 100644 --- a/bolt-cli/Cargo.toml +++ b/bolt-cli/Cargo.toml @@ -35,10 +35,10 @@ thiserror = "1.0" hex = "0.4.3" tracing = "0.1.40" tracing-subscriber = "0.3.18" +reqwest = "0.12.8" [dev-dependencies] tempfile = "3.13.0" -reqwest = "0.12.8" [build-dependencies] tonic-build = "0.12.3" diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 8458acd20..ca2019436 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -22,6 +22,9 @@ pub enum Commands { /// Output a list of pubkeys in JSON format. Pubkeys(PubkeysCommand), + + /// Send a preconfirmation request to a Bolt proposer. + Send(SendCommand), } /// Command for generating BLS delegation or revocation messages. @@ -61,6 +64,18 @@ pub struct PubkeysCommand { pub source: KeySource, } +/// Command for sending a preconfirmation request to a Bolt proposer. +#[derive(Debug, Clone, Deserialize, Parser)] +pub struct SendCommand { + /// Bolt Sidecar RPC URL to send requests to. + #[clap(long, env = "SIDECAR_RPC_URL")] + pub sidecar_rpc_url: String, + + /// The private key to sign the transaction with. + #[clap(long, env = "PRIVATE_KEY", hide_env_values = true)] + pub private_key: String, +} + /// The action to perform. #[derive(Debug, Clone, ValueEnum, Deserialize)] #[clap(rename_all = "kebab_case")] diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index 8b1378917..4ead19080 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -1 +1,10 @@ +use eyre::Result; +use crate::cli::SendCommand; + +impl SendCommand { + /// Run the `send` command. + pub async fn run(self) -> Result<()> { + Ok(()) + } +} diff --git a/bolt-cli/src/main.rs b/bolt-cli/src/main.rs index da5e9076f..b7ea0b910 100644 --- a/bolt-cli/src/main.rs +++ b/bolt-cli/src/main.rs @@ -25,6 +25,7 @@ async fn main() -> Result<()> { match cli.command { Commands::Delegate(cmd) => cmd.run().await?, Commands::Pubkeys(cmd) => cmd.run().await?, + Commands::Send(cmd) => cmd.run().await?, } Ok(()) From 178656bd0541cabc962e053a5fc111af92859771 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:27:18 +0200 Subject: [PATCH 03/11] feat: send preconfirmation requests to the rpc --- bolt-cli/Cargo.lock | 907 +++++++++++++++++++++++++++- bolt-cli/Cargo.toml | 4 +- bolt-cli/src/cli.rs | 30 +- bolt-cli/src/commands/delegate.rs | 6 +- bolt-cli/src/commands/send.rs | 134 +++- bolt-cli/src/common/bolt_manager.rs | 155 +++++ bolt-cli/src/common/dirk.rs | 2 +- bolt-cli/src/common/mod.rs | 3 + bolt-cli/src/common/signing.rs | 2 +- 9 files changed, 1219 insertions(+), 24 deletions(-) create mode 100644 bolt-cli/src/common/bolt_manager.rs diff --git a/bolt-cli/Cargo.lock b/bolt-cli/Cargo.lock index e1c9d6df7..5621db297 100644 --- a/bolt-cli/Cargo.lock +++ b/bolt-cli/Cargo.lock @@ -30,6 +30,19 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -39,6 +52,228 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "alloy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8ebf106e84a1c37f86244df7da0c7587e697b71a0d565cce079449b85ac6f8" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-core", + "alloy-eips", + "alloy-genesis", + "alloy-network", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", +] + +[[package]] +name = "alloy-chains" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4932d790c723181807738cf1ac68198ab581cd699545b155601332541ee47bd" +dependencies = [ + "alloy-primitives 0.8.9", + "num_enum", + "strum", +] + +[[package]] +name = "alloy-consensus" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed961a48297c732a5d97ee321aa8bb5009ecadbcb077d8bec90cb54e651629" +dependencies = [ + "alloy-eips", + "alloy-primitives 0.8.9", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "c-kzg", + "derive_more 1.0.0", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460ab80ce4bda1c80bcf96fe7460520476f2c7b734581c6567fac2708e2a60ef" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives 0.8.9", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror", +] + +[[package]] +name = "alloy-core" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cce174ca699ddee3bfb2ec1fbd99ad7efd05eca20c5c888d8320db41f7e8f04" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives 0.8.9", + "alloy-rlp", + "alloy-sol-types", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5647fce5a168f9630f935bf7821c4207b1755184edaeba783cb4e11d35058484" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.9", + "alloy-sol-type-parser", + "alloy-sol-types", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow", +] + +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-rlp", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ffc577390ce50234e02d841214b3dc0bea6aaaae8e04bbf3cb82e9a45da9eb" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-rlp", + "derive_more 1.0.0", + "k256 0.13.4", + "serde", +] + +[[package]] +name = "alloy-eips" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b69e06cf9c37be824b9d26d6d101114fdde6af0c87de2828b414c05c4b3daa71" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives 0.8.9", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more 1.0.0", + "once_cell", + "serde", + "sha2 0.10.8", +] + +[[package]] +name = "alloy-genesis" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde15e14944a88bd6a57d325e9a49b75558746fe16aaccc79713ae50a6a9574c" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b5671117c38b1c2306891f97ad3828d85487087f54ebe2c7591a055ea5bcea7" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af5979e0d5a7bf9c7eb79749121e8256e59021af611322aee56e77e20776b4b3" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-sol-types", + "serde", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "204237129086ce5dc17a58025e93739b01b45313841f98fa339eb1d780511e57" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives 0.8.9", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "thiserror", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514f70ee2a953db21631cd817b13a1571474ec77ddc03d47616d5e8203489fde" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives 0.8.9", + "alloy-serde", + "serde", +] + [[package]] name = "alloy-primitives" version = "0.7.7" @@ -89,16 +324,172 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-provider" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4814d141ede360bb6cd1b4b064f1aab9de391e7c4d0d4d50ac89ea4bc1e25fbd" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives 0.8.9", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru", + "parking_lot", + "pin-project", + "reqwest", + "schnellru", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-pubsub" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba46eb69ddf7a9925b81f15229cb74658e6eebe5dd30a5b74e2cd040380573" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives 0.8.9", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.1", + "tracing", +] + [[package]] name = "alloy-rlp" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ + "alloy-rlp-derive", "arrayvec", "bytes", ] +[[package]] +name = "alloy-rlp-derive" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + +[[package]] +name = "alloy-rpc-client" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc2bd1e7403463a5f2c61e955bcc9d3072b63aa177442b0f9aa6a6d22a941e3" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives 0.8.9", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.1", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea9bf1abdd506f985a53533f5ac01296bcd6102c5e139bbc5d40bc468d2c916" +dependencies = [ + "alloy-primitives 0.8.9", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "886d22d41992287a235af2f3af4299b5ced2bcafb81eb835572ad35747476946" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives 0.8.9", + "alloy-rlp", + "alloy-serde", + "derive_more 1.0.0", + "serde", + "strum", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b034779a4850b4b03f5be5ea674a1cf7d746b2da762b34d1860ab45e48ca27" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives 0.8.9", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "derive_more 1.0.0", + "itertools 0.13.0", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028e72eaa9703e4882344983cfe7636ce06d8cce104a78ea62fd19b46659efc4" +dependencies = [ + "alloy-primitives 0.8.9", + "serde", + "serde_json", +] + [[package]] name = "alloy-signer" version = "0.5.3" @@ -113,6 +504,167 @@ dependencies = [ "thiserror", ] +[[package]] +name = "alloy-signer-local" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6614f02fc1d5b079b2a4a5320018317b506fd0a6d67c1fd5542a71201724986c" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives 0.8.9", + "alloy-signer", + "async-trait", + "k256 0.13.4", + "rand", + "thiserror", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0900b83f4ee1f45c640ceee596afbc118051921b9438fdb5a3175c1a7e05f8b" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.82", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41b1e78dde06b5e12e6702fa8c1d30621bf07728ba75b801fb801c9c6a0ba10" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.6.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.82", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91dc311a561a306664393407b88d3e53ae58581624128afd8a15faa5de3627dc" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.82", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45d1fbee9e698f3ba176b6e7a145f4aefe6d2b746b611e8bb246fe11a0e9f6c4" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086f41bc6ebcd8cb15f38ba20e47be38dd03692149681ce8061c35d960dbf850" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.9", + "alloy-sol-macro", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be77579633ebbc1266ae6fd7694f75c408beb1aeb6865d0b18f22893c265a061" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.1", + "futures-util", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower 0.5.1", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-transport-http" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd1a5d0827939847983b46f2f79510361f901dc82f8e3c38ac7397af142c6e" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower 0.5.1", + "tracing", + "url", +] + +[[package]] +name = "alloy-transport-ipc" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8073d1186bfeeb8fbdd1292b6f1a0731f3aed8e21e1463905abfae0b96a887a6" +dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project", + "serde_json", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "alloy-transport-ws" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f27837bb4a1d6c83a28231c94493e814882f0e9058648a97e908a5f3fc9fcf" +dependencies = [ + "alloy-pubsub", + "alloy-transport", + "futures", + "http", + "rustls", + "serde_json", + "tokio", + "tokio-tungstenite", + "tracing", + "ws_stream_wasm", +] + [[package]] name = "anstream" version = "0.6.15" @@ -346,6 +898,17 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "async_io_stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +dependencies = [ + "futures", + "pharos", + "rustc_version 0.4.1", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -488,6 +1051,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bindgen" version = "0.69.5" @@ -602,8 +1171,7 @@ dependencies = [ name = "bolt" version = "0.1.0" dependencies = [ - "alloy-primitives 0.8.9", - "alloy-signer", + "alloy", "blst", "clap", "dotenvy", @@ -614,6 +1182,7 @@ dependencies = [ "inquire", "nu-ansi-term 0.50.1", "prost", + "rand", "reqwest", "rustls", "serde", @@ -835,6 +1404,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crossterm" version = "0.25.0" @@ -919,6 +1494,20 @@ dependencies = [ "cipher", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -1022,6 +1611,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "dotenvy" version = "0.15.7" @@ -1389,6 +1984,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -1396,6 +2006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1404,6 +2015,34 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1422,12 +2061,24 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "fuzzy-matcher" version = "0.3.7" @@ -1527,13 +2178,28 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", + "serde", ] [[package]] @@ -1795,6 +2461,7 @@ checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.0", + "serde", ] [[package]] @@ -1824,6 +2491,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "interprocess" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" +dependencies = [ + "doctest-file", + "futures-core", + "libc", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1987,6 +2669,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.0", +] + [[package]] name = "matchit" version = "0.7.3" @@ -2224,6 +2915,26 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "object" version = "0.36.5" @@ -2386,6 +3097,16 @@ dependencies = [ "indexmap 2.6.0", ] +[[package]] +name = "pharos" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" +dependencies = [ + "futures", + "rustc_version 0.4.1", +] + [[package]] name = "pin-project" version = "1.1.6" @@ -2519,6 +3240,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "proc-macro2" version = "1.0.87" @@ -2631,6 +3374,7 @@ dependencies = [ "libc", "rand_chacha", "rand_core", + "serde", ] [[package]] @@ -2661,6 +3405,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.5.7" @@ -2973,6 +3723,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "schnellru" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -3066,6 +3827,12 @@ dependencies = [ "pest", ] +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" + [[package]] name = "serde" version = "1.0.210" @@ -3133,6 +3900,17 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3333,6 +4111,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.82", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3361,6 +4161,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5e0c2ea8db64b2898b62ea2fbd60204ca95e0b2c6bdf53ff768bbe916fbe4d" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -3550,6 +4362,23 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", ] [[package]] @@ -3757,6 +4586,26 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" @@ -3855,6 +4704,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3980,6 +4835,20 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasmtimer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7ed9d8b15c7fb594d72bfb4b5a276f3d2029333cd93a932f376f5937f6f80ee" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.72" @@ -3990,6 +4859,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" @@ -4002,6 +4880,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -4211,6 +5095,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "ws_stream_wasm" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5" +dependencies = [ + "async_io_stream", + "futures", + "js-sys", + "log", + "pharos", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/bolt-cli/Cargo.toml b/bolt-cli/Cargo.toml index 747b99575..d40c45d09 100644 --- a/bolt-cli/Cargo.toml +++ b/bolt-cli/Cargo.toml @@ -25,8 +25,7 @@ blst = "0.3.12" # ethereum ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus", rev = "cf3c404" } lighthouse_eth2_keystore = { package = "eth2_keystore", git = "https://github.com/sigp/lighthouse", rev = "a87f19d" } -alloy-primitives = "0.8.9" -alloy-signer = "0.5.2" +alloy = { version = "0.5.2", features = ["full"] } # utils dotenvy = "0.15.7" @@ -36,6 +35,7 @@ hex = "0.4.3" tracing = "0.1.40" tracing-subscriber = "0.3.18" reqwest = "0.12.8" +rand = "0.8.5" [dev-dependencies] tempfile = "3.13.0" diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index ca2019436..287c14465 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -2,12 +2,12 @@ use clap::{ builder::styling::{AnsiColor, Color, Style}, Parser, Subcommand, ValueEnum, }; -use serde::Deserialize; +use reqwest::Url; use crate::common::keystore::DEFAULT_KEYSTORE_PASSWORD; /// `bolt` is a CLI tool to interact with Bolt Protocol ✨ -#[derive(Parser, Debug, Clone, Deserialize)] +#[derive(Parser, Debug, Clone)] #[command(author, version, styles = cli_styles(), about, arg_required_else_help(true))] pub struct Opts { /// The subcommand to run. @@ -15,7 +15,7 @@ pub struct Opts { pub command: Commands, } -#[derive(Subcommand, Debug, Clone, Deserialize)] +#[derive(Subcommand, Debug, Clone)] pub enum Commands { /// Generate BLS delegation or revocation messages. Delegate(DelegateCommand), @@ -28,7 +28,7 @@ pub enum Commands { } /// Command for generating BLS delegation or revocation messages. -#[derive(Debug, Clone, Deserialize, Parser)] +#[derive(Debug, Clone, Parser)] pub struct DelegateCommand { /// The BLS public key to which the delegation message should be signed. #[clap(long, env = "DELEGATEE_PUBKEY")] @@ -53,7 +53,7 @@ pub struct DelegateCommand { } /// Command for outputting a list of pubkeys in JSON format. -#[derive(Debug, Clone, Deserialize, Parser)] +#[derive(Debug, Clone, Parser)] pub struct PubkeysCommand { /// The output file for the pubkeys. #[clap(long, env = "OUTPUT_FILE_PATH", default_value = "pubkeys.json")] @@ -65,11 +65,11 @@ pub struct PubkeysCommand { } /// Command for sending a preconfirmation request to a Bolt proposer. -#[derive(Debug, Clone, Deserialize, Parser)] +#[derive(Debug, Clone, Parser)] pub struct SendCommand { - /// Bolt Sidecar RPC URL to send requests to. - #[clap(long, env = "SIDECAR_RPC_URL")] - pub sidecar_rpc_url: String, + /// Bolt RPC URL to send requests to and fetch lookahead info from. + #[clap(long, env = "BOLT_RPC_URL", default_value = "http://135.181.191.125:58017")] + pub bolt_rpc_url: Url, /// The private key to sign the transaction with. #[clap(long, env = "PRIVATE_KEY", hide_env_values = true)] @@ -77,7 +77,7 @@ pub struct SendCommand { } /// The action to perform. -#[derive(Debug, Clone, ValueEnum, Deserialize)] +#[derive(Debug, Clone, ValueEnum)] #[clap(rename_all = "kebab_case")] pub enum Action { /// Create a delegation message. @@ -86,7 +86,7 @@ pub enum Action { Revoke, } -#[derive(Debug, Clone, Parser, Deserialize)] +#[derive(Debug, Clone, Parser)] pub enum KeySource { /// Use local secret keys to generate the signed messages. SecretKeys { @@ -112,7 +112,7 @@ pub enum KeySource { } /// Options for reading a keystore folder. -#[derive(Debug, Clone, Deserialize, Parser)] +#[derive(Debug, Clone, Parser)] pub struct LocalKeystoreOpts { /// The path to the keystore file. #[clap(long, env = "KEYSTORE_PATH", default_value = "validators")] @@ -139,7 +139,7 @@ pub struct LocalKeystoreOpts { } /// Options for connecting to a DIRK keystore. -#[derive(Debug, Clone, Deserialize, Parser)] +#[derive(Debug, Clone, Parser)] pub struct DirkOpts { /// The URL of the DIRK keystore. #[clap(long, env = "DIRK_URL")] @@ -160,7 +160,7 @@ pub struct DirkOpts { } /// TLS credentials for connecting to a remote server. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Parser)] +#[derive(Debug, Clone, PartialEq, Eq, Parser)] pub struct TlsCredentials { /// Path to the client certificate file. (.crt) #[clap(long, env = "CLIENT_CERT_PATH")] @@ -174,7 +174,7 @@ pub struct TlsCredentials { } /// Supported chains for the CLI -#[derive(Debug, Clone, Copy, ValueEnum, Deserialize)] +#[derive(Debug, Clone, Copy, ValueEnum)] #[clap(rename_all = "kebab_case")] pub enum Chain { Mainnet, diff --git a/bolt-cli/src/commands/delegate.rs b/bolt-cli/src/commands/delegate.rs index e821332ae..4f1d9721d 100644 --- a/bolt-cli/src/commands/delegate.rs +++ b/bolt-cli/src/commands/delegate.rs @@ -1,5 +1,7 @@ -use alloy_primitives::B256; -use alloy_signer::k256::sha2::{Digest, Sha256}; +use alloy::{ + primitives::B256, + signers::k256::sha2::{Digest, Sha256}, +}; use ethereum_consensus::crypto::{ PublicKey as BlsPublicKey, SecretKey as BlsSecretKey, Signature as BlsSignature, }; diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index 4ead19080..ecfc53134 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -1,10 +1,142 @@ -use eyre::Result; +use alloy::{ + eips::eip2718::Encodable2718, + network::{EthereumWallet, TransactionBuilder}, + primitives::{keccak256, B256, U256}, + providers::{ProviderBuilder, SendableTx}, + rpc::types::TransactionRequest, + signers::{local::PrivateKeySigner, Signer}, +}; +use eyre::{bail, Context, Result}; +use rand::Rng; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tracing::info; use crate::cli::SendCommand; +/// Path to the lookahead endpoint on the Bolt RPC server. +const BOLT_LOOKAHEAD_PATH: &str = "proposers/lookahead?activeOnly=true&futureOnly=true"; + impl SendCommand { /// Run the `send` command. pub async fn run(self) -> Result<()> { + let wallet: PrivateKeySigner = self.private_key.parse().wrap_err("invalid private key")?; + let transaction_signer = EthereumWallet::from(wallet.clone()); + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(transaction_signer) + .on_http(self.bolt_rpc_url.clone()); + + // Fetch the lookahead info from the Bolt RPC server + let lookahead_url = self.bolt_rpc_url.join(BOLT_LOOKAHEAD_PATH)?; + let lookahead_res = reqwest::get(lookahead_url).await?.json::>().await?; + if lookahead_res.is_empty() { + println!("no bolt proposer found in the lookahead, try again later 🥲"); + return Ok(()); + } + + // Extract the next preconfirmer slot from the lookahead info + let next_preconfirmer_slot = lookahead_res[0].slot; + info!("Next preconfirmer slot: {}", next_preconfirmer_slot); + + // generate a simple self-transfer of ETH + let random_data = rand::thread_rng().gen::<[u8; 32]>(); + let req = TransactionRequest::default() + .with_to(wallet.address()) + .with_value(U256::from(100_000)) + .with_input(random_data); + + let raw_tx = match provider.fill(req).await? { + SendableTx::Builder(_) => bail!("expected a raw transaction"), + SendableTx::Envelope(raw) => raw.encoded_2718(), + }; + let tx_hash = B256::from(keccak256(&raw_tx)); + + send_rpc_request( + vec![hex::encode(&raw_tx)], + vec![tx_hash], + next_preconfirmer_slot, + self.bolt_rpc_url, + &wallet, + ) + .await?; + Ok(()) } } + +async fn send_rpc_request( + txs_rlp: Vec, + tx_hashes: Vec, + target_slot: u64, + target_sidecar_url: Url, + wallet: &PrivateKeySigner, +) -> Result<()> { + let request = prepare_rpc_request( + "bolt_requestInclusion", + serde_json::json!({ + "slot": target_slot, + "txs": txs_rlp, + }), + ); + + info!(?tx_hashes, target_slot, %target_sidecar_url); + let signature = sign_request(tx_hashes, target_slot, wallet).await?; + + let response = reqwest::Client::new() + .post(target_sidecar_url) + .header("content-type", "application/json") + .header("x-bolt-signature", signature) + .body(serde_json::to_string(&request)?) + .send() + .await?; + + let response = response.text().await?; + + // strip out long series of zeros in the response (to avoid spamming blob contents) + let response = response.replace(&"0".repeat(32), ".").replace(&".".repeat(4), ""); + info!("Response: {:?}", response); + Ok(()) +} + +async fn sign_request( + tx_hashes: Vec, + target_slot: u64, + wallet: &PrivateKeySigner, +) -> eyre::Result { + let digest = { + let mut data = Vec::new(); + let hashes = tx_hashes.iter().map(|hash| hash.as_slice()).collect::>().concat(); + data.extend_from_slice(&hashes); + data.extend_from_slice(target_slot.to_le_bytes().as_slice()); + keccak256(data) + }; + + let signature = hex::encode(wallet.sign_hash(&digest).await?.as_bytes()); + + Ok(format!("{}:0x{}", wallet.address(), signature)) +} + +fn prepare_rpc_request(method: &str, params: Value) -> Value { + serde_json::json!({ + "id": "1", + "jsonrpc": "2.0", + "method": method, + "params": vec![params], + }) +} + +/// Info about a specific slot in the beacon chain lookahead. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LookaheadSlot { + /// Slot number in the beacon chain + pub slot: u64, + /// Validator index that will propose in this slot + pub validator_index: u64, + /// Validator pubkey that will propose in this slot + pub validator_pubkey: String, + /// Optional URL of the Bolt sidecar associated with the proposer + pub sidecar_url: Option, +} diff --git a/bolt-cli/src/common/bolt_manager.rs b/bolt-cli/src/common/bolt_manager.rs new file mode 100644 index 000000000..78550494b --- /dev/null +++ b/bolt-cli/src/common/bolt_manager.rs @@ -0,0 +1,155 @@ +use std::str::FromStr; + +use alloy::{ + contract::{Error as ContractError, Result as ContractResult}, + primitives::{Address, Bytes, B256}, + providers::{ProviderBuilder, RootProvider}, + sol, + sol_types::{Error as SolError, SolInterface}, + transports::{http::Http, TransportError}, +}; +use reqwest::{Client, Url}; +use serde::Serialize; + +use BoltManagerContract::{BoltManagerContractErrors, BoltManagerContractInstance, ProposerStatus}; + +/// Bolt Manager contract bindings. +#[derive(Debug, Clone)] +pub struct BoltManager(BoltManagerContractInstance, RootProvider>>); + +impl BoltManager { + /// Creates a new BoltManager instance. + pub fn new>(execution_client_url: U, manager_address: Address) -> Self { + let provider = ProviderBuilder::new().on_http(execution_client_url.into()); + let manager = BoltManagerContract::new(manager_address, provider); + + Self(manager) + } + + /// Gets the sidecar RPC URL for a given validator index. + /// + /// Returns Ok(None) if the operator is not found in the registry. + pub async fn get_sidecar_rpc_url_for_validator( + &self, + pubkey_hash: B256, + ) -> ContractResult> { + let registrant = self.get_proposer_status(pubkey_hash).await?; + Ok(registrant.and_then(|r| if r.active { Some(r.operatorRPC) } else { None })) + } + + /// Gets the proposer status for a given pubkeyhash. + /// + /// Returns Ok(None) if the proposer is not found in the registry. + pub async fn get_proposer_status( + &self, + pubkey_hash: B256, + ) -> ContractResult> { + let returndata = self.0.getProposerStatus(pubkey_hash).call().await; + + // TODO: clean this after https://github.com/alloy-rs/alloy/issues/787 is merged + let error = match returndata.map(|data| data._0) { + Ok(proposer) => return Ok(Some(proposer)), + Err(error) => match error { + ContractError::TransportError(TransportError::ErrorResp(err)) => { + let data = err.data.unwrap_or_default(); + let data = data.get().trim_matches('"'); + let data = Bytes::from_str(data).unwrap_or_default(); + + BoltManagerContractErrors::abi_decode(&data, true)? + } + e => return Err(e), + }, + }; + + if matches!(error, BoltManagerContractErrors::ValidatorDoesNotExist(_)) { + Ok(None) + } else { + Err(SolError::custom(format!( + "unexpected Solidity error selector: {:?}", + error.selector() + )) + .into()) + } + } +} + +sol! { + #[sol(rpc)] + interface BoltManagerContract { + #[derive(Debug, Default, Serialize)] + struct ProposerStatus { + bytes32 pubkeyHash; + bool active; + address operator; + string operatorRPC; + address[] collaterals; + uint256[] amounts; + } + + function getProposerStatus(bytes32 pubkeyHash) external view returns (ProposerStatus memory); + + error InvalidQuery(); + error ValidatorDoesNotExist(); + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use alloy::primitives::U256; + + use super::*; + + #[tokio::test] + async fn test_get_operators_helder() -> eyre::Result<()> { + let registry = BoltManager::new( + Url::parse("http://remotebeast:4485")?, + Address::from_str("0xdF11D829eeC4C192774F3Ec171D822f6Cb4C14d9")?, + ); + + let sample_pubkey = B256::from_str("0xsamplepubkeyhash").expect("invalid pubkey"); + + let registrant = registry.get_proposer_status(sample_pubkey).await; + assert!(matches!(registrant, Ok(None))); + + let invalid_pubkey = B256::from_str("0xinvalidsamplepubkeyhash").expect("invalid pubkey"); + let registrant = match registry.get_proposer_status(invalid_pubkey).await { + Ok(Some(registrant)) => registrant, + e => panic!("unexpected error reading from registry: {:?}", e), + }; + + let expected = ProposerStatus { + pubkeyHash: sample_pubkey, + active: true, + operator: Address::from_str("0xad3cd1b81c80f4a495d6552ae6423508492a27f8")?, + operatorRPC: "http://sampleoperatorrpc:8000".to_string(), + collaterals: vec![Address::from_str("0xsamplecollateral1")?], + amounts: vec![U256::from(10000000000000000000u128)], + }; + + assert_eq!(registrant.pubkeyHash, expected.pubkeyHash); + assert_eq!(registrant.active, expected.active); + assert_eq!(registrant.operator, expected.operator); + assert_eq!(registrant.operatorRPC, expected.operatorRPC); + assert_eq!(registrant.collaterals, expected.collaterals); + assert_eq!(registrant.amounts, expected.amounts); + + Ok(()) + } + + #[tokio::test] + async fn test_check_validator_helder() -> eyre::Result<()> { + let registry = BoltManager::new( + Url::parse("http://remotebeast:48545")?, + Address::from_str("0xdF11D829eeC4C192774F3Ec171D822f6Cb4C14d9")?, + ); + + let pubkey_hash = B256::from_str("0xsamplepubkeyhash").expect("invalid pubkey"); + let registrant = registry.get_sidecar_rpc_url_for_validator(pubkey_hash).await?; + assert!(registrant.is_some()); + + dbg!(®istrant); + Ok(()) + } +} diff --git a/bolt-cli/src/common/dirk.rs b/bolt-cli/src/common/dirk.rs index dc5e594eb..75148bab8 100644 --- a/bolt-cli/src/common/dirk.rs +++ b/bolt-cli/src/common/dirk.rs @@ -1,6 +1,6 @@ use std::fs; -use alloy_primitives::B256; +use alloy::primitives::B256; use ethereum_consensus::crypto::bls::Signature as BlsSignature; use eyre::{bail, Context, Result}; use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; diff --git a/bolt-cli/src/common/mod.rs b/bolt-cli/src/common/mod.rs index efee3e63e..b152a4614 100644 --- a/bolt-cli/src/common/mod.rs +++ b/bolt-cli/src/common/mod.rs @@ -4,6 +4,9 @@ use ethereum_consensus::crypto::PublicKey as BlsPublicKey; use eyre::{Context, Result}; use serde::Serialize; +/// BoltManager contract bindings. +pub mod bolt_manager; + /// Utilities for working with DIRK remote keystores. pub mod dirk; diff --git a/bolt-cli/src/common/signing.rs b/bolt-cli/src/common/signing.rs index 07d3a6590..59c70a220 100644 --- a/bolt-cli/src/common/signing.rs +++ b/bolt-cli/src/common/signing.rs @@ -1,4 +1,4 @@ -use alloy_primitives::B256; +use alloy::primitives::B256; use blst::{min_pk::Signature, BLST_ERROR}; use ethereum_consensus::{ crypto::PublicKey as BlsPublicKey, From 6477a5b8a9ce6fe19cec4e11552445baa814a998 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:27:57 +0200 Subject: [PATCH 04/11] chore: add sidecar url override --- bolt-cli/src/cli.rs | 7 +++++++ bolt-cli/src/commands/send.rs | 11 ++++++++++- bolt-cli/src/common/bolt_manager.rs | 2 ++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 287c14465..5657fcb96 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -74,6 +74,13 @@ pub struct SendCommand { /// The private key to sign the transaction with. #[clap(long, env = "PRIVATE_KEY", hide_env_values = true)] pub private_key: String, + + /// The Bolt Sidecar URL to send requests to. If provided, this will override + /// the canonical bolt RPC URL and disregard any registration information. + /// + /// This is useful for testing and development purposes. + #[clap(long, env = "OVERRIDE_BOLT_SIDECAR_URL")] + pub override_bolt_sidecar_url: Option, } /// The action to perform. diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index ecfc53134..a076cf474 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -54,11 +54,20 @@ impl SendCommand { }; let tx_hash = B256::from(keccak256(&raw_tx)); + // Note: it's possible for users to override the target sidecar URL + // for testing and development purposes. In most cases, the sidecar will + // reject a request for a slot that it is not responsible for. + let target_url = if let Some(sidecar_url) = self.override_bolt_sidecar_url { + sidecar_url.clone() + } else { + self.bolt_rpc_url.clone() + }; + send_rpc_request( vec![hex::encode(&raw_tx)], vec![tx_hash], next_preconfirmer_slot, - self.bolt_rpc_url, + target_url, &wallet, ) .await?; diff --git a/bolt-cli/src/common/bolt_manager.rs b/bolt-cli/src/common/bolt_manager.rs index 78550494b..ae4e0a720 100644 --- a/bolt-cli/src/common/bolt_manager.rs +++ b/bolt-cli/src/common/bolt_manager.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] // TODO: rm this + use std::str::FromStr; use alloy::{ From 2d116deaf44235ce2b55760858e093705bfa4429 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 14:28:45 +0200 Subject: [PATCH 05/11] chore: rm unused tests --- bolt-cli/src/common/bolt_manager.rs | 61 ----------------------------- 1 file changed, 61 deletions(-) diff --git a/bolt-cli/src/common/bolt_manager.rs b/bolt-cli/src/common/bolt_manager.rs index ae4e0a720..261347ab3 100644 --- a/bolt-cli/src/common/bolt_manager.rs +++ b/bolt-cli/src/common/bolt_manager.rs @@ -94,64 +94,3 @@ sol! { error ValidatorDoesNotExist(); } } - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use alloy::primitives::U256; - - use super::*; - - #[tokio::test] - async fn test_get_operators_helder() -> eyre::Result<()> { - let registry = BoltManager::new( - Url::parse("http://remotebeast:4485")?, - Address::from_str("0xdF11D829eeC4C192774F3Ec171D822f6Cb4C14d9")?, - ); - - let sample_pubkey = B256::from_str("0xsamplepubkeyhash").expect("invalid pubkey"); - - let registrant = registry.get_proposer_status(sample_pubkey).await; - assert!(matches!(registrant, Ok(None))); - - let invalid_pubkey = B256::from_str("0xinvalidsamplepubkeyhash").expect("invalid pubkey"); - let registrant = match registry.get_proposer_status(invalid_pubkey).await { - Ok(Some(registrant)) => registrant, - e => panic!("unexpected error reading from registry: {:?}", e), - }; - - let expected = ProposerStatus { - pubkeyHash: sample_pubkey, - active: true, - operator: Address::from_str("0xad3cd1b81c80f4a495d6552ae6423508492a27f8")?, - operatorRPC: "http://sampleoperatorrpc:8000".to_string(), - collaterals: vec![Address::from_str("0xsamplecollateral1")?], - amounts: vec![U256::from(10000000000000000000u128)], - }; - - assert_eq!(registrant.pubkeyHash, expected.pubkeyHash); - assert_eq!(registrant.active, expected.active); - assert_eq!(registrant.operator, expected.operator); - assert_eq!(registrant.operatorRPC, expected.operatorRPC); - assert_eq!(registrant.collaterals, expected.collaterals); - assert_eq!(registrant.amounts, expected.amounts); - - Ok(()) - } - - #[tokio::test] - async fn test_check_validator_helder() -> eyre::Result<()> { - let registry = BoltManager::new( - Url::parse("http://remotebeast:48545")?, - Address::from_str("0xdF11D829eeC4C192774F3Ec171D822f6Cb4C14d9")?, - ); - - let pubkey_hash = B256::from_str("0xsamplepubkeyhash").expect("invalid pubkey"); - let registrant = registry.get_sidecar_rpc_url_for_validator(pubkey_hash).await?; - assert!(registrant.is_some()); - - dbg!(®istrant); - Ok(()) - } -} From a58f853893ae5c7149f242f31489401220bf13ae Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:01:51 +0200 Subject: [PATCH 06/11] fix: use next proposer slot when overriding rpc --- bolt-cli/src/cli.rs | 2 +- bolt-cli/src/commands/send.rs | 44 +++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 5657fcb96..c222ab4fd 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -68,7 +68,7 @@ pub struct PubkeysCommand { #[derive(Debug, Clone, Parser)] pub struct SendCommand { /// Bolt RPC URL to send requests to and fetch lookahead info from. - #[clap(long, env = "BOLT_RPC_URL", default_value = "http://135.181.191.125:58017")] + #[clap(long, env = "BOLT_RPC_URL", default_value = "http://135.181.191.125:58017/rpc")] pub bolt_rpc_url: Url, /// The private key to sign the transaction with. diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index a076cf474..91ac27a43 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -16,7 +16,7 @@ use tracing::info; use crate::cli::SendCommand; /// Path to the lookahead endpoint on the Bolt RPC server. -const BOLT_LOOKAHEAD_PATH: &str = "proposers/lookahead?activeOnly=true&futureOnly=true"; +const BOLT_LOOKAHEAD_PATH: &str = "proposers/lookahead"; impl SendCommand { /// Run the `send` command. @@ -30,7 +30,22 @@ impl SendCommand { .on_http(self.bolt_rpc_url.clone()); // Fetch the lookahead info from the Bolt RPC server - let lookahead_url = self.bolt_rpc_url.join(BOLT_LOOKAHEAD_PATH)?; + let mut lookahead_url = self.bolt_rpc_url.join(BOLT_LOOKAHEAD_PATH)?; + + // Note: it's possible for users to override the target sidecar URL + // for testing and development purposes. In most cases, the sidecar will + // reject a request for a slot that it is not responsible for. + let target_url = if let Some(sidecar_url) = self.override_bolt_sidecar_url { + // If using the override URL, we don't need to fetch the active proposers only. + // we will set the next slot as the target slot. + sidecar_url.clone() + } else { + // Filter out slots that are not active or in the past, to fetch the next + // active proposer slot. + lookahead_url.set_query(Some("activeOnly=true&futureOnly=true")); + self.bolt_rpc_url.clone() + }; + let lookahead_res = reqwest::get(lookahead_url).await?.json::>().await?; if lookahead_res.is_empty() { println!("no bolt proposer found in the lookahead, try again later 🥲"); @@ -38,8 +53,8 @@ impl SendCommand { } // Extract the next preconfirmer slot from the lookahead info - let next_preconfirmer_slot = lookahead_res[0].slot; - info!("Next preconfirmer slot: {}", next_preconfirmer_slot); + let target_slot = lookahead_res[0].slot; + info!("Target slot: {}", target_slot); // generate a simple self-transfer of ETH let random_data = rand::thread_rng().gen::<[u8; 32]>(); @@ -48,25 +63,16 @@ impl SendCommand { .with_value(U256::from(100_000)) .with_input(random_data); - let raw_tx = match provider.fill(req).await? { + let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { SendableTx::Builder(_) => bail!("expected a raw transaction"), SendableTx::Envelope(raw) => raw.encoded_2718(), }; let tx_hash = B256::from(keccak256(&raw_tx)); - // Note: it's possible for users to override the target sidecar URL - // for testing and development purposes. In most cases, the sidecar will - // reject a request for a slot that it is not responsible for. - let target_url = if let Some(sidecar_url) = self.override_bolt_sidecar_url { - sidecar_url.clone() - } else { - self.bolt_rpc_url.clone() - }; - send_rpc_request( vec![hex::encode(&raw_tx)], vec![tx_hash], - next_preconfirmer_slot, + target_slot, target_url, &wallet, ) @@ -100,7 +106,8 @@ async fn send_rpc_request( .header("x-bolt-signature", signature) .body(serde_json::to_string(&request)?) .send() - .await?; + .await + .wrap_err("failed to send POST request")?; let response = response.text().await?; @@ -144,8 +151,9 @@ pub struct LookaheadSlot { pub slot: u64, /// Validator index that will propose in this slot pub validator_index: u64, - /// Validator pubkey that will propose in this slot - pub validator_pubkey: String, + // TODO: add pubkey back once it's added in the rpc + // /// Validator pubkey that will propose in this slot + // pub validator_pubkey: String, /// Optional URL of the Bolt sidecar associated with the proposer pub sidecar_url: Option, } From 659d43bae9f6f3b69a926e7d9be0e83a91c70138 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:11:08 +0200 Subject: [PATCH 07/11] fix: rm unused deps, smol cleanup --- bolt-cli/Cargo.lock | 252 +++++-------------------------------------- bolt-cli/Cargo.toml | 2 - bolt-cli/src/cli.rs | 15 ++- bolt-cli/src/main.rs | 15 +-- 4 files changed, 41 insertions(+), 243 deletions(-) diff --git a/bolt-cli/Cargo.lock b/bolt-cli/Cargo.lock index 5621db297..2339a5251 100644 --- a/bolt-cli/Cargo.lock +++ b/bolt-cli/Cargo.lock @@ -1018,7 +1018,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -1063,7 +1063,7 @@ version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cexpr", "clang-sys", "itertools 0.10.5", @@ -1095,12 +1095,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.6.0" @@ -1179,8 +1173,6 @@ dependencies = [ "ethereum-consensus", "eyre", "hex", - "inquire", - "nu-ansi-term 0.50.1", "prost", "rand", "reqwest", @@ -1410,31 +1402,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "crossterm" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio 0.8.11", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" -dependencies = [ - "winapi", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -1629,12 +1596,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - [[package]] name = "ecdsa" version = "0.14.8" @@ -2079,24 +2040,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" -[[package]] -name = "fuzzy-matcher" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" -dependencies = [ - "thread_local", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2464,24 +2407,6 @@ dependencies = [ "serde", ] -[[package]] -name = "inquire" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" -dependencies = [ - "bitflags 2.6.0", - "crossterm", - "dyn-clone", - "fuzzy-matcher", - "fxhash", - "newline-converter", - "once_cell", - "tempfile", - "unicode-segmentation", - "unicode-width", -] - [[package]] name = "integer-sqrt" version = "0.1.5" @@ -2632,7 +2557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -2711,18 +2636,6 @@ dependencies = [ "adler2", ] -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - [[package]] name = "mio" version = "1.0.2" @@ -2809,15 +2722,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "newline-converter" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "nom" version = "7.1.3" @@ -2838,15 +2742,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "nu-ansi-term" -version = "0.50.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2962,7 +2857,7 @@ version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.6.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -3052,7 +2947,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3279,7 +3174,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.6.0", + "bitflags", "lazy_static", "num-traits", "rand", @@ -3417,7 +3312,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 2.6.0", + "bitflags", ] [[package]] @@ -3631,7 +3526,7 @@ version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.6.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3786,7 +3681,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -3970,27 +3865,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-mio" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" -dependencies = [ - "libc", - "mio 0.8.11", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -4206,7 +4080,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.6.0", + "bitflags", "core-foundation", "system-configuration-sys", ] @@ -4312,7 +4186,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -4561,7 +4435,7 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ - "nu-ansi-term 0.46.0", + "nu-ansi-term", "sharded-slab", "smallvec", "thread_local", @@ -4657,18 +4531,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -4916,7 +4778,7 @@ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4925,7 +4787,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4935,16 +4797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -4953,7 +4806,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -4962,22 +4815,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -4986,46 +4824,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -5038,48 +4858,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/bolt-cli/Cargo.toml b/bolt-cli/Cargo.toml index d40c45d09..64882c203 100644 --- a/bolt-cli/Cargo.toml +++ b/bolt-cli/Cargo.toml @@ -10,9 +10,7 @@ tokio = { version = "1.41.0", features = ["full"] } # cli clap = { version = "4.5.4", features = ["derive", "env"] } serde = { version = "1.0.197", features = ["derive"] } -inquire = { version = "0.7", features = ["editor"] } serde_json = "1.0.115" -nu-ansi-term = "0.50" # grpc rustls = { version = "0.23.15", features = ["ring"] } diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index c222ab4fd..445b70683 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -12,11 +12,11 @@ use crate::common::keystore::DEFAULT_KEYSTORE_PASSWORD; pub struct Opts { /// The subcommand to run. #[clap(subcommand)] - pub command: Commands, + pub command: Cmd, } #[derive(Subcommand, Debug, Clone)] -pub enum Commands { +pub enum Cmd { /// Generate BLS delegation or revocation messages. Delegate(DelegateCommand), @@ -27,6 +27,17 @@ pub enum Commands { Send(SendCommand), } +impl Cmd { + /// Run the command. + pub async fn run(self) -> eyre::Result<()> { + match self { + Cmd::Delegate(cmd) => cmd.run().await, + Cmd::Pubkeys(cmd) => cmd.run().await, + Cmd::Send(cmd) => cmd.run().await, + } + } +} + /// Command for generating BLS delegation or revocation messages. #[derive(Debug, Clone, Parser)] pub struct DelegateCommand { diff --git a/bolt-cli/src/main.rs b/bolt-cli/src/main.rs index b7ea0b910..dc59ec8ad 100644 --- a/bolt-cli/src/main.rs +++ b/bolt-cli/src/main.rs @@ -1,9 +1,7 @@ use clap::Parser; -use eyre::Result; /// CLI command definitions and options. mod cli; -use cli::{Commands, Opts}; /// CLI command implementations. mod commands; @@ -15,18 +13,13 @@ mod common; mod pb; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> eyre::Result<()> { let _ = dotenvy::dotenv(); let _ = tracing_subscriber::fmt::try_init(); - let _ = rustls::crypto::ring::default_provider().install_default(); - let cli = Opts::parse(); - - match cli.command { - Commands::Delegate(cmd) => cmd.run().await?, - Commands::Pubkeys(cmd) => cmd.run().await?, - Commands::Send(cmd) => cmd.run().await?, + if let Err(err) = rustls::crypto::ring::default_provider().install_default() { + eprintln!("Failed to install default TLS provider: {:?}", err); } - Ok(()) + cli::Opts::parse().command.run().await } From 288ea0b6758d43762aa6f98b3d9c5765222bd9da Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:36:45 +0200 Subject: [PATCH 08/11] feat: send n transactions at once --- bolt-cli/src/cli.rs | 4 ++++ bolt-cli/src/commands/send.rs | 44 ++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 445b70683..42a69eb52 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -92,6 +92,10 @@ pub struct SendCommand { /// This is useful for testing and development purposes. #[clap(long, env = "OVERRIDE_BOLT_SIDECAR_URL")] pub override_bolt_sidecar_url: Option, + + /// How many transactions to send. + #[clap(long, env = "TRANSACTION_COUNT", default_value = "1")] + pub count: u32, } /// The action to perform. diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index 91ac27a43..c598a37b3 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -56,27 +56,29 @@ impl SendCommand { let target_slot = lookahead_res[0].slot; info!("Target slot: {}", target_slot); - // generate a simple self-transfer of ETH - let random_data = rand::thread_rng().gen::<[u8; 32]>(); - let req = TransactionRequest::default() - .with_to(wallet.address()) - .with_value(U256::from(100_000)) - .with_input(random_data); - - let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { - SendableTx::Builder(_) => bail!("expected a raw transaction"), - SendableTx::Envelope(raw) => raw.encoded_2718(), - }; - let tx_hash = B256::from(keccak256(&raw_tx)); - - send_rpc_request( - vec![hex::encode(&raw_tx)], - vec![tx_hash], - target_slot, - target_url, - &wallet, - ) - .await?; + for _ in 0..self.count { + // generate a simple self-transfer of ETH + let random_data = rand::thread_rng().gen::<[u8; 32]>(); + let req = TransactionRequest::default() + .with_to(wallet.address()) + .with_value(U256::from(100_000)) + .with_input(random_data); + + let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { + SendableTx::Builder(_) => bail!("expected a raw transaction"), + SendableTx::Envelope(raw) => raw.encoded_2718(), + }; + let tx_hash = B256::from(keccak256(&raw_tx)); + + send_rpc_request( + vec![hex::encode(&raw_tx)], + vec![tx_hash], + target_slot, + target_url.clone(), + &wallet, + ) + .await?; + } Ok(()) } From ed30e675600a823f5b0fea9d92d8e768a71e9e6c Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 15:47:33 +0200 Subject: [PATCH 09/11] chore: updated readme --- bolt-cli/README.md | 82 ++++++++++++++++++++++++++++++++--- bolt-cli/src/cli.rs | 4 ++ bolt-cli/src/commands/send.rs | 25 ++++++++--- 3 files changed, 97 insertions(+), 14 deletions(-) diff --git a/bolt-cli/README.md b/bolt-cli/README.md index 6f40376d0..00d51a8c4 100644 --- a/bolt-cli/README.md +++ b/bolt-cli/README.md @@ -31,12 +31,7 @@ Available commands: - [`delegate`](#delegate) - Generate BLS delegation messages for the Constraints API. - [`pubkeys`](#pubkeys) - List available BLS public keys from various key sources. - -All above commands support three key sources: - -- Local BLS secret keys (as hex-encoded strings) via `secret-keys` -- Local EIP-2335 filesystem keystore directories via `local-keystore` -- Remote Dirk keystore via `dirk` (requires TLS credentials) +- [`send`](#send) - Send a preconfirmation request to a Bolt sidecar. --- @@ -45,6 +40,12 @@ All above commands support three key sources: The `delegate` command generates signed delegation messages for the Constraints API. To learn more about the Constraints API, please refer to the [Bolt documentation][bolt-docs]. +The `delegate` command supports three key sources: + +- Local BLS secret keys (as hex-encoded strings) via `secret-keys` +- Local EIP-2335 filesystem keystore directories via `local-keystore` +- Remote Dirk keystore via `dirk` (requires TLS credentials) +
Usage @@ -136,7 +137,11 @@ bolt delegate \ ### `Pubkeys` -The `pubkeys` command lists available BLS public keys from different key sources. +The `pubkeys` command lists available BLS public keys from different key sources: + +- Local BLS secret keys (as hex-encoded strings) via `secret-keys` +- Local EIP-2335 filesystem keystore directories via `local-keystore` +- Remote Dirk keystore via `dirk` (requires TLS credentials)
Usage @@ -192,6 +197,69 @@ bolt pubkeys dirk --url https://localhost:9091 \ --- +### `Send` + +The `send` command sends a preconfirmation request to a Bolt sidecar. + +
+Usage + +```text +❯ bolt send --help + +Send a preconfirmation request to a Bolt proposer + +Usage: bolt send [OPTIONS] --private-key + +Options: + --bolt-rpc-url + Bolt RPC URL to send requests to and fetch lookahead info from + + [env: BOLT_RPC_URL=] + [default: http://135.181.191.125:58017/rpc] + + --private-key + The private key to sign the transaction with + + [env: PRIVATE_KEY] + + --override-bolt-sidecar-url + The Bolt Sidecar URL to send requests to. If provided, this will override the canonical bolt RPC URL and disregard any registration information. + + This is useful for testing and development purposes. + + [env: OVERRIDE_BOLT_SIDECAR_URL=] + + --count + How many transactions to send + + [env: TRANSACTION_COUNT=] + [default: 1] + + --blob + If set, the transaction will be blob-carrying (type 3) + + [env: BLOB=] + + -h, --help + Print help (see a summary with '-h') +``` + +
+ +
+Examples + +1. Sending a preconfirmation request to a Bolt sidecar + +```text +bolt send --private-key $(openssl rand -hex 32) +``` + +
+ +--- + ## Security The Bolt CLI is designed to be used offline. It does not require any network connections diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 42a69eb52..4a43b67f2 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -96,6 +96,10 @@ pub struct SendCommand { /// How many transactions to send. #[clap(long, env = "TRANSACTION_COUNT", default_value = "1")] pub count: u32, + + /// If set, the transaction will be blob-carrying (type 3) + #[clap(long, env = "BLOB", default_value = "false")] + pub blob: bool, } /// The action to perform. diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index c598a37b3..3e2c9e53d 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -1,7 +1,8 @@ use alloy::{ + consensus::{BlobTransactionSidecar, SidecarBuilder, SimpleCoder}, eips::eip2718::Encodable2718, - network::{EthereumWallet, TransactionBuilder}, - primitives::{keccak256, B256, U256}, + network::{EthereumWallet, TransactionBuilder, TransactionBuilder4844}, + primitives::{keccak256, Address, B256, U256}, providers::{ProviderBuilder, SendableTx}, rpc::types::TransactionRequest, signers::{local::PrivateKeySigner, Signer}, @@ -58,11 +59,7 @@ impl SendCommand { for _ in 0..self.count { // generate a simple self-transfer of ETH - let random_data = rand::thread_rng().gen::<[u8; 32]>(); - let req = TransactionRequest::default() - .with_to(wallet.address()) - .with_value(U256::from(100_000)) - .with_input(random_data); + let req = create_tx_request(wallet.address(), self.blob); let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { SendableTx::Builder(_) => bail!("expected a raw transaction"), @@ -84,6 +81,20 @@ impl SendCommand { } } +fn create_tx_request(to: Address, with_blob: bool) -> TransactionRequest { + let mut req = TransactionRequest::default(); + req = req.with_to(to).with_value(U256::from(100_000)); + req = req.with_input(rand::thread_rng().gen::<[u8; 32]>()); + + if with_blob { + let sidecar = SidecarBuilder::::from_slice(b"Blobs are fun!"); + let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); + req = req.with_blob_sidecar(sidecar) + } + + req +} + async fn send_rpc_request( txs_rlp: Vec, tx_hashes: Vec, From 4f02c6e5f8a3211bbf7c79a2f1c52a4af49c186e Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 16:49:55 +0200 Subject: [PATCH 10/11] feat: added devnet preconf support --- bolt-cli/src/cli.rs | 19 ++++++++++- bolt-cli/src/commands/send.rs | 61 ++++++++++++++++++++++++++++++++--- justfile | 30 ++++++++--------- 3 files changed, 90 insertions(+), 20 deletions(-) diff --git a/bolt-cli/src/cli.rs b/bolt-cli/src/cli.rs index 4a43b67f2..646c0a546 100644 --- a/bolt-cli/src/cli.rs +++ b/bolt-cli/src/cli.rs @@ -24,7 +24,7 @@ pub enum Cmd { Pubkeys(PubkeysCommand), /// Send a preconfirmation request to a Bolt proposer. - Send(SendCommand), + Send(Box), } impl Cmd { @@ -100,6 +100,23 @@ pub struct SendCommand { /// If set, the transaction will be blob-carrying (type 3) #[clap(long, env = "BLOB", default_value = "false")] pub blob: bool, + + /// If set, the transaction will target the devnet environment. + /// This is only used in Kurtosis for internal testing purposes + #[clap(long, hide = true, env = "DEVNET", default_value = "false")] + pub devnet: bool, + + /// The URL of the devnet execution client for filling transactions + #[clap(long = "devnet.execution_url", hide = true)] + pub devnet_execution_url: Option, + + /// The URL of the devnet beacon node for fetching slot numbers + #[clap(long = "devnet.beacon_url", hide = true)] + pub devnet_beacon_url: Option, + + /// The URL of the devnet sidecar for sending transactions + #[clap(long = "devnet.sidecar_url", hide = true)] + pub devnet_sidecar_url: Option, } /// The action to perform. diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index 3e2c9e53d..fdd2e12e0 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -7,7 +7,7 @@ use alloy::{ rpc::types::TransactionRequest, signers::{local::PrivateKeySigner, Signer}, }; -use eyre::{bail, Context, Result}; +use eyre::{bail, Context, ContextCompat, Result}; use rand::Rng; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -23,8 +23,17 @@ impl SendCommand { /// Run the `send` command. pub async fn run(self) -> Result<()> { let wallet: PrivateKeySigner = self.private_key.parse().wrap_err("invalid private key")?; - let transaction_signer = EthereumWallet::from(wallet.clone()); + if self.devnet { + self.send_devnet_transaction(&wallet).await + } else { + self.send_transaction(&wallet).await + } + } + + /// Send a transaction. + async fn send_transaction(self, wallet: &PrivateKeySigner) -> Result<()> { + let transaction_signer = EthereumWallet::from(wallet.clone()); let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(transaction_signer) @@ -36,7 +45,7 @@ impl SendCommand { // Note: it's possible for users to override the target sidecar URL // for testing and development purposes. In most cases, the sidecar will // reject a request for a slot that it is not responsible for. - let target_url = if let Some(sidecar_url) = self.override_bolt_sidecar_url { + let target_url = if let Some(sidecar_url) = &self.override_bolt_sidecar_url { // If using the override URL, we don't need to fetch the active proposers only. // we will set the next slot as the target slot. sidecar_url.clone() @@ -72,13 +81,57 @@ impl SendCommand { vec![tx_hash], target_slot, target_url.clone(), - &wallet, + wallet, ) .await?; } Ok(()) } + + /// Send a transaction on the Kurtosis devnet. + async fn send_devnet_transaction(self, wallet: &PrivateKeySigner) -> Result<()> { + let transaction_signer = EthereumWallet::from(wallet.clone()); + let el_url = self.devnet_execution_url.clone().wrap_err("missing devnet execution URL")?; + let cl_url = self.devnet_beacon_url.clone().wrap_err("missing devnet beacon URL")?; + let sidecar_url = self.devnet_sidecar_url.clone().wrap_err("missing devnet sidecar URL")?; + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(transaction_signer) + .on_http(el_url); + + // Fetch the current slot from the devnet beacon node + let slot = request_current_slot_number(&cl_url).await?; + + // Send the transaction to the devnet sidecar + for _ in 0..self.count { + let req = create_tx_request(wallet.address(), self.blob); + let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { + SendableTx::Builder(_) => bail!("expected a raw transaction"), + SendableTx::Envelope(raw) => raw.encoded_2718(), + }; + let tx_hash = B256::from(keccak256(&raw_tx)); + + send_rpc_request( + vec![hex::encode(&raw_tx)], + vec![tx_hash], + slot + 2, + sidecar_url.clone(), + wallet, + ) + .await?; + } + + Ok(()) + } +} + +async fn request_current_slot_number(beacon_url: &Url) -> Result { + let res = reqwest::get(beacon_url.join("eth/v1/beacon/headers/head")?).await?; + let res = res.json::().await?; + let slot = res.pointer("/header/message/slot").wrap_err("missing slot")?; + slot.as_u64().wrap_err("slot is not a number") } fn create_tx_request(to: Address, with_blob: bool) -> TransactionRequest { diff --git a/justfile b/justfile index 8708fd7be..83e1ea3f3 100644 --- a/justfile +++ b/justfile @@ -124,24 +124,24 @@ grafana: # manually send a preconfirmation to the bolt devnet send-preconf count='1': - cd bolt-kurtosis-client && RUST_LOG=info cargo run -- \ - --provider-url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ - --beacon-client-url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ - --bolt-sidecar-url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ - --private-key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ - --slot head \ - --count {{count}} + cd bolt-cli && RUST_LOG=info cargo run -- \ + --devnet \ + --devnet.execution_url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ + --devnet.beacon_url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ + --devnet.sidecar_url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ + --private_key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ + --count {{count}} # manually send a blob preconfirmation to the bolt devnet send-blob-preconf count='1': - cd bolt-kurtosis-client && RUST_LOG=info cargo run -- \ - --provider-url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ - --beacon-client-url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ - --bolt-sidecar-url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ - --private-key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ - --slot head \ - --blob \ - --count {{count}} \ + cd bolt-cli && RUST_LOG=info cargo run -- \ + --devnet \ + --devnet.execution_url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ + --devnet.beacon_url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ + --devnet.sidecar_url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ + --private_key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ + --blob \ + --count {{count}} # build all the docker images locally build-images: From efedd73c97f8c9e8a66d85e0100f72edfba8f070 Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:26:23 +0200 Subject: [PATCH 11/11] fix for sending preconfs in devnet --- bolt-cli/src/commands/send.rs | 49 +++++++++++++++++++++++++---------- justfile | 8 +++--- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/bolt-cli/src/commands/send.rs b/bolt-cli/src/commands/send.rs index fdd2e12e0..c2b7367eb 100644 --- a/bolt-cli/src/commands/send.rs +++ b/bolt-cli/src/commands/send.rs @@ -1,5 +1,7 @@ +use std::time::Duration; + use alloy::{ - consensus::{BlobTransactionSidecar, SidecarBuilder, SimpleCoder}, + consensus::{BlobTransactionSidecar, SidecarBuilder, SimpleCoder, Transaction}, eips::eip2718::Encodable2718, network::{EthereumWallet, TransactionBuilder, TransactionBuilder4844}, primitives::{keccak256, Address, B256, U256}, @@ -66,15 +68,22 @@ impl SendCommand { let target_slot = lookahead_res[0].slot; info!("Target slot: {}", target_slot); + // Send the transactions to the Bolt sidecar + let mut next_nonce = None; for _ in 0..self.count { // generate a simple self-transfer of ETH - let req = create_tx_request(wallet.address(), self.blob); + let mut req = create_tx_request(wallet.address(), self.blob); + if let Some(next_nonce) = next_nonce { + req.set_nonce(next_nonce); + } - let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { + let (raw_tx, tx_hash) = match provider.fill(req).await.wrap_err("failed to fill")? { SendableTx::Builder(_) => bail!("expected a raw transaction"), - SendableTx::Envelope(raw) => raw.encoded_2718(), + SendableTx::Envelope(raw) => { + next_nonce = Some(raw.nonce() + 1); + (raw.encoded_2718(), *raw.tx_hash()) + } }; - let tx_hash = B256::from(keccak256(&raw_tx)); send_rpc_request( vec![hex::encode(&raw_tx)], @@ -84,6 +93,9 @@ impl SendCommand { wallet, ) .await?; + + // Sleep for a bit to avoid spamming + tokio::time::sleep(Duration::from_millis(200)).await; } Ok(()) @@ -104,14 +116,21 @@ impl SendCommand { // Fetch the current slot from the devnet beacon node let slot = request_current_slot_number(&cl_url).await?; - // Send the transaction to the devnet sidecar + // Send the transactions to the devnet sidecar + let mut next_nonce = None; for _ in 0..self.count { - let req = create_tx_request(wallet.address(), self.blob); - let raw_tx = match provider.fill(req).await.wrap_err("failed to fill transaction")? { + let mut req = create_tx_request(wallet.address(), self.blob); + if let Some(next_nonce) = next_nonce { + req.set_nonce(next_nonce); + } + + let (raw_tx, tx_hash) = match provider.fill(req).await.wrap_err("failed to fill")? { SendableTx::Builder(_) => bail!("expected a raw transaction"), - SendableTx::Envelope(raw) => raw.encoded_2718(), + SendableTx::Envelope(raw) => { + next_nonce = Some(raw.nonce() + 1); + (raw.encoded_2718(), *raw.tx_hash()) + } }; - let tx_hash = B256::from(keccak256(&raw_tx)); send_rpc_request( vec![hex::encode(&raw_tx)], @@ -121,6 +140,9 @@ impl SendCommand { wallet, ) .await?; + + // Sleep for a bit to avoid spamming + tokio::time::sleep(Duration::from_millis(200)).await; } Ok(()) @@ -130,8 +152,8 @@ impl SendCommand { async fn request_current_slot_number(beacon_url: &Url) -> Result { let res = reqwest::get(beacon_url.join("eth/v1/beacon/headers/head")?).await?; let res = res.json::().await?; - let slot = res.pointer("/header/message/slot").wrap_err("missing slot")?; - slot.as_u64().wrap_err("slot is not a number") + let slot = res.pointer("/data/header/message/slot").wrap_err("missing slot")?; + Ok(slot.as_u64().unwrap_or(slot.as_str().wrap_err("invalid slot type")?.parse()?)) } fn create_tx_request(to: Address, with_blob: bool) -> TransactionRequest { @@ -142,7 +164,8 @@ fn create_tx_request(to: Address, with_blob: bool) -> TransactionRequest { if with_blob { let sidecar = SidecarBuilder::::from_slice(b"Blobs are fun!"); let sidecar: BlobTransactionSidecar = sidecar.build().unwrap(); - req = req.with_blob_sidecar(sidecar) + req = req.with_blob_sidecar(sidecar); + req = req.with_max_fee_per_blob_gas(3_000_000); } req diff --git a/justfile b/justfile index 83e1ea3f3..8527eb23e 100644 --- a/justfile +++ b/justfile @@ -124,22 +124,22 @@ grafana: # manually send a preconfirmation to the bolt devnet send-preconf count='1': - cd bolt-cli && RUST_LOG=info cargo run -- \ + cd bolt-cli && RUST_LOG=info cargo run -- send \ --devnet \ --devnet.execution_url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ --devnet.beacon_url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ --devnet.sidecar_url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ - --private_key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ + --private-key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ --count {{count}} # manually send a blob preconfirmation to the bolt devnet send-blob-preconf count='1': - cd bolt-cli && RUST_LOG=info cargo run -- \ + cd bolt-cli && RUST_LOG=info cargo run -- send \ --devnet \ --devnet.execution_url $(kurtosis port print bolt-devnet el-1-geth-lighthouse rpc) \ --devnet.beacon_url $(kurtosis port print bolt-devnet cl-1-lighthouse-geth http) \ --devnet.sidecar_url http://$(kurtosis port print bolt-devnet bolt-sidecar-1-lighthouse-geth api) \ - --private_key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ + --private-key 53321db7c1e331d93a11a41d16f004d7ff63972ec8ec7c25db329728ceeb1710 \ --blob \ --count {{count}}