use std::cmp::Ordering;
use anyhow::{anyhow, Result};
use colored::Colorize;
use drand_core::{
beacon::{BeaconError, RandomnessBeacon, RandomnessBeaconTime},
ChainOptions, DrandError, HttpClient,
};
use serde::Serialize;
use crate::{
config::{self, ConfigChain},
print::{print_with_format, Format, Print},
};
#[derive(Serialize)]
pub(crate) struct RandResult {
beacon: Option<RandomnessBeacon>,
time: RandomnessBeaconTime,
}
impl RandResult {
pub(crate) fn new(beacon: Option<RandomnessBeacon>, time: RandomnessBeaconTime) -> Self {
Self { beacon, time }
}
}
impl Print for RandResult {
fn short(&self) -> Result<String> {
match self.beacon.as_ref() {
Some(beacon) => Ok(hex::encode(beacon.randomness())),
None => {
let format = time::format_description::parse(
"[year]-[month]-[day]T[hour]:[minute]:[second]Z",
)?;
let relative = self.time.relative();
let seconds = relative.whole_seconds().abs() % 60;
let minutes = relative.whole_minutes().abs() % 60;
let hours = relative.whole_hours().abs();
let relative = format!("{hours:0>2}:{minutes:0>2}:{seconds:0>2}");
Err(anyhow!(
"Too early. Beacon round is {}, estimated in {} ({}).",
self.time.round(),
relative,
self.time.absolute().format(&format)?,
))
}
}
}
fn long(&self) -> Result<String> {
let format =
time::format_description::parse("[year]-[month]-[day]T[hour]:[minute]:[second]Z")?;
let relative = self.time.relative();
let seconds = relative.whole_seconds().abs() % 60;
let minutes = (relative.whole_minutes()).abs() % 60;
let hours = relative.whole_hours().abs();
let epoch = match relative.whole_seconds().cmp(&0) {
Ordering::Less => "ago",
Ordering::Equal => "now",
Ordering::Greater => "from now",
};
let relative = format!("{hours:0>2}:{minutes:0>2}:{seconds:0>2} {epoch}");
let mut output = format!(
r"{: <10}: {}
{: <10}: {}
{: <10}: {}",
"Round".bold(),
self.time.round(),
"Relative".bold(),
relative,
"Absolute".bold(),
self.time.absolute().format(&format)?,
);
if let Some(beacon) = self.beacon.as_ref() {
output = format!(
r"{output}
{: <10}: {}
{: <10}: {}",
"Randomness".bold(),
hex::encode(beacon.randomness()),
"Signature".bold(),
hex::encode(beacon.signature()),
);
}
Ok(output)
}
fn json(&self) -> Result<String> {
Ok(serde_json::to_string(&self.beacon)?)
}
}
pub fn rand(
_cfg: &config::Local,
format: Format,
chain: ConfigChain,
beacon: Option<String>,
verify: bool,
) -> Result<String> {
let base_url = chain.url();
let info = chain.info();
let latest = beacon.is_none();
let beacon = beacon.unwrap_or("0s".to_owned());
let time = match RandomnessBeaconTime::parse(&info.clone().into(), &beacon) {
Ok(time) => time,
Err(_) => return Err(anyhow!("Invalid beacon round \"{beacon}\"")),
};
let client = HttpClient::new(
&base_url,
Some(ChainOptions::new(verify, true, Some(info.into()))),
)?;
let beacon = if latest {
client.latest()
} else {
client.get(time.round())
};
match beacon {
Ok(beacon) => print_with_format(RandResult::new(Some(beacon), time), format),
Err(DrandError::Beacon(e)) => match *e {
BeaconError::NotFound => print_with_format(RandResult::new(None, time), format),
_ => Ok(e.to_string()),
},
Err(e) => Err(e.into()),
}
}