mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-08 22:28:12 -05:00
script/research/blockchain-explorer: added base statistics calls
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
script/research/blockchain-explorer/src/rpc_statistics.rs
Normal file
58
script/research/blockchain-explorer/src/rpc_statistics.rs
Normal file
@@ -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
script/research/blockchain-explorer/src/statistics.rs
Normal file
96
script/research/blockchain-explorer/src/statistics.rs
Normal file
@@ -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 })
|
||||
}
|
||||
}
|
||||
@@ -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)),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user