use clap::{Parser, Subcommand};
use essential_node_types::BigBang;
use essential_rest_client::{
builder_client::EssentialBuilderClient, contract_from_path, node_client::EssentialNodeClient,
};
use essential_types::{
convert::word_from_bytes,
solution::{Solution, SolutionSet},
ContentAddress, Word,
};
use std::{path::PathBuf, str::FromStr};
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
ListBlocks {
node_address: String,
range: BlockRange,
},
QueryState {
node_address: String,
#[arg(short, long)]
content_address: ContentAddress,
key: Key,
},
DeployContract {
builder_address: String,
contract: PathBuf,
},
SubmitSolution {
builder_address: String,
solution: PathBuf,
},
LatestSolutionFailures {
builder_address: String,
#[arg(short, long)]
content_address: ContentAddress,
limit: u32,
},
}
#[tokio::main]
async fn main() {
let args = Cli::parse();
if let Err(err) = run(args).await {
eprintln!("Command failed because: {}", err);
}
}
async fn run(cli: Cli) -> anyhow::Result<()> {
let Cli { command } = cli;
match command {
Command::ListBlocks {
node_address,
range,
} => {
let node_client = EssentialNodeClient::new(node_address)?;
let output = node_client.list_blocks(range.start..range.end).await?;
println!("{}", serde_json::to_string(&output)?);
}
Command::QueryState {
node_address,
content_address,
key,
} => {
let node_client = EssentialNodeClient::new(node_address)?;
let output = node_client
.query_state(content_address.to_owned(), key.0.to_owned())
.await?;
println!("{}", serde_json::to_string(&output)?);
}
Command::DeployContract {
builder_address,
contract,
} => {
let big_bang = BigBang::default();
let builder_client = EssentialBuilderClient::new(builder_address)?;
let (contract, programs) = contract_from_path(&contract).await?;
let output = builder_client
.deploy_contract(&big_bang, &contract, &programs)
.await?;
println!("{}", output);
}
Command::SubmitSolution {
builder_address,
solution,
} => {
let builder_client = EssentialBuilderClient::new(builder_address)?;
let solution = serde_json::from_str::<Solution>(&from_file(solution).await?)?;
let solutions = SolutionSet {
solutions: vec![solution],
};
let output = builder_client.submit_solution(&solutions).await?;
println!("{}", output);
}
Command::LatestSolutionFailures {
builder_address,
content_address,
limit,
} => {
let builder_client = EssentialBuilderClient::new(builder_address)?;
let output = builder_client
.latest_solution_failures(&content_address, limit)
.await?;
println!("{}", serde_json::to_string(&output)?);
}
}
Ok(())
}
async fn from_file(path: PathBuf) -> anyhow::Result<String> {
let content = tokio::fs::read_to_string(path).await?;
Ok(content)
}
#[derive(Clone, Debug)]
pub struct BlockRange {
start: Word,
end: Word,
}
impl FromStr for BlockRange {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split("..");
let start = split
.next()
.ok_or_else(|| anyhow::anyhow!("No start block"))?;
let end = split
.next()
.ok_or_else(|| anyhow::anyhow!("No end block"))?;
Ok(Self {
start: start.parse()?,
end: end.parse()?,
})
}
}
#[derive(Clone, Debug)]
struct Key(essential_types::Key);
impl FromStr for Key {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(
hex::decode(s)?
.chunks_exact(8)
.map(|chunk| word_from_bytes(chunk.try_into().expect("Always 8 bytes")))
.collect(),
))
}
}