Skip to content

Commit

Permalink
Merge pull request #321 from chainbound/nico/feat/bolt-cli-send
Browse files Browse the repository at this point in the history
feat(cli): `bolt send` command
  • Loading branch information
merklefruit authored Oct 28, 2024
2 parents 1985f4f + efedd73 commit d234647
Show file tree
Hide file tree
Showing 15 changed files with 1,618 additions and 193 deletions.
907 changes: 905 additions & 2 deletions bolt-cli/Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions bolt-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,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"
Expand All @@ -33,10 +32,11 @@ thiserror = "1.0"
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"
reqwest = "0.12.8"

[build-dependencies]
tonic-build = "0.12.3"
82 changes: 75 additions & 7 deletions bolt-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand All @@ -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)

<details>
<summary>Usage</summary>

Expand Down Expand Up @@ -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)

<details>
<summary>Usage</summary>
Expand Down Expand Up @@ -192,6 +197,69 @@ bolt pubkeys dirk --url https://localhost:9091 \

---

### `Send`

The `send` command sends a preconfirmation request to a Bolt sidecar.

<details>
<summary>Usage</summary>

```text
❯ bolt send --help
Send a preconfirmation request to a Bolt proposer
Usage: bolt send [OPTIONS] --private-key <PRIVATE_KEY>
Options:
--bolt-rpc-url <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 <PRIVATE_KEY>
The private key to sign the transaction with
[env: PRIVATE_KEY]
--override-bolt-sidecar-url <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 <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')
```

</details>

<details>
<summary>Examples</summary>

1. Sending a preconfirmation request to a Bolt sidecar

```text
bolt send --private-key $(openssl rand -hex 32)
```

</details>

---

## Security

The Bolt CLI is designed to be used offline. It does not require any network connections
Expand Down
173 changes: 127 additions & 46 deletions bolt-cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,126 @@
use clap::{Parser, Subcommand, ValueEnum};
use serde::Deserialize;
use clap::{
builder::styling::{AnsiColor, Color, Style},
Parser, Subcommand, ValueEnum,
};
use reqwest::Url;

use crate::utils::keystore::DEFAULT_KEYSTORE_PASSWORD;
use crate::common::keystore::DEFAULT_KEYSTORE_PASSWORD;

/// A CLI tool to interact with Bolt Protocol ✨
#[derive(Parser, Debug, Clone, Deserialize)]
#[command(author, version, about, long_about = None)]
/// `bolt` is a CLI tool to interact with Bolt Protocol ✨
#[derive(Parser, Debug, Clone)]
#[command(author, version, styles = cli_styles(), about, arg_required_else_help(true))]
pub struct Opts {
/// The subcommand to run.
#[clap(subcommand)]
pub command: Commands,
pub command: Cmd,
}

#[derive(Subcommand, Debug, Clone, Deserialize)]
pub enum Commands {
#[derive(Subcommand, Debug, Clone)]
pub enum Cmd {
/// 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),

/// Send a preconfirmation request to a Bolt proposer.
Send(Box<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 {
/// 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, 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,
}

/// Command for sending a preconfirmation request to a Bolt proposer.
#[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/rpc")]
pub bolt_rpc_url: Url,

/// 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<Url>,

/// 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,

/// 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<Url>,

/// The URL of the devnet beacon node for fetching slot numbers
#[clap(long = "devnet.beacon_url", hide = true)]
pub devnet_beacon_url: Option<Url>,

/// The URL of the devnet sidecar for sending transactions
#[clap(long = "devnet.sidecar_url", hide = true)]
pub devnet_sidecar_url: Option<Url>,
}

/// 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.
Expand All @@ -60,7 +129,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 {
Expand All @@ -86,7 +155,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")]
Expand All @@ -113,7 +182,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")]
Expand All @@ -134,7 +203,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")]
Expand All @@ -148,7 +217,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,
Expand All @@ -169,6 +238,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;
Expand Down
Loading

0 comments on commit d234647

Please sign in to comment.