Просмотр исходного кода

script/research/blockchain-explorer: added base statistics calls

skoupidi 2 лет назад
Родитель
Сommit
c680467899

+ 23 - 17
script/research/blockchain-explorer/src/blocks.rs

@@ -68,16 +68,16 @@ pub struct BlockRecord {
 impl BlockRecord {
     /// Auxiliary function to convert a `BlockRecord` into a `JsonValue` array.
     pub fn to_json_array(&self) -> JsonValue {
-        let mut ret = vec![];
-        ret.push(JsonValue::String(self.header_hash.clone()));
-        ret.push(JsonValue::Number(self.version as f64));
-        ret.push(JsonValue::String(self.previous.clone()));
-        ret.push(JsonValue::Number(self.height as f64));
-        ret.push(JsonValue::Number(self.timestamp as f64));
-        ret.push(JsonValue::Number(self.nonce as f64));
-        ret.push(JsonValue::String(self.root.clone()));
-        ret.push(JsonValue::String(format!("{:?}", self.signature)));
-        JsonValue::Array(ret)
+        JsonValue::Array(vec![
+            JsonValue::String(self.header_hash.clone()),
+            JsonValue::Number(self.version as f64),
+            JsonValue::String(self.previous.clone()),
+            JsonValue::Number(self.height as f64),
+            JsonValue::Number(self.timestamp as f64),
+            JsonValue::Number(self.nonce as f64),
+            JsonValue::String(self.root.clone()),
+            JsonValue::String(format!("{:?}", self.signature)),
+        ])
     }
 }
 
@@ -248,12 +248,12 @@ impl BlockchainExplorer {
         self.parse_block_record(&row)
     }
 
-    /// Fetch last block from the database.
-    pub async fn last_block(&self) -> WalletDbResult<u32> {
+    /// Fetch last block height from the database.
+    pub async fn last_block(&self) -> WalletDbResult<(u32, String)> {
         // First we prepare the query
         let query = format!(
-            "SELECT {} FROM {} ORDER BY {} DESC LIMIT 1;",
-            BLOCKS_COL_HEIGHT, BLOCKS_TABLE, BLOCKS_COL_HEIGHT
+            "SELECT {}, {} FROM {} ORDER BY {} DESC LIMIT 1;",
+            BLOCKS_COL_HEADER_HASH, BLOCKS_COL_HEIGHT, BLOCKS_TABLE, BLOCKS_COL_HEIGHT
         );
         let Ok(conn) = self.database.conn.lock() else {
             return Err(WalletDbError::FailedToAquireLock)
@@ -269,11 +269,17 @@ impl BlockchainExplorer {
         let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
         let row = match next {
             Some(row_result) => row_result,
-            None => return Ok(0_u32),
+            None => return Ok((0_u32, "".to_string())),
         };
 
-        // Parse returned value
+        // Parse returned values
         let Ok(value) = row.get(0) else { return Err(WalletDbError::ParseColumnValueError) };
+        let Value::Text(ref header_hash) = value else {
+            return Err(WalletDbError::ParseColumnValueError)
+        };
+        let header_hash = header_hash.clone();
+
+        let Ok(value) = row.get(1) else { return Err(WalletDbError::ParseColumnValueError) };
         let Value::Integer(height) = value else {
             return Err(WalletDbError::ParseColumnValueError)
         };
@@ -281,7 +287,7 @@ impl BlockchainExplorer {
             return Err(WalletDbError::ParseColumnValueError)
         };
 
-        Ok(height)
+        Ok((height, header_hash))
     }
 
     /// Auxiliary function to parse a `BLOCKS_TABLE` query rows into block records.

+ 4 - 0
script/research/blockchain-explorer/src/main.rs

@@ -47,6 +47,7 @@ mod error;
 mod rpc;
 mod rpc_blocks;
 use rpc_blocks::subscribe_blocks;
+mod rpc_statistics;
 mod rpc_transactions;
 
 /// Database functionality related to blocks
@@ -55,6 +56,9 @@ mod blocks;
 /// Database functionality related to transactions
 mod transactions;
 
+/// Database functionality related to statistics
+mod statistics;
+
 const CONFIG_FILE: &str = "blockchain_explorer_config.toml";
 const CONFIG_FILE_CONTENTS: &str = include_str!("../blockchain_explorer_config.toml");
 

+ 0 - 466
script/research/blockchain-explorer/src/main_old.rs

@@ -1,466 +0,0 @@
-/* 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 <https://www.gnu.org/licenses/>.
- */
-
-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<String>,
-
-    #[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<HeaderInfo>,
-    _sync: Vec<HeaderInfo>,
-}
-
-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<TransactionHash>,
-    _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<BlockInfo>,
-    _order: Vec<OrderInfo>,
-    _difficulty: Vec<BlockDifficultyInfo>,
-}
-
-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<TxInfo>,
-    _location: Vec<TxLocationInfo>,
-    _pending: Vec<TxInfo>,
-    _pending_order: Vec<PendingOrderInfo>,
-}
-
-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<blake3::Hash>,
-}
-
-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<ContractStateInfo>,
-    _wasm: Vec<WasmInfo>,
-}
-
-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(())
-}

