Skip to content

Commit

Permalink
feat: cli scaffolding
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Nov 14, 2023
1 parent 8195f58 commit e0b7cbb
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 15 deletions.
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ members = ["bin/*", "crates/*"]
resolver = "2"

[workspace.package]
name = "mevboost-relay-api"
version = "0.1.1"
edition = "2021"
license = "MIT"
Expand All @@ -19,6 +18,7 @@ tracing-subscriber = "0.3.17"
serde = { version = "1.0.192", features = ["derive"] }
clap = { version = "4.4.7", features = ["derive"] }
tokio = { version = "1.12.0", features = ["full"] }
# beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus.git" }

[profile.dev]
opt-level = 1
Expand Down
5 changes: 5 additions & 0 deletions bin/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ anyhow.workspace = true
inquire.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true

csv = "1.3.0"
123 changes: 114 additions & 9 deletions bin/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,127 @@
use clap::Parser;
use clap::{Parser, Subcommand, ValueEnum};
use mevboost_relay_api::{
types::{BuilderBidsReceivedOptions, PayloadDeliveredQueryOptions},
Client,
};

#[derive(Parser)]
#[clap(author = "Chainbound")]
#[command(author, version, about, long_about = None)]
// #[command(propagate_version = true)]
struct Args {
#[clap(long, short = 'r', default_value = "flashbots")]
relay_name: String,
/// The subcommand to execute.
#[clap(subcommand)]
command: Command,
/// The output method to use. Default: human readable text.
#[clap(long, short = 'o', default_value = "human")]
output_method: OutputMethod,
/// The path to write the output to. If not provided,
/// output will be written to stdout.
#[clap(long, short = 'p')]
output_path: Option<String>,
}

