diff --git a/lib/core/core/src/commands/dbex.rs b/lib/core/core/src/commands/dbex.rs index f406bb754ed43e665dd45b2fbe37c6e651745dd9..b41dead8ef3de526143d4566bc50cfab0d6f1c1e 100644 --- a/lib/core/core/src/commands/dbex.rs +++ b/lib/core/core/src/commands/dbex.rs @@ -47,6 +47,10 @@ pub enum DbExSubCommand { #[structopt(name = "distance", setting(structopt::clap::AppSettings::ColoredHelp))] DistanceOpt(DistanceOpt), /// Forks tree explorer + /// + /// Blocks are printed from the root to the newer ones (from top to bottom). Main branch blocks are printed on the left (the main branch is represented by ║ characters). A block is represented by the character # and is followed by its block stamp (block number + hash). + /// + /// By default, blocks inside a branch, those with only one child, are ommited and hashs are truncated to 10 characters. If some blocks are ommited, then the number of them is indicated between parantheses. In verbose mode, all blocks and complete hashs are printed. #[structopt(name = "forks", setting(structopt::clap::AppSettings::ColoredHelp))] ForksOpt(ForksOpt), /// Member explorer @@ -67,7 +71,11 @@ pub struct DistanceOpt { #[derive(StructOpt, Debug, Copy, Clone)] /// ForksOpt -pub struct ForksOpt {} +pub struct ForksOpt { + /// Print complete tree and complete hashs + #[structopt(short = "v", long = "verbose")] + pub verbose: bool, +} #[derive(StructOpt, Debug, Copy, Clone)] /// MembersOpt @@ -113,9 +121,11 @@ impl DursExecutableCoreCommand for DbExOpt { self.csv, &DbExQuery::WotQuery(DbExWotQuery::AllDistances(distance_opts.reverse)), ), - DbExSubCommand::ForksOpt(_forks_opts) => { - dbex(profile_path, self.csv, &DbExQuery::ForkTreeQuery) - } + DbExSubCommand::ForksOpt(forks_opts) => dbex( + profile_path, + self.csv, + &DbExQuery::ForkTreeQuery(forks_opts.verbose), + ), DbExSubCommand::MemberOpt(member_opts) => dbex( profile_path, self.csv, diff --git a/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs b/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs index c70cb88c4157d399bdf1157cca4835df87e99d85..b7226d38bcb6d7ad57ce6546495444a898345dbc 100644 --- a/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs +++ b/lib/modules-lib/bc-db-reader/src/blocks/fork_tree.rs @@ -18,8 +18,10 @@ use dubp_common_doc::{BlockHash, BlockNumber, Blockstamp}; use serde::de::{self, Deserializer, Visitor}; use serde::{Deserialize, Serialize, Serializer}; +use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::fmt; +use std::io::Write; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] /// unique identifier of tree node @@ -140,6 +142,170 @@ impl ForkTree { sheets: HashSet::new(), } } + + /// Auxilliary function of print. + /// Push children of a given node in the heap 'to_treat' so that the main branch child (if it exists) is pushed first. + fn print_aux_push_children( + &self, + to_treat: &mut Vec<(TreeNodeId, usize)>, + node_id: TreeNodeId, + node: &TreeNode, + offset: usize, + ) { + let mut to_treat_after = Vec::new(); + let mut child_main_branch_found = false; + + for &child_id in node.children.iter() { + if let Some(Some(child)) = self.nodes.get(child_id.0) { + if self.is_main_branch_node(child) { + to_treat.push((child_id, 0)); + child_main_branch_found = true; + if !self.is_main_branch_node(node) { + durs_common_tools::fatal_error!( + "Dev error: fork tree : block #{} ({:?}) is on main branch while it is a child of block #{} ({:?}) which is not on main branch.", + child.data.id, child_id, node.data.id, node_id + ); + } + } else { + to_treat_after.push(child_id); + } + } else { + durs_common_tools::fatal_error!( + "Dev error: fork tree : cannot get {:?} in tree nodes.", + child_id + ); + } + } + // treat other childs after the main branch child + for (i, &child_id) in to_treat_after.iter().enumerate() { + to_treat.push(( + child_id, + offset + i + (if child_main_branch_found { 1 } else { 0 }), + )); + } + } + + /// Print fork tree in writer + pub fn print<F>(&self, mut writer: F, verbose: bool) + where + F: Write, + { + if let Some(root_id) = self.root { + // Heap of nodes id to treat. The second parameter is the offset of the character # in the printing line + let mut to_treat = Vec::new(); + to_treat.push((root_id, 0)); + + while let Some((node_id, offset)) = to_treat.pop() { + if let Some(Some(node)) = self.nodes.get(node_id.0) { + // result string that will be written at the end of the process of node + let mut result_string = "".to_string(); + + // print informations on the node + let mut hash = node.data.hash.to_hex(); + if !verbose { + hash.truncate(10); + } + if offset > 0 { + result_string.push_str(&format!("║ {}", "│ ".repeat(offset - 1))); + } + result_string.push_str(&format!("# {}-{}\n", node.data.id, hash)); + + match node.children.len().cmp(&1) { + Ordering::Greater => { + // if node has > 1 childs, then print a fork + if offset == 0 { + result_string.push_str(&format!( + "╟{}─┐\n", + "─┬".repeat(node.children.len() - 2) + )); + } else { + result_string.push_str(&format!( + "║ {}├{}─┐\n", + "│ ".repeat(offset - 1), + "─┬".repeat(node.children.len() - 2) + )); + } + + // push children in to_treat + // push first the main branch child (if it exists) so that it will be on the left + self.print_aux_push_children(&mut to_treat, node_id, node, offset); + } + Ordering::Equal => { + // there is only one child + result_string += &format!("║ {}\n", "│ ".repeat(offset)); + + // non verbose case + // omit next childs with outdegree 1 only if there are > 1 of such type + if !verbose { + let mut father_id = node_id; + let mut next_child_id = None; + for &child_id in node.children.iter() { + next_child_id = Some(child_id); + } + let mut nb_omissions = 0; + + while let Some(child_id) = next_child_id { + if let Some(Some(child)) = self.nodes.get(child_id.0) { + if child.children.iter().count() != 1 { + // child has > 1 or == 0 subchildren + if nb_omissions == 1 { + to_treat.push((father_id, offset)); + } else { + to_treat.push((child_id, offset)); + } + break; + } else { + // child has 1 subchild + for &sub_child_id in child.children.iter() { + father_id = child_id; + next_child_id = Some(sub_child_id); + nb_omissions += 1; + } + } + } else { + durs_common_tools::fatal_error!( + "Dev error: fork tree : cannot get {:?} in tree nodes.", + child_id + ); + } + } + if nb_omissions > 1 { + if offset > 0 { + result_string += &format!("║ {}", "│ ".repeat(offset - 1)); + } + result_string += + &format!("┆ ({} blocks ommited)\n", nb_omissions); + result_string += &format!("║ {}\n", "│ ".repeat(offset)); + } + } else { + // verbose case + for (i, &child_id) in node.children.iter().enumerate() { + to_treat.push((child_id, offset + i)); + } + } + } + Ordering::Less => { + // if there is no child (node is a leaf) + if !to_treat.is_empty() { + // if there are still nodes to treat + result_string += &format!("║ {}\n", "│ ".repeat(offset - 1)); + } + } + } + + if writer.write(result_string.as_bytes()).is_err() { + durs_common_tools::fatal_error!("Print fork: write error"); + } + } else { + durs_common_tools::fatal_error!( + "Dev error: fork tree : cannot get {:?} in tree nodes.", + node_id + ); + } + } + } + } + /// Set max depth #[inline] pub fn set_max_depth(&mut self, max_depth: usize) { @@ -500,6 +666,28 @@ mod tests { use super::*; use dubp_currency_params::constants::DEFAULT_FORK_WINDOW_SIZE; + #[test] + fn print() { + let mut tree = ForkTree::default(); + let blockstamps = + dubp_blocks_tests_tools::mocks::generate_blockstamps(*DEFAULT_FORK_WINDOW_SIZE + 2); + + tree.insert_new_node(blockstamps[0], None, true); + tree.insert_new_node(blockstamps[1], Some(TreeNodeId(0)), false); + tree.insert_new_node(blockstamps[2], Some(TreeNodeId(0)), true); + tree.insert_new_node(blockstamps[3], Some(TreeNodeId(1)), false); + tree.insert_new_node(blockstamps[4], Some(TreeNodeId(3)), false); + tree.insert_new_node(blockstamps[5], Some(TreeNodeId(4)), false); + tree.insert_new_node(blockstamps[6], Some(TreeNodeId(5)), false); + tree.insert_new_node(blockstamps[7], Some(TreeNodeId(3)), false); + tree.insert_new_node(blockstamps[8], Some(TreeNodeId(3)), false); + tree.insert_new_node(blockstamps[9], Some(TreeNodeId(6)), false); + tree.insert_new_node(blockstamps[10], Some(TreeNodeId(2)), true); + tree.insert_new_node(blockstamps[11], Some(TreeNodeId(10)), true); + + tree.print(std::io::stdout(), false); + } + #[test] fn insert_root_nodes() { let mut tree = ForkTree::default(); diff --git a/lib/modules/blockchain/blockchain/src/dbex.rs b/lib/modules/blockchain/blockchain/src/dbex.rs index 2888c1f575946c756bf48017a75fd3dca8e9ce7a..2a00f623c01b8422a29d01c65abfe5f887c72f1e 100644 --- a/lib/modules/blockchain/blockchain/src/dbex.rs +++ b/lib/modules/blockchain/blockchain/src/dbex.rs @@ -87,7 +87,7 @@ pub enum DbExQuery { /// Blockchain query BcQuery(DbExBcQuery), /// Fork tree query - ForkTreeQuery, + ForkTreeQuery(bool), /// Tx query TxQuery(DbExTxQuery), /// Wot query @@ -114,7 +114,7 @@ fn open_bc_db_ro(profile_path: PathBuf) -> Option<BcDbRo> { /// Execute DbExQuery pub fn dbex(profile_path: PathBuf, csv: bool, query: &DbExQuery) { match *query { - DbExQuery::ForkTreeQuery => dbex_fork_tree(profile_path, csv), + DbExQuery::ForkTreeQuery(verbose) => dbex_fork_tree(profile_path, csv, verbose), DbExQuery::BcQuery(bc_query) => { dbex_bc(profile_path, csv, bc_query).expect("Error: fail to open DB.") } @@ -193,7 +193,7 @@ pub fn dbex_bc(profile_path: PathBuf, _csv: bool, _query: DbExBcQuery) -> Result } /// Print fork tree -pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool) { +pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool, verbose: bool) { // Open DB let load_db_begin = SystemTime::now(); let db = if let Some(db) = open_bc_db_ro(profile_path) { @@ -213,17 +213,7 @@ pub fn dbex_fork_tree(profile_path: PathBuf, _csv: bool) { .r(|db_r| durs_bc_db_reader::current_metadata::get_fork_tree(db_r)) .expect("fail to get fork tree"); // Print all fork branches - for (tree_node_id, blockstamp) in fork_tree.get_sheets() { - debug!( - "fork_tree.get_fork_branch({:?}, {})", - tree_node_id, blockstamp - ); - let branch = fork_tree.get_fork_branch(tree_node_id); - if !branch.is_empty() { - println!("Fork branch #{}:", blockstamp); - println!("{:#?}", branch); - } - } + fork_tree.print(std::io::stdout(), verbose); } /// Execute DbExTxQuery