+ 8 - 2
script/research/blockchain-explorer/src/rpc.rs

@@ -68,8 +68,14 @@ impl RequestHandler for BlockchainExplorer {
                 self.transactions_get_transaction_by_hash(req.id, req.params).await
             }
 
-            // TODO: add statistics retrieval method
-            // TODO: add any other usefull method
+            // =====================
+            // Statistics methods
+            // =====================
+            "statistics.get_basic_statistics" => {
+                self.statistics_get_basic_statistics(req.id, req.params).await
+            }
+
+            // TODO: add any other usefull methods
 
             // ==============
             // Invalid method

+ 2 - 2
script/research/blockchain-explorer/src/rpc_blocks.rs

@@ -59,7 +59,7 @@ impl BlockchainExplorer {
     /// If reset flag is provided, all tables are reset, and start syncing from beginning.
     pub async fn sync_blocks(&self, reset: bool) -> WalletDbResult<()> {
         // Grab last synced block height
-        let mut height = self.last_block().await?;
+        let (mut height, _) = self.last_block().await?;
         // If last synced block is genesis (0) or reset flag
         // has been provided we reset, otherwise continue with
         // the next block height
@@ -253,7 +253,7 @@ pub async fn subscribe_blocks(
         .darkfid_daemon_request("blockchain.last_known_block", &JsonValue::Array(vec![]))
         .await?;
     let last_known = *rep.get::<f64>().unwrap() as u32;
-    let last_synced = match explorer.last_block().await {
+    let (last_synced, _) = match explorer.last_block().await {
         Ok(l) => l,
         Err(e) => {
             return Err(Error::RusqliteError(format!(

+ 58 - 0
script/research/blockchain-explorer/src/rpc_statistics.rs

@@ -0,0 +1,58 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+use log::error;
+use tinyjson::JsonValue;
+
+use darkfi::rpc::jsonrpc::{
+    ErrorCode::{InternalError, InvalidParams},
+    JsonError, JsonResponse, JsonResult,
+};
+
+use crate::BlockchainExplorer;
+
+impl BlockchainExplorer {
+    // RPCAPI:
+    // Queries the database to retrieve current basic statistics.
+    // Returns the readable transaction upon success.
+    //
+    // **Params:**
+    // * `None`
+    //
+    // **Returns:**
+    // * `BaseStatistics` encoded into a JSON.
+    //
+    // --> {"jsonrpc": "2.0", "method": "statistics.get_basic_statistics", "params": [], "id": 1}
+    // <-- {"jsonrpc": "2.0", "result": {...}, "id": 1}
+    pub async fn statistics_get_basic_statistics(&self, id: u16, params: JsonValue) -> JsonResult {
+        let params = params.get::<Vec<JsonValue>>().unwrap();
+        if !params.is_empty() {
+            return JsonError::new(InvalidParams, None, id).into()
+        }
+
+        let base_statistics = match self.get_base_statistics().await {
+            Ok(v) => v,
+            Err(e) => {
+                error!(target: "blockchain-explorer::rpc_statistics::statistics_get_basic_statistics", "Failed fetching basic statistics: {}", e);
+                return JsonError::new(InternalError, None, id).into()
+            }
+        };
+
+        JsonResponse::new(base_statistics.to_json_array(), id).into()
+    }
+}

+ 96 - 0
script/research/blockchain-explorer/src/statistics.rs

@@ -0,0 +1,96 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+use rusqlite::types::Value;
+use tinyjson::JsonValue;
+
+use darkfi_sdk::blockchain::block_epoch;
+use drk::error::{WalletDbError, WalletDbResult};
+
+use crate::{blocks::BLOCKS_TABLE, transactions::TRANSACTIONS_TABLE, BlockchainExplorer};
+
+#[derive(Debug, Clone)]
+/// Structure representing basic statistic extracted from the database.
+pub struct BaseStatistics {
+    /// Current blockchain height
+    pub height: u32,
+    /// Current blockchain epoch (based on current height)
+    pub epoch: u8,
+    /// Blockchains' last block hash
+    pub last_block: String,
+    /// Blockchain total blocks
+    pub total_blocks: u64,
+    /// Blockchain total transactions
+    pub total_txs: u64,
+}
+
+impl BaseStatistics {
+    /// Auxiliary function to convert `BaseStatistics` into a `JsonValue` array.
+    pub fn to_json_array(&self) -> JsonValue {
+        JsonValue::Array(vec![
+            JsonValue::Number(self.height as f64),
+            JsonValue::Number(self.epoch as f64),
+            JsonValue::String(self.last_block.clone()),
+            JsonValue::Number(self.total_blocks as f64),
+            JsonValue::Number(self.total_txs as f64),
+        ])
+    }
+}
+
+impl BlockchainExplorer {
+    /// Fetch total rows count of given table from the database.
+    pub async fn get_table_count(&self, table: &str) -> WalletDbResult<u64> {
+        // First we prepare the query
+        let query = format!("SELECT COUNT() FROM {};", table);
+        let Ok(conn) = self.database.conn.lock() else {
+            return Err(WalletDbError::FailedToAquireLock)
+        };
+        let Ok(mut stmt) = conn.prepare(&query) else {
+            return Err(WalletDbError::QueryPreparationFailed)
+        };
+
+        // Execute the query using provided params
+        let Ok(mut rows) = stmt.query([]) else { return Err(WalletDbError::QueryExecutionFailed) };
+
+        // Check if row exists
+        let Ok(next) = rows.next() else { return Err(WalletDbError::QueryExecutionFailed) };
+        let row = match next {
+            Some(row_result) => row_result,
+            None => return Ok(0_u64),
+        };
+
+        // Parse returned value
+        let Ok(count) = row.get(0) else { return Err(WalletDbError::ParseColumnValueError) };
+        let Value::Integer(count) = count else { return Err(WalletDbError::ParseColumnValueError) };
+        let Ok(count) = u64::try_from(count) else {
+            return Err(WalletDbError::ParseColumnValueError)
+        };
+
+        Ok(count)
+    }
+
+    /// Fetch current database basic statistic.
+    pub async fn get_base_statistics(&self) -> WalletDbResult<BaseStatistics> {
+        let (height, last_block) = self.last_block().await?;
+        let epoch = block_epoch(height);
+        let total_blocks = self.get_table_count(BLOCKS_TABLE).await?;
+        let total_txs = self.get_table_count(TRANSACTIONS_TABLE).await?;
+
+        Ok(BaseStatistics { height, epoch, last_block, total_blocks, total_txs })
+    }
+}

+ 5 - 5
script/research/blockchain-explorer/src/transactions.rs

@@ -50,11 +50,11 @@ pub struct TransactionRecord {
 impl TransactionRecord {
     /// Auxiliary function to convert a `TransactionRecord` into a `JsonValue` array.
     pub fn to_json_array(&self) -> JsonValue {
-        let mut ret = vec![];
-        ret.push(JsonValue::String(self.transaction_hash.clone()));
-        ret.push(JsonValue::String(self.header_hash.clone()));
-        ret.push(JsonValue::String(format!("{:?}", self.payload)));
-        JsonValue::Array(ret)
+        JsonValue::Array(vec![
+            JsonValue::String(self.transaction_hash.clone()),
+            JsonValue::String(self.header_hash.clone()),
+            JsonValue::String(format!("{:?}", self.payload)),
+        ])
     }
 }