Commit 8eb86397 authored by Lucas @ Ales's avatar Lucas @ Ales Committed by Lucas
Browse files

[feat] bc-db-reader: forks tree implemented and added in dbex

parent 322e0de0
Pipeline #8532 failed with stages
in 85 minutes and 35 seconds
......@@ -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,
......
......@@ -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();
......
......@@ -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
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment