/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2024 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{fs::File, io::Write}; use clap::Parser; use darkfi::{ blockchain::{ block_store::{Block, BlockDifficulty, BlockRanks, BlockStore}, contract_store::ContractStore, header_store::{Header, HeaderHash, HeaderStore}, tx_store::TxStore, Blockchain, }, cli_desc, tx::Transaction, util::{path::expand_path, time::Timestamp}, Result, }; use darkfi_sdk::{ blockchain::block_epoch, crypto::{ContractId, MerkleNode}, tx::TransactionHash, }; use num_bigint::BigUint; #[derive(Parser)] #[command(about = cli_desc!())] struct Args { #[arg(short, long, default_value = "../../../contrib/localnet/darkfid-single-node/")] /// Path containing the node folders path: String, #[arg(short, long, default_values = ["darkfid"])] /// Node folder name (supports multiple values) node: Vec, #[arg(short, long, default_value = "")] /// Node blockchain folder blockchain: String, #[arg(short, long)] /// Export all contents into a JSON file export: bool, } #[derive(Debug)] struct HeaderInfo { _hash: HeaderHash, _version: u8, _previous: HeaderHash, _height: u32, _timestamp: Timestamp, _nonce: u64, _root: MerkleNode, } impl HeaderInfo { pub fn new(_hash: HeaderHash, header: &Header) -> HeaderInfo { HeaderInfo { _hash, _version: header.version, _previous: header.previous, _height: header.height, _timestamp: header.timestamp, _nonce: header.nonce, _root: header.root, } } } #[derive(Debug)] struct HeaderStoreInfo { _main: Vec, _sync: Vec, } impl HeaderStoreInfo { pub fn new(headerstore: &HeaderStore) -> HeaderStoreInfo { let mut _main = Vec::new(); let result = headerstore.get_all(); match result { Ok(iter) => { for (hash, header) in iter.iter() { _main.push(HeaderInfo::new(*hash, header)); } } Err(e) => println!("Error: {:?}", e), } let mut _sync = Vec::new(); let result = headerstore.get_all_sync(); match result { Ok(iter) => { for (_, header) in iter.iter() { _sync.push(HeaderInfo::new(header.hash(), header)); } } Err(e) => println!("Error: {:?}", e), } HeaderStoreInfo { _main, _sync } } } #[derive(Debug)] struct BlockInfo { _hash: HeaderHash, _header: HeaderHash, _txs: Vec, _signature: String, } impl BlockInfo { pub fn new(_hash: HeaderHash, block: &Block) -> BlockInfo { BlockInfo { _hash, _header: block.header, _txs: block.txs.clone(), _signature: format!("{:?}", block.signature), } } } #[derive(Debug)] struct OrderInfo { _height: u32, _hash: HeaderHash, } impl OrderInfo { pub fn new(_height: u32, _hash: HeaderHash) -> OrderInfo { OrderInfo { _height, _hash } } } #[derive(Debug)] struct BlockRanksInfo { _target_rank: BigUint, _targets_rank: BigUint, _hash_rank: BigUint, _hashes_rank: BigUint, } impl BlockRanksInfo { pub fn new(ranks: &BlockRanks) -> BlockRanksInfo { BlockRanksInfo { _target_rank: ranks.target_rank.clone(), _targets_rank: ranks.targets_rank.clone(), _hash_rank: ranks.hash_rank.clone(), _hashes_rank: ranks.hashes_rank.clone(), } } } #[derive(Debug)] struct BlockDifficultyInfo { _height: u32, _timestamp: Timestamp, _difficulty: BigUint, _cummulative_difficulty: BigUint, _ranks: BlockRanksInfo, } impl BlockDifficultyInfo { pub fn new(difficulty: &BlockDifficulty) -> BlockDifficultyInfo { BlockDifficultyInfo { _height: difficulty.height, _timestamp: difficulty.timestamp, _difficulty: difficulty.difficulty.clone(), _cummulative_difficulty: difficulty.cummulative_difficulty.clone(), _ranks: BlockRanksInfo::new(&difficulty.ranks), } } } #[derive(Debug)] struct BlockStoreInfo { _main: Vec, _order: Vec, _difficulty: Vec, } impl BlockStoreInfo { pub fn new(blockstore: &BlockStore) -> BlockStoreInfo { let mut _main = Vec::new(); let result = blockstore.get_all(); match result { Ok(iter) => { for (hash, block) in iter.iter() { _main.push(BlockInfo::new(*hash, block)); } } Err(e) => println!("Error: {:?}", e), } let mut _order = Vec::new(); let result = blockstore.get_all_order(); match result { Ok(iter) => { for (height, hash) in iter.iter() { _order.push(OrderInfo::new(*height, *hash)); } } Err(e) => println!("Error: {:?}", e), } let mut _difficulty = Vec::new(); let result = blockstore.get_all_difficulty(); match result { Ok(iter) => { for (_, difficulty) in iter.iter() { _difficulty.push(BlockDifficultyInfo::new(difficulty)); } } Err(e) => println!("Error: {:?}", e), } BlockStoreInfo { _main, _order, _difficulty } } } #[derive(Debug)] struct TxInfo { _hash: TransactionHash, _payload: Transaction, } impl TxInfo { pub fn new(_hash: TransactionHash, tx: &Transaction) -> TxInfo { TxInfo { _hash, _payload: tx.clone() } } } #[derive(Debug)] struct TxLocationInfo { _hash: TransactionHash, _block_height: u32, _index: u16, } impl TxLocationInfo { pub fn new(_hash: TransactionHash, _block_height: u32, _index: u16) -> TxLocationInfo { TxLocationInfo { _hash, _block_height, _index } } } #[derive(Debug)] struct PendingOrderInfo { _order: u64, _hash: TransactionHash, } impl PendingOrderInfo { pub fn new(_order: u64, _hash: TransactionHash) -> PendingOrderInfo { PendingOrderInfo { _order, _hash } } } #[derive(Debug)] struct TxStoreInfo { _main: Vec, _location: Vec, _pending: Vec, _pending_order: Vec, } impl TxStoreInfo { pub fn new(txstore: &TxStore) -> TxStoreInfo { let mut _main = Vec::new(); let result = txstore.get_all(); match result { Ok(iter) => { for (hash, tx) in iter.iter() { _main.push(TxInfo::new(*hash, tx)); } } Err(e) => println!("Error: {:?}", e), } let mut _location = Vec::new(); let result = txstore.get_all_location(); match result { Ok(iter) => { for (hash, location) in iter.iter() { _location.push(TxLocationInfo::new(*hash, location.0, location.1)); } } Err(e) => println!("Error: {:?}", e), } let mut _pending = Vec::new(); let result = txstore.get_all_pending(); match result { Ok(iter) => { for (hash, tx) in iter.iter() { _pending.push(TxInfo::new(*hash, tx)); } } Err(e) => println!("Error: {:?}", e), } let mut _pending_order = Vec::new(); let result = txstore.get_all_pending_order(); match result { Ok(iter) => { for (order, hash) in iter.iter() { _pending_order.push(PendingOrderInfo::new(*order, *hash)); } } Err(e) => println!("Error: {:?}", e), } TxStoreInfo { _main, _location, _pending, _pending_order } } } #[derive(Debug)] struct ContractStateInfo { _id: ContractId, _state_hashes: Vec, } impl ContractStateInfo { pub fn new(_id: ContractId, state_hashes: &[blake3::Hash]) -> ContractStateInfo { ContractStateInfo { _id, _state_hashes: state_hashes.to_vec() } } } #[derive(Debug)] struct WasmInfo { _id: ContractId, _bincode_hash: blake3::Hash, } impl WasmInfo { pub fn new(_id: ContractId, bincode: &[u8]) -> WasmInfo { let _bincode_hash = blake3::hash(bincode); WasmInfo { _id, _bincode_hash } } } #[derive(Debug)] struct ContractStoreInfo { _state: Vec, _wasm: Vec, } impl ContractStoreInfo { pub fn new(contractsstore: &ContractStore) -> ContractStoreInfo { let mut _state = Vec::new(); let result = contractsstore.get_all_states(); match result { Ok(iter) => { for (id, state_hash) in iter.iter() { _state.push(ContractStateInfo::new(*id, state_hash)); } } Err(e) => println!("Error: {:?}", e), } let mut _wasm = Vec::new(); let result = contractsstore.get_all_wasm(); match result { Ok(iter) => { for (id, bincode) in iter.iter() { _wasm.push(WasmInfo::new(*id, bincode)); } } Err(e) => println!("Error: {:?}", e), } ContractStoreInfo { _state, _wasm } } } #[derive(Debug)] struct BlockchainInfo { _headers: HeaderStoreInfo, _blocks: BlockStoreInfo, _transactions: TxStoreInfo, _contracts: ContractStoreInfo, } impl BlockchainInfo { pub fn new(blockchain: &Blockchain) -> BlockchainInfo { BlockchainInfo { _headers: HeaderStoreInfo::new(&blockchain.headers), _blocks: BlockStoreInfo::new(&blockchain.blocks), _transactions: TxStoreInfo::new(&blockchain.transactions), _contracts: ContractStoreInfo::new(&blockchain.contracts), } } } fn statistics(folder: &str, node: &str, blockchain: &str) -> Result<()> { println!("Retrieving blockchain statistics for {node}..."); // Node folder let folder = folder.to_owned() + node; // Initialize or load sled database let path = folder.to_owned() + blockchain; let db_path = expand_path(&path).unwrap(); let sled_db = sled::open(db_path)?; // Retrieve statistics let blockchain = Blockchain::new(&sled_db)?; let (height, block) = blockchain.last()?; let epoch = block_epoch(height); let blocks = blockchain.len(); let txs = blockchain.txs_len(); drop(sled_db); // Print statistics println!("Latest height: {height}"); println!("Epoch: {epoch}"); println!("Latest block: {block}"); println!("Total blocks: {blocks}"); println!("Total transactions: {txs}"); Ok(()) } fn export(folder: &str, node: &str, blockchain: &str) -> Result<()> { println!("Exporting data for {node}..."); // Node folder let folder = folder.to_owned() + node; // Initialize or load sled database let path = folder.to_owned() + blockchain; let db_path = expand_path(&path).unwrap(); let sled_db = sled::open(db_path)?; // Data export let blockchain = Blockchain::new(&sled_db)?; let info = BlockchainInfo::new(&blockchain); let info_string = format!("{:#?}", info); let file_name = node.to_owned() + "_db"; let mut file = File::create(file_name.clone())?; file.write_all(info_string.as_bytes())?; drop(sled_db); println!("Data exported to file: {file_name}"); Ok(()) } fn main() -> Result<()> { // Parse arguments let args = Args::parse(); println!("Node folder path: {}", args.path); // Export data for each node for node in args.node { if args.export { export(&args.path, &node, &args.blockchain)?; continue } statistics(&args.path, &node, &args.blockchain)?; } Ok(()) }