fn main() -> anyhow::Result<()> {
#[derive(Default, ValueEnum, Clone)]
enum OutputMethod {
/// Output in human readable format
#[default]
Human,
/// Output in CSV format
Csv,
/// Output in JSON format, to a file path or
/// to stdout if no file path is provided.
Json,
}

#[derive(Subcommand)]
enum Command {
/// Get the payloads delivered to proposers for a given slot.
#[clap(name = "payloads-delivered")]
PayloadsDelivered { slot: u64 },

/// Get the block bids received by the relay for a given slot.
#[clap(name = "block-bids")]
BlockBids {
#[clap(long)]
slot: Option<u64>,
#[clap(long)]
block_hash: Option<String>,
},

/// Get the timestamp of the winning bid for a given slot.
#[clap(name = "winning-bid-timestamp")]
WinningBidTimestamp { slot: u64 },
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
let _ = tracing_subscriber::fmt::try_init();

let client = mevboost_relay_api::Client::default();
if !client.relays.contains_key(args.relay_name.as_str()) {
anyhow::bail!("Relay `{}` not found in list of relays.", args.relay_name)
let client = Client::default();

println!();
if !matches!(args.output_method, OutputMethod::Human) {
todo!("Only human output is currently supported")
}

// TODO
match args.command {
Command::PayloadsDelivered { slot } => {
let payloads = client
.get_payloads_delivered_bidtraces_on_all_relays(&PayloadDeliveredQueryOptions {
slot: Some(slot),
..Default::default()
})
.await?;

println!("{:#?}", payloads);
}

Command::BlockBids { slot, block_hash } => {
let block_bids = client
.get_builder_blocks_received_on_all_relays(&BuilderBidsReceivedOptions {
slot,
block_hash,
..Default::default()
})
.await?;

println!("{:#?}", block_bids);
}

Command::WinningBidTimestamp { slot } => {
let payloads = client
.get_payloads_delivered_bidtraces_on_all_relays(&PayloadDeliveredQueryOptions {
slot: Some(slot),
..Default::default()
})
.await?;

for (relay, relay_payloads) in payloads {
if relay_payloads.is_empty() {
continue;
}

let block_hash = relay_payloads[0].block_hash.clone();
let block_bids = client
.get_builder_blocks_received(
relay,
&BuilderBidsReceivedOptions {
slot: Some(slot),
block_hash: Some(block_hash),
..Default::default()
},
)
.await?;

let timestamp = block_bids[0].timestamp_ms;
println!(
"The winning bid for slot {} was submitted to {} at: {}",
slot, relay, timestamp
)
}
}
}

println!();
Ok(())
}
69 changes: 64 additions & 5 deletions crates/mevboost-relay-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub mod types;
#[derive(Debug)]
pub struct Client<'a> {
/// List of relay names and endpoints to use for queries.
pub relays: HashMap<&'a str, &'a str>,
relays: HashMap<&'a str, &'a str>,
/// HTTP client used for requests.
inner: reqwest::Client,
}
Expand All @@ -50,6 +50,13 @@ impl<'a> Client<'a> {
Self { relays, inner }
}

/// Check if the client contains a relay with the given name.
///
/// This is useful for checking if a relay is available before performing a query.
pub fn contains(&self, relay_name: &str) -> bool {
self.relays.contains_key(relay_name)
}

/// Perform a relay query for validator registrations for the current and next epochs.
///
/// [Visit the docs](https://flashbots.github.io/relay-specs/#/Builder/getValidators) for more info.
Expand Down Expand Up @@ -92,7 +99,7 @@ impl<'a> Client<'a> {
pub async fn get_payload_delivered_bidtraces(
&self,
relay_name: &str,
opts: types::PayloadDeliveredQueryOptions,
opts: &types::PayloadDeliveredQueryOptions,
) -> anyhow::Result<Vec<types::PayloadBidtrace>> {
let relay_url = self.get_relay_url(relay_name)?;
let endpoint = format!(
Expand All @@ -107,12 +114,38 @@ impl<'a> Client<'a> {
.map_err(|e| anyhow::anyhow!("Failed to parse JSON response: {}", e))
}

/// Perform queries on all relays to get the payloads delivered by each relay to proposers.
/// Query options act as filters. Returns a hashmap of relay names to payload bidtraces.
pub async fn get_payloads_delivered_bidtraces_on_all_relays(
&self,
opts: &types::PayloadDeliveredQueryOptions,
) -> anyhow::Result<HashMap<&'a str, Vec<types::PayloadBidtrace>>> {
let mut payloads_delivered = HashMap::new();
for relay_name in self.relays.keys() {
match self.get_payload_delivered_bidtraces(relay_name, opts).await {
Ok(relay_res) => {
payloads_delivered.insert(*relay_name, relay_res);
}
Err(e) => {
tracing::warn!(
"Failed to get payloads delivered for relay {}: {}",
relay_name,
e
);
continue;
}
}
}

Ok(payloads_delivered)
}

/// Perform a relay query to get the builder bid submissions.
/// Query options act as filters.
pub async fn get_builder_blocks_received(
&self,
relay_name: &str,
opts: types::BuilderBidsReceivedOptions,
opts: &types::BuilderBidsReceivedOptions,
) -> anyhow::Result<Vec<types::BuilderBlockBidtrace>> {
let relay_url = self.get_relay_url(relay_name)?;
let endpoint = format!(
Expand All @@ -127,6 +160,32 @@ impl<'a> Client<'a> {
.map_err(|e| anyhow::anyhow!("Failed to parse JSON response: {}", e))
}

/// Perform queries on all relays to get the builder bid submissions.
/// Query options act as filters. Returns a hashmap of relay names to builder block bidtraces.
pub async fn get_builder_blocks_received_on_all_relays(
&self,
opts: &types::BuilderBidsReceivedOptions,
) -> anyhow::Result<HashMap<&'a str, Vec<types::BuilderBlockBidtrace>>> {
let mut builder_blocks_received = HashMap::new();
for relay_name in self.relays.keys() {
match self.get_builder_blocks_received(relay_name, opts).await {
Ok(relay_res) => {
builder_blocks_received.insert(*relay_name, relay_res);
}
Err(e) => {
tracing::warn!(
"Failed to get builder blocks received for relay {}: {}",
relay_name,
e
);
continue;
}
}
}

Ok(builder_blocks_received)
}

/// Perform a relay query to check if a validator with the given pubkey
/// is registered with any of the relays in the client. Returns a hashmap
/// of relay names to validator entries. If an entry is not found for a
Expand Down Expand Up @@ -297,7 +356,7 @@ mod tests {
};

let response = client
.get_payload_delivered_bidtraces("ultrasound", opts)
.get_payload_delivered_bidtraces("ultrasound", &opts)
.await?;

assert!(!response.is_empty());
Expand All @@ -313,7 +372,7 @@ mod tests {
};

let response = client
.get_builder_blocks_received("ultrasound", opts)
.get_builder_blocks_received("ultrasound", &opts)
.await?;

dbg!(&response);
Expand Down

0 comments on commit e0b7cbb

Please sign in to comment.