use std::{cmp, convert::TryFrom, io::Write};
use anyhow::Error;
use async_trait::async_trait;
use chrono::Utc;
use clap::Parser;
use tari_core::{chain_storage::ChainStorageError, proof_of_work::lwma_diff::LinearWeightedMovingAverage};
use tari_transaction_components::tari_proof_of_work::PowAlgorithm;
use tari_utilities::hex::Hex;
use tokio::{
fs::File,
io::{self, AsyncWriteExt},
};
use super::{CommandContext, HandleCommand};
#[derive(Debug, Parser)]
pub struct Args {
start_height: u64,
end_height: u64,
#[clap(default_value = "header-data.csv")]
filename: String,
pow_algo: Option<PowAlgorithm>,
}
#[async_trait]
impl HandleCommand<Args> for CommandContext {
async fn handle_command(&mut self, args: Args) -> Result<(), Error> {
self.save_header_stats(args.start_height, args.end_height, args.filename, args.pow_algo)
.await
}
}
impl CommandContext {
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::too_many_lines)]
pub async fn save_header_stats(
&self,
start_height: u64,
end_height: u64,
filename: String,
pow_algo: Option<PowAlgorithm>,
) -> Result<(), Error> {
let mut output = File::create(&filename).await?;
println!(
"Loading header from height {} to {} and dumping to file [working-dir]/{}.{}",
start_height,
end_height,
filename,
pow_algo.map(|a| format!(" PoW algo = {a}")).unwrap_or_default()
);
let start_height = cmp::max(start_height, 1);
let mut prev_header = self.blockchain_db.fetch_chain_header(start_height - 1).await?;
let mut buff = Vec::new();
writeln!(
buff,
"Height,Achieved,TargetDifficulty,CalculatedDifficulty,SolveTime,NormalizedSolveTime,Algo,Timestamp,\
Window,Acc.Monero,Acc.Sha3, Acc.Rxt, Acc.Cuckaroo"
)?;
output.write_all(&buff).await?;
for height in start_height..=end_height {
let header = self.blockchain_db.fetch_chain_header(height).await?;
if pow_algo.map(|algo| header.header().pow_algo() != algo).unwrap_or(false) {
continue;
}
let target_diff = self
.blockchain_db
.fetch_target_difficulties_for_next_block(*prev_header.hash())
.await?;
let pow_algo = header.header().pow_algo();
let min = self
.consensus_rules
.consensus_constants(height)
.min_pow_difficulty(pow_algo);
let max = self
.consensus_rules
.consensus_constants(height)
.max_pow_difficulty(pow_algo);
let calculated_target_difficulty = target_diff
.get(pow_algo)
.map_err(ChainStorageError::UnexpectedResult)?
.calculate(min, max);
let existing_target_difficulty = header.accumulated_data().target_difficulty;
let achieved = header.accumulated_data().achieved_difficulty;
let solve_time = header.header().timestamp.as_u64() as i64 - prev_header.header().timestamp.as_u64() as i64;
let normalized_solve_time = cmp::min(
u64::try_from(cmp::max(solve_time, 1)).unwrap(),
LinearWeightedMovingAverage::max_block_time(
self.consensus_rules
.consensus_constants(height)
.pow_target_block_interval(pow_algo),
)
.map_err(Error::msg)?,
);
let acc_sha3 = header.accumulated_data().accumulated_sha3x_difficulty();
let acc_monero = header.accumulated_data().accumulated_monero_randomx_difficulty();
let acc_tari_rx = header.accumulated_data().accumulated_tari_randomx_difficulty();
let acc_cuckaroo = header.accumulated_data().accumulated_cuckaroo_difficulty();
buff.clear();
writeln!(
buff,
"{},{},{},{},{},{},{},{},{},{},{},{}, {}",
height,
achieved.as_u64(),
existing_target_difficulty.as_u64(),
calculated_target_difficulty.as_u64(),
solve_time,
normalized_solve_time,
pow_algo,
chrono::DateTime::<Utc>::from_timestamp(header.header().timestamp.as_u64() as i64, 0)
.unwrap_or_default(),
target_diff
.get(pow_algo)
.map_err(ChainStorageError::UnexpectedResult)?
.len(),
acc_monero,
acc_tari_rx,
acc_sha3,
acc_cuckaroo,
)?;
output.write_all(&buff).await?;
if header.header().hash() != header.accumulated_data().hash {
eprintln!(
"Difference in hash at {}! header = {} and accum hash = {}",
height,
header.header().hash().to_hex(),
header.accumulated_data().hash.to_hex()
);
}
if existing_target_difficulty != calculated_target_difficulty {
eprintln!(
"Difference at {height}! existing = {existing_target_difficulty} and calculated = \
{calculated_target_difficulty}"
);
}
print!("{height}");
io::stdout().flush().await?;
print!("\x1B[{}D\x1B[K", (height + 1).to_string().chars().count());
prev_header = header;
}
println!("Complete");
Ok(())
}
}