use chrono::Utc;
use clap::AppSettings;
use concordium_rust_sdk::{
types::{AbsoluteBlockHeight, SpecialTransactionOutcome},
v2,
v2::Endpoint,
};
use futures::TryStreamExt;
use structopt::StructOpt;
#[derive(StructOpt)]
struct App {
#[structopt(
long = "node",
help = "GRPC V2 interface of the node.",
default_value = "http://localhost:20000"
)]
endpoint: Endpoint,
#[structopt(
long = "from",
help = "Starting time (format 2023-06-22T14:00:00+0200). Defaults to genesis time."
)]
from: Option<chrono::DateTime<chrono::Utc>>,
#[structopt(
long = "to",
help = "End time (format 2023-06-22T14:00:00+0200). Defaults to the time the tool has run."
)]
to: Option<chrono::DateTime<Utc>>,
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
let app = {
let app = App::clap().global_setting(AppSettings::ColoredHelp);
let matches = app.get_matches();
App::from_clap(&matches)
};
let mut client = v2::Client::new(app.endpoint).await?;
let mut h = if let Some(start_time) = app.from {
let b = client
.find_at_lowest_height(.., |mut client, height| async move {
let bi = client.get_block_info(&height).await?.response;
Ok(if bi.block_slot_time >= start_time {
Some(bi)
} else {
None
})
})
.await;
match b {
Ok(bi) => bi.block_height,
Err(e) if e.is_not_found() => {
anyhow::bail!("Last finalized block is not after the requested start time.")
}
Err(e) => anyhow::bail!("An error occurred: {e:#}"),
}
} else {
AbsoluteBlockHeight::from(0u64)
};
let mut block_count: u32 = 0;
let mut finalization_count: u32 = 0;
let mut blocks = client.get_finalized_blocks_from(h).await?;
let end_time = app.to.unwrap_or_else(chrono::Utc::now);
println!(
"Block hash, block time, block receive time, block arrive time, receive delta, arrive \
delta, #payday events, finalization data, transaction count, round, epoch, baker id"
);
while let Some(block) = blocks.next().await {
let bi = client.get_block_info(block.block_hash).await?.response;
if bi.block_slot_time > end_time {
break;
}
let payday_block = client
.get_block_special_events(bi.block_hash)
.await?
.response
.try_fold(0, |count, ev| async move {
let v2::Upward::Known(ev) = ev else {
return Ok(count);
};
let add = match ev {
SpecialTransactionOutcome::PaydayFoundationReward { .. } => 1u32,
SpecialTransactionOutcome::PaydayAccountReward { .. } => 1u32,
SpecialTransactionOutcome::PaydayPoolReward { .. } => 1u32,
_ => 0u32,
};
Ok(count + add)
})
.await?;
h = h.next();
let has_finalization_data = client
.get_block_finalization_summary(bi.block_hash)
.await?
.response
.is_some();
println!(
"{}, {}, {}, {}, {}ms, {}ms, {}, {}, {}, {}, {}, {}",
bi.block_hash,
bi.block_slot_time.format("%H:%M:%S%.3f"),
bi.block_receive_time.format("%H:%M:%S%.3f"),
bi.block_arrive_time.format("%H:%M:%S%.3f"),
bi.block_receive_time
.signed_duration_since(bi.block_slot_time)
.num_milliseconds(),
bi.block_arrive_time
.signed_duration_since(bi.block_slot_time)
.num_milliseconds(),
payday_block,
has_finalization_data,
bi.transaction_count,
bi.round.map_or(String::new(), |x| x.to_string()),
bi.epoch.map_or(String::new(), |x| x.to_string()),
bi.block_baker.map_or(String::new(), |x| x.to_string()),
);
if has_finalization_data {
finalization_count += 1
};
block_count += 1;
}
println!("Block count = {}", block_count);
println!("Finalization record count = {}", finalization_count);
Ok(())
}