explorerd/service/
statistics.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use tinyjson::JsonValue;
20
21use darkfi::{Error, Result};
22use darkfi_sdk::blockchain::block_epoch;
23
24use crate::{service::ExplorerService, store::metrics::GasMetrics};
25
26#[derive(Debug, Clone)]
27/// Structure representing basic statistic extracted from the database.
28pub struct BaseStatistics {
29    /// Current blockchain height
30    pub height: u32,
31    /// Current blockchain epoch (based on current height)
32    pub epoch: u8,
33    /// Blockchains' last block hash
34    pub last_block: String,
35    /// Blockchain total blocks
36    pub total_blocks: usize,
37    /// Blockchain total transactions
38    pub total_txs: usize,
39}
40
41impl BaseStatistics {
42    /// Auxiliary function to convert `BaseStatistics` into a `JsonValue` array.
43    pub fn to_json_array(&self) -> JsonValue {
44        JsonValue::Array(vec![
45            JsonValue::Number(self.height as f64),
46            JsonValue::Number(self.epoch as f64),
47            JsonValue::String(self.last_block.clone()),
48            JsonValue::Number(self.total_blocks as f64),
49            JsonValue::Number(self.total_txs as f64),
50        ])
51    }
52}
53
54/// Structure representing metrics extracted from the database.
55#[derive(Default)]
56pub struct MetricStatistics {
57    /// Metrics used to store explorer statistics
58    pub metrics: GasMetrics,
59}
60
61impl MetricStatistics {
62    pub fn new(metrics: GasMetrics) -> Self {
63        Self { metrics }
64    }
65
66    /// Auxiliary function to convert [`MetricStatistics`] into a [`JsonValue`] array.
67    pub fn to_json_array(&self) -> JsonValue {
68        JsonValue::Array(vec![
69            JsonValue::Number(self.metrics.avg_total_gas_used() as f64),
70            JsonValue::Number(self.metrics.total_gas.min as f64),
71            JsonValue::Number(self.metrics.total_gas.max as f64),
72            JsonValue::Number(self.metrics.avg_wasm_gas_used() as f64),
73            JsonValue::Number(self.metrics.wasm_gas.min as f64),
74            JsonValue::Number(self.metrics.wasm_gas.max as f64),
75            JsonValue::Number(self.metrics.avg_zk_circuits_gas_used() as f64),
76            JsonValue::Number(self.metrics.zk_circuits_gas.min as f64),
77            JsonValue::Number(self.metrics.zk_circuits_gas.max as f64),
78            JsonValue::Number(self.metrics.avg_signatures_gas_used() as f64),
79            JsonValue::Number(self.metrics.signatures_gas.min as f64),
80            JsonValue::Number(self.metrics.signatures_gas.max as f64),
81            JsonValue::Number(self.metrics.avg_deployments_gas_used() as f64),
82            JsonValue::Number(self.metrics.deployments_gas.min as f64),
83            JsonValue::Number(self.metrics.deployments_gas.max as f64),
84            JsonValue::Number(self.metrics.timestamp.inner() as f64),
85        ])
86    }
87}
88impl ExplorerService {
89    /// Fetches the latest [`BaseStatistics`] from the explorer database, or returns `None` if no block exists.
90    pub fn get_base_statistics(&self) -> Result<Option<BaseStatistics>> {
91        let last_block = self.last_block();
92        Ok(last_block
93            // Throw database error if last_block retrievals fails
94            .map_err(|e| {
95                Error::DatabaseError(format!(
96                    "[get_base_statistics] Retrieving last block failed: {e:?}"
97                ))
98            })?
99            // Calculate base statistics and return result
100            .map(|(height, header_hash)| {
101                let epoch = block_epoch(height);
102                let total_blocks = self.get_block_count();
103                let total_txs = self.get_transaction_count();
104                BaseStatistics { height, epoch, last_block: header_hash, total_blocks, total_txs }
105            }))
106    }
107
108    /// Fetches the latest metrics from the explorer database, returning a vector of
109    /// [`MetricStatistics`] if found, or an empty Vec if no metrics exist.
110    pub async fn get_metrics_statistics(&self) -> Result<Vec<MetricStatistics>> {
111        // Fetch all metrics from the metrics store, handling any potential errors
112        let metrics = self.db.metrics_store.get_all_metrics().map_err(|e| {
113            Error::DatabaseError(format!(
114                "[get_metrics_statistics] Retrieving metrics failed: {e:?}"
115            ))
116        })?;
117
118        // Transform the fetched metrics into `MetricStatistics`, collect them into a vector
119        let metric_statistics =
120            metrics.iter().map(|metrics| MetricStatistics::new(metrics.clone())).collect();
121
122        Ok(metric_statistics)
123    }
124
125    /// Fetches the latest metrics from the explorer database, returning [`MetricStatistics`] if found,
126    /// or zero-initialized defaults when not.
127    pub async fn get_latest_metrics_statistics(&self) -> Result<MetricStatistics> {
128        // Fetch the latest metrics, handling any potential errors
129        match self.db.metrics_store.get_last().map_err(|e| {
130            Error::DatabaseError(format!(
131                "[get_metrics_statistics] Retrieving latest metrics failed: {e:?}"
132            ))
133        })? {
134            // Transform metrics into `MetricStatistics` when found
135            Some((_, metrics)) => Ok(MetricStatistics::new(metrics)),
136            // Return default statistics when no metrics exist
137            None => Ok(MetricStatistics::default()),
138        }
139    }
140}