From bc4ddd1d4bf1f48f2f16b21bda39470a29bed97f Mon Sep 17 00:00:00 2001 From: s8sato <49983831+s8sato@users.noreply.github.com> Date: Sat, 15 Jan 2022 19:19:20 +0900 Subject: [PATCH] Implement Signed-off-by: s8sato <49983831+s8sato@users.noreply.github.com> --- Cargo.lock | 5 +- core/src/block.rs | 1 - tools/kura_inspector/Cargo.toml | 7 +- tools/kura_inspector/src/lib.rs | 81 +++++++++++++- tools/kura_inspector/src/main.rs | 34 ++++-- tools/kura_inspector/src/print.rs | 176 ++++++++++++++++++++++++++++++ 6 files changed, 288 insertions(+), 16 deletions(-) create mode 100644 tools/kura_inspector/src/print.rs diff --git a/Cargo.lock b/Cargo.lock index ed26c8243da..acfdd8f5812 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2077,8 +2077,11 @@ name = "kura_inspector" version = "2.0.0-pre.1" dependencies = [ "clap 3.0.7", + "futures-util", + "iroha_config", "iroha_core", - "iroha_version", + "tempfile", + "tokio", ] [[package]] diff --git a/core/src/block.rs b/core/src/block.rs index 526084ad2f0..93ee8c56933 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -577,7 +577,6 @@ impl ValidBlock { /// /// # Panics /// If generating keys or block signing fails. - #[cfg(test)] #[allow(clippy::restriction)] pub fn new_dummy() -> Self { Self { diff --git a/tools/kura_inspector/Cargo.toml b/tools/kura_inspector/Cargo.toml index 39a7bc44917..d51886eadff 100644 --- a/tools/kura_inspector/Cargo.toml +++ b/tools/kura_inspector/Cargo.toml @@ -8,6 +8,11 @@ edition = "2021" [dependencies] iroha_core = { path = "../../core" } -iroha_version = { path = "../../version" } +iroha_config = { path = "../../config" } clap = { version = "3.0", features = ["derive"] } +futures-util = "0.3" +tokio = { version = "1.6.0", features = ["rt"]} + +[dev-dependencies] +tempfile = "3" diff --git a/tools/kura_inspector/src/lib.rs b/tools/kura_inspector/src/lib.rs index ab3cfef5502..cec7c23f45c 100644 --- a/tools/kura_inspector/src/lib.rs +++ b/tools/kura_inspector/src/lib.rs @@ -1 +1,80 @@ -use iroha_core::kura::BlockStore; +//! General objects independent from executables. + +use std::path::Path; + +use iroha_config::Configurable; +use iroha_core::kura; + +pub mod print; + +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum Config { + Print(print::Config), +} + +/// Where to write the results of the inspection. +pub struct Output +where + T: std::io::Write + Send, + E: std::io::Write + Send, +{ + /// Writer for valid data + pub ok: T, + /// Writer for invalid data + pub err: E, +} + +impl Config { + /// Configure [`kura::BlockStore`] and route to the subcommand. + /// + /// # Errors + /// Fails if + /// 1. Fails to configure [`kura::BlockStore`]. + /// 2. Fails to run the subcommand. + pub async fn run(&self, output: &mut Output) -> Result<(), Error> + where + T: std::io::Write + Send, + E: std::io::Write + Send, + { + let block_store = block_store().await?; + match self { + Self::Print(cfg) => cfg.run(&block_store, output).await.map_err(Error::Print)?, + } + Ok(()) + } +} + +async fn block_store() -> Result, Error> { + let mut kura_config = kura::config::KuraConfiguration::default(); + kura_config.load_environment().map_err(Error::KuraConfig)?; + kura::BlockStore::new( + Path::new(&kura_config.block_store_path), + kura_config.blocks_per_storage_file, + kura::DefaultIO, + ) + .await + .map_err(Error::SetBlockStore) +} + +/// [`Output`] for CLI use. +pub type DefaultOutput = Output; + +impl DefaultOutput { + /// Construct [`DefaultOutput`]. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + ok: std::io::stdout(), + err: std::io::stderr(), + } + } +} + +#[derive(Debug)] +#[allow(missing_docs)] +pub enum Error { + KuraConfig(iroha_config::derive::Error), + SetBlockStore(kura::Error), + Print(print::Error), +} diff --git a/tools/kura_inspector/src/main.rs b/tools/kura_inspector/src/main.rs index 21bdfc38e9d..679ad826bf9 100644 --- a/tools/kura_inspector/src/main.rs +++ b/tools/kura_inspector/src/main.rs @@ -1,18 +1,14 @@ -use std::path::PathBuf; - use clap::{Parser, Subcommand}; +use kura_inspector::{print, Config, DefaultOutput}; /// Kura inspector #[derive(Parser)] #[clap(version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"))] struct Opts { /// Height of the block up to which exclude from the inspection. - /// Defaults to the previous one from the current top + /// Defaults to exclude the all except the latest block #[clap(short, long, name = "BLOCK_HEIGHT")] - skip_to: Option, - /// Find blocks whose data collapsed - #[clap(long)] - scan: bool, + skip_to: Option, #[clap(subcommand)] command: Command, } @@ -24,13 +20,27 @@ enum Command { /// Number of the blocks to print. /// The excess will be truncated #[clap(short = 'n', long, default_value_t = 1)] - length: u64, + length: usize, }, - /// Listen for additions to the storage and report it - Follow, } -fn main() { +#[tokio::main] +#[allow(clippy::use_debug, clippy::print_stderr)] +async fn main() { let opts = Opts::parse(); - // kura_inspector::run() + let mut output = DefaultOutput::new(); + Config::from(opts) + .run(&mut output) + .await + .unwrap_or_else(|e| eprintln!("{:?}", e)) +} + +impl From for Config { + fn from(src: Opts) -> Self { + let Opts { skip_to, command } = src; + + match command { + Command::Print { length } => Config::Print(print::Config { skip_to, length }), + } + } } diff --git a/tools/kura_inspector/src/print.rs b/tools/kura_inspector/src/print.rs new file mode 100644 index 00000000000..b49e3631810 --- /dev/null +++ b/tools/kura_inspector/src/print.rs @@ -0,0 +1,176 @@ +//! Objects for the `print` subcommand. + +use futures_util::StreamExt; +use iroha_core::{kura, prelude::VersionedCommittedBlock}; + +use crate::Output; + +/// Configuration for the `print` subcommand. +#[derive(Clone, Copy)] +pub struct Config { + /// Height of the block up to which exclude from the printing. + /// `None` means excluding the all except the latest block. + pub skip_to: Option, + /// Number of the blocks to print. + /// The excess will be truncated. + pub length: usize, +} + +impl Config { + /// Read `block_store` and print the results to `output`. + /// + /// # Errors + /// Fails if + /// 1. Fails to read `block_store`. + /// 2. Fails to print to `output`. + /// 3. Tries to print the latest block and there is none. + pub async fn run( + &self, + block_store: &kura::BlockStore, + output: &mut Output, + ) -> Result<(), Error> + where + T: std::io::Write + Send, + E: std::io::Write + Send, + { + let block_stream = block_store + .read_all() + .await + .map_err(Box::new) + .map_err(Error::ReadBlockStore)?; + tokio::pin!(block_stream); + + if let Some(height) = self.skip_to { + let mut block_stream = block_stream.skip(height).take(self.length); + while let Some(block_result) = block_stream.next().await { + output.print(block_result).map_err(Error::Output)? + } + } else { + let mut last; + match block_stream.next().await { + Some(block_result) => { + last = block_result; + } + None => return Err(Error::NoBlock), + } + while let Some(block_result) = block_stream.next().await { + last = block_result; + } + output.print(last).map_err(Error::Output)? + } + Ok(()) + } +} + +impl Output +where + T: std::io::Write + Send, + E: std::io::Write + Send, +{ + #[allow(clippy::use_debug)] + fn print( + &mut self, + block_result: Result, + ) -> Result<(), std::io::Error> { + match block_result { + Ok(block) => writeln!(self.ok, "{:#?}", block), + Err(error) => writeln!(self.err, "{:#?}", error), + } + } +} + +#[derive(Debug)] +#[allow(missing_docs)] +pub enum Error { + ReadBlockStore(Box), + Output(std::io::Error), + NoBlock, +} + +#[cfg(test)] +#[allow(clippy::restriction)] +mod tests { + use std::io::Write; + + use iroha_core::prelude::ValidBlock; + + use super::*; + + type TestOutput = Output, Vec>; + const BLOCKS_PER_FILE: u64 = 3; + + impl TestOutput { + fn new() -> Self { + Self { + ok: Vec::new(), + err: Vec::new(), + } + } + } + + async fn block_store(dir: &tempfile::TempDir) -> kura::BlockStore { + kura::BlockStore::new( + dir.path(), + std::num::NonZeroU64::new(BLOCKS_PER_FILE).unwrap(), + kura::DefaultIO, + ) + .await + .unwrap() + } + + #[tokio::test] + /// Confirm that `print` command defaults to print the latest block. + async fn test_print_default() { + const BLOCK_COUNT: usize = 10; + + let dir = tempfile::tempdir().unwrap(); + let brock_store = block_store(&dir).await; + let mut output = TestOutput::new(); + + let mut tester = Vec::new(); + for height in 1..=BLOCK_COUNT { + let mut block: VersionedCommittedBlock = ValidBlock::new_dummy().commit().into(); + block.as_mut_v1().header.height = height as u64; + if BLOCK_COUNT == height { + writeln!(tester, "{:#?}", block).unwrap() + } + brock_store.write(&block).await.unwrap(); + } + let cfg = Config { + skip_to: None, + length: 1, + }; + cfg.run(&brock_store, &mut output).await.unwrap(); + + assert_eq!(tester, output.ok) + } + + #[tokio::test] + /// Confirm that `skip_to` and `length` options work properly. + async fn test_print_range() { + const BLOCK_COUNT: usize = 10; + const SKIP_TO: usize = 2; + const LENGTH: usize = 5; + + let dir = tempfile::tempdir().unwrap(); + let brock_store = block_store(&dir).await; + let mut output = TestOutput::new(); + + let mut tester = Vec::new(); + for height in 1..=BLOCK_COUNT { + let mut block: VersionedCommittedBlock = ValidBlock::new_dummy().commit().into(); + block.as_mut_v1().header.height = height as u64; + if (SKIP_TO + 1..=SKIP_TO + LENGTH).contains(&height) { + writeln!(tester, "{:#?}", block).unwrap() + } + brock_store.write(&block).await.unwrap(); + } + let cfg = Config { + skip_to: Some(SKIP_TO), + length: LENGTH, + }; + cfg.run(&brock_store, &mut output).await.unwrap(); + + assert_eq!(tester, output.ok) + } +}