metrics.rs 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362
  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. use std::{
  19. fmt,
  20. sync::{Arc, Mutex, MutexGuard},
  21. };
  22. use log::{debug, info};
  23. use sled_overlay::{sled, SledDbOverlay};
  24. use darkfi::{
  25. blockchain::SledDbOverlayPtr,
  26. util::time::{DateTime, Timestamp},
  27. validator::fees::GasData,
  28. Error, Result,
  29. };
  30. use darkfi_sdk::{num_traits::ToBytes, tx::TransactionHash};
  31. use darkfi_serial::{async_trait, deserialize, serialize, SerialDecodable, SerialEncodable};
  32. /// Gas metrics tree name.
  33. pub const SLED_GAS_METRICS_TREE: &[u8] = b"_gas_metrics";
  34. /// Gas metrics `by_height` tree that contains all metrics by height.
  35. pub const SLED_GAS_METRICS_BY_HEIGHT_TREE: &[u8] = b"_gas_metrics_by_height";
  36. /// Transaction gas data tree name.
  37. pub const SLED_TX_GAS_DATA_TREE: &[u8] = b"_tx_gas_data";
  38. /// The time interval for [`GasMetricsKey`]s in the main tree, specified in seconds.
  39. /// Metrics are stored in hourly intervals (3600 seconds), meaning all metrics accumulated
  40. /// within a specific hour are stored using a key representing the start of that hour.
  41. pub const GAS_METRICS_KEY_TIME_INTERVAL: u64 = 3600;
  42. #[derive(Debug, Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
  43. /// Represents metrics used to capture key statistical data.
  44. pub struct Metrics {
  45. /// An aggregate value that represents the sum of the metrics.
  46. pub sum: u64,
  47. /// The smallest value in the series of measured metrics.
  48. pub min: u64,
  49. /// The largest value in the series of measured metrics.
  50. pub max: u64,
  51. }
  52. // Temporarily disable unused warnings until the store is integrated with the explorer
  53. #[allow(dead_code)]
  54. impl Metrics {
  55. /// Constructs a [`Metrics`] instance with provided parameters.
  56. pub fn new(sum: u64, min: u64, max: u64) -> Self {
  57. Self { sum, min, max }
  58. }
  59. }
  60. /// Structure for managing gas metrics across all transactions in the store.
  61. ///
  62. /// This struct maintains running totals, extrema, and transaction counts to efficiently calculate
  63. /// metrics without the need to iterate through previous transactions when new data is added. It is used to build a
  64. /// comprehensive view of gas metrics across the blockchain's history, including total gas, WASM gas,
  65. /// ZK circuit gas, and signature gas. The structure allows for O(1) performance in calculating
  66. /// averages and updating min/max values.
  67. #[derive(Clone, Default, Eq, PartialEq, SerialEncodable, SerialDecodable)]
  68. pub struct GasMetrics {
  69. /// Represents the total count of transactions tracked by the metrics store.
  70. pub txs_count: u64,
  71. /// Overall gas consumed metrics across all transactions.
  72. pub total_gas: Metrics,
  73. /// Gas used across all executed wasm transactions.
  74. pub wasm_gas: Metrics,
  75. /// Gas consumed across all zk circuit computations.
  76. pub zk_circuits_gas: Metrics,
  77. /// Gas used metrics related to signatures across transactions.
  78. pub signatures_gas: Metrics,
  79. /// Gas consumed for deployments across transactions.
  80. pub deployments_gas: Metrics,
  81. /// The time the metrics was calculated
  82. pub timestamp: Timestamp,
  83. }
  84. // Temporarily disable unused warnings until the store is integrated with the explorer
  85. #[allow(dead_code)]
  86. impl GasMetrics {
  87. /// Creates a [`GasMetrics`] instance.
  88. pub fn new(
  89. txs_count: u64,
  90. total_gas: Metrics,
  91. wasm_gas: Metrics,
  92. zk_circuit_gas: Metrics,
  93. signature_gas: Metrics,
  94. deployment_gas: Metrics,
  95. timestamp: Timestamp,
  96. ) -> Self {
  97. Self {
  98. txs_count,
  99. total_gas,
  100. wasm_gas,
  101. zk_circuits_gas: zk_circuit_gas,
  102. signatures_gas: signature_gas,
  103. deployments_gas: deployment_gas,
  104. timestamp,
  105. }
  106. }
  107. /// Provides the average of the total gas used.
  108. pub fn avg_total_gas_used(&self) -> u64 {
  109. self.total_gas.sum.checked_div(self.txs_count).unwrap_or_default()
  110. }
  111. /// Provides the average of the gas used across WASM transactions.
  112. pub fn avg_wasm_gas_used(&self) -> u64 {
  113. self.wasm_gas.sum.checked_div(self.txs_count).unwrap_or_default()
  114. }
  115. /// Provides the average of the gas consumed across Zero-Knowledge Circuit computations.
  116. pub fn avg_zk_circuits_gas_used(&self) -> u64 {
  117. self.zk_circuits_gas.sum.checked_div(self.txs_count).unwrap_or_default()
  118. }
  119. /// Provides the average of the gas used to sign transactions.
  120. pub fn avg_signatures_gas_used(&self) -> u64 {
  121. self.signatures_gas.sum.checked_div(self.txs_count).unwrap_or_default()
  122. }
  123. /// Provides the average of the gas used for deployments.
  124. pub fn avg_deployments_gas_used(&self) -> u64 {
  125. self.deployments_gas.sum.checked_div(self.txs_count).unwrap_or_default()
  126. }
  127. /// Adds new [`GasData`] to the existing accumulated values.
  128. ///
  129. /// This method updates running totals, transaction counts, and min/max values
  130. /// for various gas metric categories. It accumulates new data without reading existing
  131. /// averages, minimums, or maximums from the database to optimize performance.
  132. pub fn add(&mut self, tx_gas_data: &[GasData]) {
  133. for gas_data in tx_gas_data {
  134. // Increment number of transactions included in stats
  135. self.txs_count += 1;
  136. // Update the statistics related to total gas
  137. self.total_gas.sum += gas_data.total_gas_used();
  138. // Update the statistics related to WASM gas
  139. self.wasm_gas.sum += gas_data.wasm;
  140. // Update the statistics related to ZK circuit gas
  141. self.zk_circuits_gas.sum += gas_data.zk_circuits;
  142. // Update the statistics related to signature gas
  143. self.signatures_gas.sum += gas_data.signatures;
  144. // Update the statistics related to deployment gas
  145. self.deployments_gas.sum += gas_data.deployments;
  146. if self.txs_count == 1 {
  147. // For the first transaction, set min/max to the transaction values
  148. self.total_gas.min = gas_data.total_gas_used();
  149. self.total_gas.max = gas_data.total_gas_used();
  150. self.wasm_gas.min = gas_data.wasm;
  151. self.wasm_gas.max = gas_data.wasm;
  152. self.zk_circuits_gas.min = gas_data.zk_circuits;
  153. self.zk_circuits_gas.max = gas_data.zk_circuits;
  154. self.signatures_gas.min = gas_data.signatures;
  155. self.signatures_gas.max = gas_data.signatures;
  156. self.deployments_gas.min = gas_data.deployments;
  157. self.deployments_gas.max = gas_data.deployments;
  158. return;
  159. }
  160. // For subsequent transactions, compare with min/max
  161. self.total_gas.min = self.total_gas.min.min(gas_data.total_gas_used());
  162. self.total_gas.max = self.total_gas.max.max(gas_data.total_gas_used());
  163. self.wasm_gas.min = self.wasm_gas.min.min(gas_data.wasm);
  164. self.wasm_gas.max = self.wasm_gas.max.max(gas_data.wasm);
  165. self.zk_circuits_gas.min = self.zk_circuits_gas.min.min(gas_data.zk_circuits);
  166. self.zk_circuits_gas.max = self.zk_circuits_gas.max.max(gas_data.zk_circuits);
  167. self.signatures_gas.min = self.signatures_gas.min.min(gas_data.signatures);
  168. self.signatures_gas.max = self.signatures_gas.max.max(gas_data.signatures);
  169. self.deployments_gas.min = self.deployments_gas.min.min(gas_data.deployments);
  170. self.deployments_gas.max = self.deployments_gas.max.max(gas_data.deployments);
  171. }
  172. }
  173. }
  174. /// Debug formatting support for [`GasMetrics`] instances to include averages.
  175. impl fmt::Debug for GasMetrics {
  176. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  177. f.debug_struct("GasMetrics")
  178. .field("txs_count", &self.txs_count)
  179. .field("avg_total_gas_used", &self.avg_total_gas_used())
  180. .field("avg_wasm_gas_used", &self.avg_wasm_gas_used())
  181. .field("avg_zk_circuits_gas_used", &self.avg_zk_circuits_gas_used())
  182. .field("avg_signatures_gas_used", &self.avg_signatures_gas_used())
  183. .field("avg_deployments_gas_used", &self.avg_deployments_gas_used())
  184. .field("total_gas", &format_args!("{:?}", self.total_gas))
  185. .field("wasm_gas", &format_args!("{:?}", self.wasm_gas))
  186. .field("zk_circuits_gas", &format_args!("{:?}", self.zk_circuits_gas))
  187. .field("signatures_gas", &format_args!("{:?}", self.signatures_gas))
  188. .field("deployments_gas", &format_args!("{:?}", self.deployments_gas))
  189. .field("timestamp", &self.timestamp)
  190. .finish()
  191. }
  192. }
  193. /// The `MetricStore` serves as the entry point for managing metrics,
  194. /// offering an API for fetching, inserting, and resetting metrics backed by a Sled database.
  195. ///
  196. /// It organizes data into separate Sled trees, including main storage for gas metrics by a defined time interval,
  197. /// a tree containing metrics by height for handling reorgs, and a transaction-specific gas data tree.
  198. /// Different keys, such as gas metric keys, block heights, and transaction hashes, are used to handle
  199. /// various use cases.
  200. ///
  201. /// The `MetricStore` utilizes an overlay pattern for write operations, allowing unified management of metrics,
  202. /// by internally delegating write-related actions like adding metrics and handling reorgs to [`MetricsStoreOverlay`].
  203. #[derive(Clone)]
  204. pub struct MetricsStore {
  205. /// Pointer to the underlying sled database used by the store and its associated overlay
  206. pub sled_db: sled::Db,
  207. /// Primary sled tree for storing gas metrics, utilizing [`GasMetricsKey`] as keys and
  208. /// serialized [`GasMetrics`] as values.
  209. pub main: sled::Tree,
  210. /// Sled tree for storing gas metrics by height, utilizing block `height` as keys
  211. /// and serialized [`GasMetrics`] as values.
  212. pub by_height: sled::Tree,
  213. /// Sled tree for storing transaction gas data, utilizing [`TransactionHash`] inner value as keys
  214. /// and serialized [`GasData`] as values.
  215. pub tx_gas_data: sled::Tree,
  216. }
  217. // Temporarily disable unused warnings until the store is integrated with the explorer
  218. #[allow(dead_code)]
  219. impl MetricsStore {
  220. /// Creates a [`MetricsStore`] instance by opening the necessary trees in the provided sled database [`Db`]
  221. pub fn new(db: &sled::Db) -> Result<Self> {
  222. let main = db.open_tree(SLED_GAS_METRICS_TREE)?;
  223. let tx_gas_data = db.open_tree(SLED_TX_GAS_DATA_TREE)?;
  224. let metrcs_by_height = db.open_tree(SLED_GAS_METRICS_BY_HEIGHT_TREE)?;
  225. Ok(Self { sled_db: db.clone(), main, tx_gas_data, by_height: metrcs_by_height })
  226. }
  227. /// Fetches [`GasMetrics`]s associated with the provided slice of [`GasMetricsKey`]s.
  228. pub fn get(&self, keys: &[GasMetricsKey]) -> Result<Vec<GasMetrics>> {
  229. let mut ret = Vec::with_capacity(keys.len());
  230. for key in keys {
  231. if let Some(metrics_bytes) = self.main.get(key.to_sled_key())? {
  232. let metrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  233. ret.push(metrics);
  234. }
  235. }
  236. Ok(ret)
  237. }
  238. /// Fetches [`GasMetrics`]s associated with the provided slice of [`u32`] heights.
  239. pub fn get_by_height(&self, heights: &[u32]) -> Result<Vec<GasMetrics>> {
  240. let mut ret = Vec::with_capacity(heights.len());
  241. for height in heights {
  242. if let Some(metrics_bytes) = self.by_height.get(height.to_be_bytes())? {
  243. let metrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  244. ret.push(metrics);
  245. }
  246. }
  247. Ok(ret)
  248. }
  249. /// Fetches the most recent [`GasMetrics`] and its associated [`GasMetricsKey`] from the main tree,
  250. /// returning `None` if no metrics are found.
  251. pub fn get_last(&self) -> Result<Option<(GasMetricsKey, GasMetrics)>> {
  252. self.main
  253. .last()?
  254. .map(|(key_bytes, metrics_bytes)| {
  255. // Deserialize gas metrics key and value
  256. let key = GasMetricsKey::from_sled_key(&key_bytes)?;
  257. let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  258. debug!(target: "explorerd::metrics_store::get_last", "Deserialized metrics at key {key}: {metrics:?}");
  259. Ok((key, metrics))
  260. })
  261. .transpose()
  262. }
  263. /// Fetches all [`GasMetrics`] from the main tree without corresponding key, returning an empty `Vec`
  264. /// if no metrics are found.
  265. pub fn get_all_metrics(&self) -> Result<Vec<GasMetrics>> {
  266. // Iterate through all metrics, deserialize each one, and collect results
  267. self.main
  268. .iter()
  269. .map(|iter_result| match iter_result {
  270. Ok((_, metrics_bytes)) => deserialize(&metrics_bytes).map_err(Error::from),
  271. Err(e) => Err(Error::from(e)),
  272. })
  273. .collect()
  274. }
  275. /// Fetches the most recent [`GasMetrics`] and its associated `height` from the `by_height` tree, returning `None` if no metrics are found.
  276. pub fn get_last_by_height(&self) -> Result<Option<(u32, GasMetrics)>> {
  277. self.by_height
  278. .last()?
  279. .map(|(height_bytes, metrics_bytes)| {
  280. // Deserialize height key and value
  281. let key_bytes: [u8; 4] = height_bytes.as_ref().try_into().unwrap();
  282. let height = u32::from_be_bytes(key_bytes);
  283. let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  284. debug!(target: "explorerd::metrics_store::get_last_by_height", "Deserialized metrics at height {height:?}: {metrics:?}");
  285. Ok((height, metrics))
  286. })
  287. .transpose()
  288. }
  289. /// Fetches the [`GasData`] associated with the provided [`TransactionHash`], or `None` if no gas data is found.
  290. pub fn get_tx_gas_data(&self, tx_hash: &TransactionHash) -> Result<Option<GasData>> {
  291. // Query transaction gas data tree using provided hash
  292. let opt = self.tx_gas_data.get(tx_hash.inner())?;
  293. // Deserialize gas data, map error if needed, return result
  294. opt.map(|value| deserialize(&value).map_err(Error::from)).transpose()
  295. }
  296. /// Adds gas metrics for a specific block of transactions to the store.
  297. ///
  298. /// This function takes block `height`, [`Timestamp`], with associated pairs of [`TransactionHash`] and [`GasData`],
  299. /// and updates the accumulated gas metrics in the store. It handles the storage of metrics for both regular use and
  300. /// blockchain reorganizations.
  301. ///
  302. /// Delegates operation to [`MetricsStoreOverlay::insert_gas_metrics`], whose documentation
  303. /// provides more details.
  304. pub fn insert_gas_metrics(
  305. &self,
  306. block_height: u32,
  307. block_timestamp: &Timestamp,
  308. tx_hashes: &[TransactionHash],
  309. tx_gas_data: &[GasData],
  310. ) -> Result<GasMetricsKey> {
  311. let overlay = MetricsStoreOverlay::new(self.sled_db.clone())?;
  312. overlay.insert_gas_metrics(block_height, block_timestamp, tx_hashes, tx_gas_data)
  313. }
  314. /// Resets the gas metrics in the store to a specified `height` [`u32`].
  315. ///
  316. /// This function reverts all gas metrics data after the given height, effectively
  317. /// undoing changes made beyond that point. It's useful for handling blockchain
  318. /// reorganizations.
  319. ///
  320. /// Delegates operation to [`MetricsStoreOverlay::reset_gas_metrics`], whose documentation
  321. /// provides more details.
  322. pub fn reset_gas_metrics(&self, height: u32) -> Result<()> {
  323. let overlay = MetricsStoreOverlay::new(self.sled_db.clone())?;
  324. overlay.reset_gas_metrics(height)
  325. }
  326. /// Checks if provided [`GasMetricsKey`] exists in the store's main tree.
  327. pub fn contains(&self, key: &GasMetricsKey) -> Result<bool> {
  328. Ok(self.main.contains_key(key.to_sled_key())?)
  329. }
  330. /// Provides the number of stored metrics in the main tree.
  331. pub fn len(&self) -> usize {
  332. self.main.len()
  333. }
  334. /// Provides the number of stored metrics by height.
  335. pub fn len_by_height(&self) -> usize {
  336. self.by_height.len()
  337. }
  338. /// Returns the number of transaction gas usage metrics stored.
  339. pub fn len_tx_gas_data(&self) -> usize {
  340. self.tx_gas_data.len()
  341. }
  342. /// Checks if there are any gas metrics stored.
  343. pub fn is_empty(&self) -> bool {
  344. self.main.is_empty()
  345. }
  346. /// Checks if transaction gas data metrics are stored.
  347. pub fn is_empty_tx_gas_data(&self) -> bool {
  348. self.tx_gas_data.is_empty()
  349. }
  350. }
  351. /// The `MetricsStoreOverlay` provides write operations for managing metrics in conjunction with the
  352. /// underlying sled database. It supports inserting new [`GasData`] into the stored accumulated metrics,
  353. /// adding transaction gas data, and reverting metric changes after a specified height.
  354. struct MetricsStoreOverlay {
  355. /// Pointer to the overlay used for accessing and performing database write operations to the store.
  356. overlay: SledDbOverlayPtr,
  357. /// Pointer managed by the [`MetricsStore`] that references the sled instance on which the overlay operates.
  358. db: sled::Db,
  359. }
  360. impl MetricsStoreOverlay {
  361. /// Instantiate a [`MetricsStoreOverlay`] over the provided [`SledDbPtr`] instance.
  362. pub fn new(db: sled::Db) -> Result<Self> {
  363. // Create overlay pointer
  364. let overlay = Arc::new(Mutex::new(SledDbOverlay::new(&db, vec![])));
  365. // Open trees
  366. overlay.lock().unwrap().open_tree(SLED_GAS_METRICS_TREE, true)?;
  367. overlay.lock().unwrap().open_tree(SLED_GAS_METRICS_BY_HEIGHT_TREE, true)?;
  368. overlay.lock().unwrap().open_tree(SLED_TX_GAS_DATA_TREE, true)?;
  369. Ok(Self { overlay: overlay.clone(), db })
  370. }
  371. /// Adds the provided [`TransactionHash`] and [`GasData`] pairs to the accumulated [`GasMetrics`]
  372. /// in the store's [`SLED_GAS_METRICS_BY_HEIGHT_TREE`] and [`SLED_GAS_METRICS_TREE`] trees, while
  373. /// also storing transaction gas data in the [`SLED_TX_GAS_DATA_TREE`], committing all changes upon success.
  374. ///
  375. /// This function retrieves the latest recorded metrics, updates them with the new gas data, and
  376. /// stores the accumulated result. It uses the provided `block_timestamp` to create a normalied time-sequenced
  377. /// [`GasMetricsKey`] for metrics storage. The `block_height` is used as a key to store metrics by height
  378. /// which are used to handle chain reorganizations. After updating the aggregate metrics, it stores
  379. /// the transaction gas data for each transaction in the block.
  380. ///
  381. /// Returns the created [`GasMetricsKey`] that can be used to retrieve the metric upon success.
  382. pub fn insert_gas_metrics(
  383. &self,
  384. block_height: u32,
  385. block_timestamp: &Timestamp,
  386. tx_hashes: &[TransactionHash],
  387. tx_gas_data: &[GasData],
  388. ) -> Result<GasMetricsKey> {
  389. // Ensure lengths of tx_hashes and gas_data arrays match
  390. if tx_hashes.len() != tx_gas_data.len() {
  391. return Err(Error::Custom(String::from(
  392. "The lengths of tx_hashes and gas_data arrays must match",
  393. )));
  394. }
  395. // Ensure gas data is provided
  396. if tx_gas_data.is_empty() {
  397. return Err(Error::Custom(String::from("No transaction gas data was provided")));
  398. }
  399. // Lock the database
  400. let mut lock = self.overlay.lock().unwrap();
  401. // Retrieve latest recorded metrics, returning default if not exist
  402. let mut metrics = match self.get_last_by_height(&mut lock)? {
  403. None => GasMetrics::default(),
  404. Some((_, metrics)) => metrics,
  405. };
  406. // Update the accumulated metrics with the provided transaction gas data
  407. metrics.add(tx_gas_data);
  408. // Update the time that the metrics was recorded
  409. metrics.timestamp = *block_timestamp;
  410. // Insert metrics by height
  411. self.insert_by_height(&[block_height], &[metrics.clone()], &mut lock)?;
  412. // Create metrics key based on block_timestamp
  413. let metrics_key = GasMetricsKey::new(block_timestamp)?;
  414. // Normalize metric timestamp based on the key's time interval
  415. metrics.timestamp = GasMetricsKey::normalize_timestamp(block_timestamp)?;
  416. // Insert the gas metrics using metrics key
  417. self.insert(&[metrics_key.clone()], &[metrics], &mut lock)?;
  418. // Insert the transaction gas data for each transaction in the block
  419. self.insert_tx_gas_data(tx_hashes, tx_gas_data, &mut lock)?;
  420. // Commit the changes
  421. lock.apply()?;
  422. Ok(metrics_key)
  423. }
  424. /// Inserts [`TransactionHash`] and [`GasData`] pairs into the store's [`SLED_TX_GAS_DATA_TREE`],
  425. /// committing the changes upon success.
  426. ///
  427. /// This function locks the overlay, verifies that the tx_hashes and gas_data arrays have matching lengths,
  428. /// then inserts them into the store while handling serialization and potential errors. Returns a
  429. /// successful result upon success.
  430. fn insert_tx_gas_data(
  431. &self,
  432. tx_hashes: &[TransactionHash],
  433. gas_data: &[GasData],
  434. lock: &mut MutexGuard<SledDbOverlay>,
  435. ) -> Result<()> {
  436. // Ensure lengths of tx_hashes and gas_data arrays match
  437. if tx_hashes.len() != gas_data.len() {
  438. return Err(Error::Custom(String::from(
  439. "The lengths of tx_hashes and gas_data arrays must match",
  440. )));
  441. }
  442. // Insert each transaction hash and gas data pair
  443. for (tx_hash, gas_data) in tx_hashes.iter().zip(gas_data.iter()) {
  444. // Serialize the gas data
  445. let serialized_gas_data = serialize(gas_data);
  446. // Insert serialized gas data
  447. lock.insert(SLED_TX_GAS_DATA_TREE, tx_hash.inner(), &serialized_gas_data)?;
  448. info!(target: "explorerd::metrics_store::insert_tx_gas_data", "Inserted gas data for transaction {}: {gas_data:?}", tx_hash);
  449. }
  450. Ok(())
  451. }
  452. /// Resets gas metrics in the [`SLED_GAS_METRICS_TREE`] and [`SLED_GAS_METRICS_BY_HEIGHT_TREE`]
  453. /// to a specified block height, undoing all entries after provided height and committing the
  454. /// changes upon success.
  455. ///
  456. /// This function first obtains a lock on the overlay, then reverts changes by calling
  457. /// [`Self::revert_by_height_metrics`] and [`Self::revert_metrics`]. Upon successful revert,
  458. /// all modifications made after the specified height are permanently reverted.
  459. pub fn reset_gas_metrics(&self, height: u32) -> Result<()> {
  460. // Obtain lock
  461. let mut lock = self.overlay.lock().unwrap();
  462. // Revert the metrics by height
  463. self.revert_by_height_metrics(height, &mut lock)?;
  464. // Revert the main metrics entries now that `by_height` tree is reset
  465. self.revert_metrics(&mut lock)?;
  466. // Commit the changes
  467. lock.apply()?;
  468. Ok(())
  469. }
  470. /// Inserts [`GasMetricsKey`] and [`GasMetrics`] pairs into the store's [`SLED_GAS_METRICS_TREE`].
  471. ///
  472. /// This function verifies that the provided keys and metrics arrays have matching lengths,
  473. /// then inserts each pair while handling serialization. Returns a successful result
  474. /// if all insertions are completed without errors.
  475. fn insert(
  476. &self,
  477. keys: &[GasMetricsKey],
  478. metrics: &[GasMetrics],
  479. lock: &mut MutexGuard<SledDbOverlay>,
  480. ) -> Result<()> {
  481. // Ensure lengths of keys and metrics match
  482. if keys.len() != metrics.len() {
  483. return Err(Error::Custom(String::from(
  484. "The lengths of keys and metrics arrays must match",
  485. )));
  486. }
  487. // Insert each metric corresponding to respective gas metrics key
  488. for (key, metric) in keys.iter().zip(metrics.iter()) {
  489. // Insert metric
  490. lock.insert(SLED_GAS_METRICS_TREE, &key.to_sled_key(), &serialize(metric))?;
  491. info!(target: "explorerd::metrics_store::insert", "Added gas metrics using key {key}: {metric:?}");
  492. }
  493. Ok(())
  494. }
  495. /// Inserts provided [`u32`] height and [`GasMetrics`] pairs into the store's [`SLED_GAS_METRICS_BY_HEIGHT_TREE`].
  496. ///
  497. /// This function verifies matching lengths of provided heights and metrics arrays,
  498. /// and inserts each pair while handling serialization and errors. Returns a successful result
  499. /// if all insertions are completed without errors.
  500. fn insert_by_height(
  501. &self,
  502. heights: &[u32],
  503. metrics: &[GasMetrics],
  504. lock: &mut MutexGuard<SledDbOverlay>,
  505. ) -> Result<()> {
  506. // Ensure lengths of heights and metrics match
  507. if heights.len() != metrics.len() {
  508. return Err(Error::Custom(String::from(
  509. "The lengths of heights and metrics arrays must match",
  510. )));
  511. }
  512. // Insert each metric corresponding to respective height
  513. for (height, metric) in heights.iter().zip(metrics.iter()) {
  514. // Serialize the metric and handle potential errors
  515. let serialized_metric = serialize(metric);
  516. // Insert the serialized metric
  517. lock.insert(
  518. SLED_GAS_METRICS_BY_HEIGHT_TREE,
  519. &height.to_be_bytes(),
  520. &serialized_metric,
  521. )?;
  522. info!(target: "explorerd::metrics_store::insert_by_height", "Added gas metrics using height {height}: {metric:?}");
  523. }
  524. Ok(())
  525. }
  526. /// This function reverts gas metric entries in the [`SLED_GAS_METRICS_TREE`] to align
  527. /// with the latest metrics state in the [`SLED_GAS_METRICS_BY_HEIGHT_TREE`].
  528. ///
  529. /// It first determines the target timestamp to revert to based on the latest entry
  530. /// in the by_height tree timestamp. Then, it iteratively removes entries from the main metrics
  531. /// tree that are newer than the target timestamp. Once all that is complete, it adds the latest
  532. /// metrics by height to the main metrics tree, returning a successful result if revert processes
  533. /// without error.
  534. fn revert_metrics(&self, lock: &mut MutexGuard<SledDbOverlay>) -> Result<()> {
  535. /*** Determine Metrics To Revert ***/
  536. // Get the last metrics by height and determine the target timestamp to revert to
  537. let latest_by_height = self.get_last_by_height(lock)?;
  538. let target_timestamp = match &latest_by_height {
  539. None => 0,
  540. Some((_, metrics)) => GasMetricsKey::normalize_timestamp(&metrics.timestamp)?.inner(),
  541. };
  542. // Get the timestamp of the latest metrics entry in the metrics store
  543. let mut current_timestamp = match self.get_last(lock)? {
  544. None => return Ok(()),
  545. Some((_, metrics)) => metrics.timestamp.inner(),
  546. };
  547. /*** Revert Main Tree Gas Metrics ***/
  548. // Iterate through at most the total number of gas metric tree entries
  549. for _ in 0..self.db.open_tree(SLED_GAS_METRICS_TREE)?.len() {
  550. // Stop the loop if the current timestamp is less than or equal to the target timestamp,
  551. // as there are no more entries to revert
  552. if current_timestamp <= target_timestamp {
  553. break;
  554. }
  555. // Create a `GasMetricsKey` for the current timestamp to locate the entry to be reverted.
  556. let key_to_revert = GasMetricsKey::new(current_timestamp)?;
  557. // Remove the corresponding entry from the gas metrics tree.
  558. lock.remove(SLED_GAS_METRICS_TREE, &key_to_revert.to_sled_key())?;
  559. info!(target: "explorerd:metrics_store:revert_metrics", "Successfully reverted metrics with key: {}", key_to_revert);
  560. // Move to the previous valid timestamp by subtracting the defined time interval
  561. current_timestamp = current_timestamp.saturating_sub(GAS_METRICS_KEY_TIME_INTERVAL);
  562. }
  563. /*** Add the Latest Reverted Metrics To Main Tree ***/
  564. // Retrieve the latest metrics from the `by_height` tree and normalize its timestamp so it can be added to the main tree.
  565. // If there are no metrics in the `by_height` tree, we may have reset to 0, so return as there is nothing add.
  566. let latest_metrics = match latest_by_height {
  567. None => return Ok(()),
  568. Some((_, mut metrics)) => {
  569. metrics.timestamp = GasMetricsKey::normalize_timestamp(&metrics.timestamp)?;
  570. metrics
  571. }
  572. };
  573. // Add the latest metrics to the main tree based on latest reverted metrics by height
  574. let gas_metrics_key = GasMetricsKey::new(&latest_metrics.timestamp)?;
  575. self.insert(&[gas_metrics_key], &[latest_metrics], lock)?;
  576. Ok(())
  577. }
  578. /// Reverts gas metric entries from [`SLED_GAS_METRICS_BY_HEIGHT_TREE`] to provided `height`.
  579. ///
  580. /// This function iterates through the entries in gas metrics by height tree and removes all entries
  581. /// with heights greater than the specified `height`, effectively reverting all gas metrics beyond that point.
  582. fn revert_by_height_metrics(
  583. &self,
  584. height: u32,
  585. lock: &mut MutexGuard<SledDbOverlay>,
  586. ) -> Result<()> {
  587. // Retrieve the last stored block height
  588. let (last_height, _) = match self.get_last_by_height(lock)? {
  589. None => return Ok(()),
  590. Some(v) => v,
  591. };
  592. // Return early if the requested height is after the last stored height
  593. if height >= last_height {
  594. return Ok(());
  595. }
  596. // Remove keys greater than `height`
  597. while let Some((cur_height_bytes, _)) = lock.last(SLED_GAS_METRICS_BY_HEIGHT_TREE)? {
  598. // Convert height bytes to u32
  599. let cur_height = u32::from_be_bytes(cur_height_bytes.as_ref().try_into()?);
  600. // Process all heights that are bigger than provided `height`
  601. if cur_height <= height {
  602. break;
  603. }
  604. // Remove height being reverted
  605. lock.remove(SLED_GAS_METRICS_BY_HEIGHT_TREE, &cur_height_bytes)?;
  606. info!(target: "explorerd:metrics_store:revert_by_height_metrics", "Successfully reverted metrics with height: {}", cur_height);
  607. }
  608. Ok(())
  609. }
  610. /// Fetches the most recent gas metrics from [`SLED_GAS_METRICS_TREE`], returning an option
  611. /// containing a metrics key [`GasMetricsKey`] and [`GasMetrics`] pair, or `None` if no metrics exist.
  612. fn get_last(
  613. &self,
  614. lock: &mut MutexGuard<SledDbOverlay>,
  615. ) -> Result<Option<(GasMetricsKey, GasMetrics)>> {
  616. // Fetch and deserialize key and metric pair
  617. lock.last(SLED_GAS_METRICS_TREE)?
  618. .map(|(key_bytes, metrics_bytes)| {
  619. // Deserialize the metrics key
  620. let key = GasMetricsKey::from_sled_key(&key_bytes)?;
  621. // Deserialize the stored gas metrics
  622. let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  623. Ok((key, metrics))
  624. })
  625. .transpose()
  626. }
  627. /// Fetches the most recent gas metrics from [`SLED_GAS_METRICS_BY_HEIGHT_TREE`], returning an option
  628. /// containing a height [`u32`] and [`GasMetrics`] pair, or `None` if no metrics exist.
  629. fn get_last_by_height(
  630. &self,
  631. lock: &mut MutexGuard<SledDbOverlay>,
  632. ) -> Result<Option<(u32, GasMetrics)>> {
  633. // Fetch and deserialize height and metric pair
  634. lock.last(SLED_GAS_METRICS_BY_HEIGHT_TREE)?
  635. .map(|(height_bytes, metrics_bytes)| {
  636. // Deserialize the height
  637. let key_bytes: [u8; 4] = height_bytes.as_ref().try_into().unwrap();
  638. let height = u32::from_be_bytes(key_bytes);
  639. // Deserialize the stored gas metrics
  640. let metrics: GasMetrics = deserialize(&metrics_bytes).map_err(Error::from)?;
  641. Ok((height, metrics))
  642. })
  643. .transpose()
  644. }
  645. }
  646. /// Represents a key used to store and fetch metrics in the metrics store.
  647. ///
  648. /// This struct provides methods for creating, serializing, and deserializing gas metrics keys.
  649. /// It supports creation from various time representations through the [`GasMetricsKeySource`] trait
  650. /// and offers conversion methods for use with a sled database.
  651. #[derive(Debug, Eq, PartialEq, Clone)]
  652. pub struct GasMetricsKey(pub DateTime);
  653. impl GasMetricsKey {
  654. /// Creates a new [`GasMetricsKey`] from a source that implements [`GasMetricsKeySource`].
  655. /// Depending on the use case, the key supports different input sources such as `Timestamp`, `u64` timestamp,
  656. /// or `&str` timestamp to create the key.
  657. pub fn new<T: GasMetricsKeySource>(source: T) -> Result<GasMetricsKey> {
  658. source.to_key()
  659. }
  660. /// Gets the inner [`DateTime`] value.
  661. pub fn inner(&self) -> &DateTime {
  662. &self.0
  663. }
  664. /// Converts the [`GasMetricsKey`] into a key suitable for use with a sled database.
  665. pub fn to_sled_key(&self) -> Vec<u8> {
  666. // Create a new vector with a capacity of 28 bytes
  667. let mut sled_key = Vec::with_capacity(28);
  668. // Push the byte representations of each field into the vector
  669. sled_key.extend_from_slice(&self.inner().year.to_be_bytes());
  670. sled_key.extend_from_slice(&self.inner().month.to_be_bytes());
  671. sled_key.extend_from_slice(&self.inner().day.to_be_bytes());
  672. sled_key.extend_from_slice(&self.inner().hour.to_be_bytes());
  673. sled_key.extend_from_slice(&self.inner().min.to_be_bytes());
  674. sled_key.extend_from_slice(&self.inner().sec.to_be_bytes());
  675. sled_key.extend_from_slice(&self.inner().nanos.to_be_bytes());
  676. // Return sled key
  677. sled_key
  678. }
  679. /// Converts a `sled` key into a [`GasMetricsKey`] by deserializing a slice of bytes.
  680. pub fn from_sled_key(bytes: &[u8]) -> Result<Self> {
  681. if bytes.len() != 28 {
  682. return Err(Error::Custom(String::from("Invalid byte length for GasMetricsKey")));
  683. }
  684. // Deserialize byte representations into each field
  685. let key = DateTime {
  686. year: u32::from_be_bytes(bytes[0..4].try_into()?),
  687. month: u32::from_be_bytes(bytes[4..8].try_into()?),
  688. day: u32::from_be_bytes(bytes[8..12].try_into()?),
  689. hour: u32::from_be_bytes(bytes[12..16].try_into()?),
  690. min: u32::from_be_bytes(bytes[16..20].try_into()?),
  691. sec: u32::from_be_bytes(bytes[20..24].try_into()?),
  692. nanos: u32::from_be_bytes(bytes[24..28].try_into()?),
  693. };
  694. Ok(Self(key))
  695. }
  696. /// Normalizes the given [`DateTime`] to the start of hour.
  697. pub fn normalize_date_time(date_time: DateTime) -> DateTime {
  698. DateTime {
  699. nanos: 0,
  700. sec: 0,
  701. min: 0,
  702. hour: date_time.hour,
  703. day: date_time.day,
  704. month: date_time.month,
  705. year: date_time.year,
  706. }
  707. }
  708. /// Normalizes a given [`Timestamp`] to the start of the hour.
  709. pub fn normalize_timestamp(timestamp: &Timestamp) -> Result<Timestamp> {
  710. let remainder = timestamp.inner() % GAS_METRICS_KEY_TIME_INTERVAL;
  711. timestamp.checked_sub(Timestamp::from_u64(remainder))
  712. }
  713. }
  714. impl fmt::Display for GasMetricsKey {
  715. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
  716. write!(f, "{}", self.inner())
  717. }
  718. }
  719. /// Provides a unified method for creating new instances of GasMetricKeys using
  720. /// various time representations: [`Timestamp`], `u64` timestamp, or `&str` timestamp.
  721. pub trait GasMetricsKeySource {
  722. fn to_key(&self) -> Result<GasMetricsKey>;
  723. }
  724. /// Implements [`GasMetricsKeySource`] for &[`Timestamp`], converting it to a [`GasMetricsKey`].
  725. impl GasMetricsKeySource for &Timestamp {
  726. fn to_key(&self) -> Result<GasMetricsKey> {
  727. let date_time = DateTime::from_timestamp(self.inner(), 0);
  728. Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
  729. }
  730. }
  731. /// Implements [`GasMetricsKeySource`] for `u64`, converting it to a [`GasMetricsKey`].
  732. impl GasMetricsKeySource for u64 {
  733. fn to_key(&self) -> Result<GasMetricsKey> {
  734. let date_time = DateTime::from_timestamp(*self, 0);
  735. Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
  736. }
  737. }
  738. /// Implements [`GasMetricsKeySource`] for string slices, converting a `&str` in the `YYYY-MM-DD HH:mm:ss UTC` format
  739. /// to a [`GasMetricsKey`]. Returns an [`Error::ParseFailed`] error if the provided timestamp string slice is invalid.
  740. impl GasMetricsKeySource for &str {
  741. fn to_key(&self) -> Result<GasMetricsKey> {
  742. let date_time = DateTime::from_timestamp_str(self)?;
  743. Ok(GasMetricsKey(GasMetricsKey::normalize_date_time(date_time)))
  744. }
  745. }
  746. #[cfg(test)]
  747. /// This test module verifies the correct insertion, retrieval, and reset of metrics in the store.
  748. /// It covers adding metrics, searching metrics by time and transaction hash, and resetting metrics with specified heights.
  749. mod tests {
  750. use darkfi::util::time::DateTime;
  751. use std::{
  752. str::FromStr,
  753. time::{Duration, SystemTime, UNIX_EPOCH},
  754. };
  755. use structopt::lazy_static::lazy_static;
  756. use super::*;
  757. use crate::test_utils::init_logger;
  758. /// Number of heights to simulate.
  759. const HEIGHT: u32 = 10;
  760. /// Fixed timestamp in seconds since UNIX epoch.
  761. const FIXED_TIMESTAMP: u64 = 1732042800;
  762. /// [`FIXED_TIMESTAMP`] timestamp as a string in UTC format.
  763. const FIXED_TIMESTAMP_STR: &str = "2024-11-19T19:00:00";
  764. lazy_static! {
  765. /// Test transaction hash.
  766. pub static ref TX_HASH: TransactionHash = TransactionHash::from_str(
  767. "92225ff00a3755d8df93c626b59f6e36cf021d85ebccecdedc38f3f1890a15fc"
  768. ).expect("Invalid transaction hash");
  769. }
  770. /// Tests inserting gas metrics, verifying the correctness of stored metrics.
  771. #[test]
  772. fn test_insert_gas_metrics() -> Result<()> {
  773. // Declare constants used for test
  774. const EXPECTED_HEIGHT: usize = HEIGHT as usize - 1;
  775. // Setup test, returning initialized metrics store
  776. let store = setup()?;
  777. // Load test data into the store and get the expected metrics results
  778. let test_data = load_random_metrics(&store, |_, _| {})?;
  779. // Verify metrics were inserted with the expected counts
  780. assert_eq!(store.len(), EXPECTED_HEIGHT);
  781. // Process height 0 test data separately
  782. let mut test_data_iter = test_data.iter();
  783. // For height 0, confirm there are no metrics stored in the store
  784. if let Some(test_data_height0) = test_data_iter.next() {
  785. let actual_height0 = store.get(&[GasMetricsKey::new(&test_data_height0.timestamp)?])?;
  786. assert!(
  787. actual_height0.is_empty(),
  788. "Timestamp associated with height 0 should not have any metrics stored"
  789. );
  790. }
  791. // Process remaining test data, verifying that each stored metric matches expected results
  792. for expected in test_data_iter {
  793. let actual = store.get(&[GasMetricsKey::new(&expected.timestamp)?])?;
  794. let expected_normalized = normalize_metrics_timestamp(expected)?;
  795. assert_eq!(&expected_normalized, &actual[0]);
  796. }
  797. Ok(())
  798. }
  799. /// Tests inserting gas metrics into the `by_height` tree, verifying the correctness of stored metrics.
  800. #[test]
  801. fn test_insert_by_height_gas_metrics() -> Result<()> {
  802. // Declare constants used for test
  803. const EXPECTED_HEIGHT: usize = HEIGHT as usize - 1;
  804. // Setup test, returning initialized metrics store
  805. let store = setup()?;
  806. // Load test data into the store and get the expected metrics results
  807. let test_data = load_random_metrics(&store, |_, _| {})?;
  808. // Verify metrics were inserted with the expected counts
  809. assert_eq!(store.len(), EXPECTED_HEIGHT);
  810. // For height 0, confirm there are no metrics stored in metrics by height
  811. let actual_height0 = store.get_by_height(&[0])?;
  812. assert!(actual_height0.is_empty(), "Height 0 should not have any metrics stored");
  813. // Process remaining heights, verifying that each stored metric matches expected results
  814. for (height, expected) in (1..).zip(test_data.iter().skip(1)) {
  815. let actual = store.get_by_height(&[height])?;
  816. assert!(!actual.is_empty(), "No metrics found for height {}", height);
  817. assert_eq!(expected, &actual[0]);
  818. }
  819. Ok(())
  820. }
  821. /// Tests searching gas metrics by the hour, verifying the correct metrics are found
  822. /// and match expected values.
  823. #[test]
  824. fn test_search_metrics_by_hour() -> Result<()> {
  825. // Setup test, returning initialized metrics store
  826. let store = setup()?;
  827. // Load test data, initializing expected with the fourth loaded record
  828. let expected = &load_random_metrics(&store, |_, _| {})?[3];
  829. // Create search criteria based on the expected timestamp value
  830. let search_criteria = DateTime::from_timestamp(expected.timestamp.inner(), 0);
  831. // Search metrics by the hour
  832. let actual_opt = store.main.iter().find_map(|res| {
  833. res.ok().and_then(|(k, v)| {
  834. let key = GasMetricsKey::from_sled_key(&k).ok()?;
  835. if key.inner().hour == search_criteria.hour {
  836. deserialize::<GasMetrics>(&v).ok()
  837. } else {
  838. None
  839. }
  840. })
  841. });
  842. // Verify the found metrics match expected results
  843. assert!(actual_opt.is_some());
  844. assert_eq!(normalize_metrics_timestamp(expected)?, actual_opt.unwrap());
  845. Ok(())
  846. }
  847. /// Tests fetching gas metrics by a timestamp string, verifying the retrieved metrics
  848. /// match expected values.
  849. #[test]
  850. fn test_get_metrics_by_timestamp_str() -> Result<()> {
  851. // Setup test, returning initialized metrics store
  852. let store = setup()?;
  853. // Load fixed data needed for test, initializing expected with the first loaded record
  854. let (expected, _) = &load_fixed_metrics(&store)?[0];
  855. // Create gas metrics key using a test fixed timestamp
  856. let gas_metrics_key = GasMetricsKey::new(FIXED_TIMESTAMP_STR)?;
  857. // Verify the key retrieves the correct metrics and matches the expected value
  858. let actual = store.get(&[gas_metrics_key])?;
  859. assert_eq!(expected, &actual[0]);
  860. Ok(())
  861. }
  862. /// Tests the insertion and retrieval of transaction gas data in the store, verifying expected results.
  863. /// Additionally, it tests that transactions not found in the store correctly return a `None` result.
  864. #[test]
  865. fn test_tx_gas_data() -> Result<()> {
  866. let tx_hash_not_found: TransactionHash = TransactionHash::from_str(
  867. "93325ff00a3755d8df93c626b59f6e36cf021d85ebccecdedc38f3f1890a15fc",
  868. )
  869. .expect("Invalid hash");
  870. // Setup test, returning initialized metrics store
  871. let store = setup()?;
  872. // Load data needed for test, initializing expected with the first loaded record
  873. let (_, expected) = &load_fixed_metrics(&store)?[0];
  874. // Verify that existing transaction is found
  875. let actual_opt = store.get_tx_gas_data(&TX_HASH)?;
  876. assert!(actual_opt.is_some());
  877. assert_eq!(*expected, actual_opt.unwrap());
  878. // Verify that transactions that do not exist return None result
  879. let actual_not_found = store.get_tx_gas_data(&tx_hash_not_found)?;
  880. assert_eq!(None, actual_not_found);
  881. Ok(())
  882. }
  883. /// Tests resetting gas metrics within a specified height range, verifying that both the `by_height` and `main` trees
  884. /// are properly set to the reset height.
  885. #[test]
  886. fn test_reset_metrics_within_height_range() -> Result<()> {
  887. // Declare constants used for test
  888. const RESET_HEIGHT: u32 = 6;
  889. // Setup test, returning initialized metrics store
  890. let store = setup()?;
  891. // Load test data into the store and get the expected reset metrics result
  892. let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
  893. // Reset metrics
  894. store.reset_gas_metrics(RESET_HEIGHT)?;
  895. // Fetch reset metrics by height
  896. let actual_by_height_opt = store.get_last_by_height()?;
  897. assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
  898. // Verify metrics by height are properly reset
  899. let (_, actual_by_height) = actual_by_height_opt.unwrap();
  900. assert_eq!(&expected, &actual_by_height);
  901. // Fetch reset main metrics
  902. let actual_main_opt = store.get_last()?;
  903. assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
  904. // Verify main metrics are properly reset
  905. let (_, actual_main_metrics) = actual_main_opt.unwrap();
  906. assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main_metrics);
  907. Ok(())
  908. }
  909. /// Tests resetting the metrics store to height 0, ensuring it handles the operation gracefully without errors
  910. /// and verifies that no metrics remain in the store afterward.
  911. #[test]
  912. fn test_reset_metrics_height_to_0() -> Result<()> {
  913. // Declare constants used for test
  914. const RESET_HEIGHT: u32 = 0;
  915. const EXPECTED_RESET_HEIGHT: usize = 0;
  916. // Setup test, returning initialized metrics store
  917. let store = setup()?;
  918. // Load reset test data needed for test
  919. _ = load_reset_metrics(&store, RESET_HEIGHT)?;
  920. // Reset metrics
  921. store.reset_gas_metrics(RESET_HEIGHT)?;
  922. // Verify metrics were reset with the expected counts
  923. assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
  924. assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
  925. // Verify metrics by height are empty
  926. let actual_by_height_opt = store.get_last_by_height()?;
  927. assert!(actual_by_height_opt.is_none(), "Expected None from get_last_by_height");
  928. // Confirm main metrics are empty
  929. let actual_main_opt = store.get_last()?;
  930. assert!(actual_main_opt.is_none(), "Expected None from get_last");
  931. Ok(())
  932. }
  933. /// Tests that resetting beyond the number of available metrics does not change
  934. /// the store and no errors are thrown since there are no metrics to reset.
  935. #[test]
  936. fn test_reset_metrics_beyond_height() -> Result<()> {
  937. // Declare constants used for test
  938. const RESET_HEIGHT: u32 = HEIGHT + 1;
  939. const EXPECTED_RESET_HEIGHT: usize = HEIGHT as usize - 1;
  940. // Setup test, returning initialized metrics store
  941. let store = setup()?;
  942. // Load reset test data needed for test, storing the expected result
  943. let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
  944. // Reset metrics to given height
  945. store.reset_gas_metrics(RESET_HEIGHT)?;
  946. // Verify metrics were reset with the expected counts
  947. assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
  948. assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
  949. // Verify that the last record for metrics by height is correctly reset
  950. let actual_by_height_opt = store.get_last_by_height()?;
  951. assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
  952. let (_, actual_by_height) = actual_by_height_opt.unwrap();
  953. assert_eq!(&expected, &actual_by_height);
  954. // Verify that the last record for main metrics is correctly reset
  955. let actual_main_opt = store.get_last()?;
  956. assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
  957. let (_, actual_main) = actual_main_opt.unwrap();
  958. assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main);
  959. Ok(())
  960. }
  961. /// Tests resetting metrics at the last available height to verify that the code
  962. /// can handle the boundary condition.
  963. #[test]
  964. fn test_reset_metrics_at_height() -> Result<()> {
  965. // Declare constants used for test
  966. const RESET_HEIGHT: u32 = HEIGHT;
  967. const EXPECTED_RESET_HEIGHT: usize = HEIGHT as usize - 1;
  968. // Setup test, returning initialized metrics store
  969. let store = setup()?;
  970. // Load reset test data needed for test
  971. let expected = load_reset_metrics(&store, RESET_HEIGHT)?;
  972. // Reset metrics to given height
  973. store.reset_gas_metrics(RESET_HEIGHT)?;
  974. // Verify metrics were reset with the expected counts
  975. assert_eq!(store.len_by_height(), EXPECTED_RESET_HEIGHT);
  976. assert_eq!(store.len(), EXPECTED_RESET_HEIGHT);
  977. // Verify that the last record for metrics by height is correctly reset
  978. let actual_by_height_opt = store.get_last_by_height()?;
  979. assert!(actual_by_height_opt.is_some(), "Expected get_last_by_height to return metrics");
  980. let (_, actual_by_height) = actual_by_height_opt.unwrap();
  981. assert_eq!(&expected, &actual_by_height);
  982. // Verify that the last record for main metrics is correctly reset
  983. let actual_main_opt = store.get_last()?;
  984. assert!(actual_main_opt.is_some(), "Expected get_last to return metrics");
  985. let (_, actual_main) = actual_main_opt.unwrap();
  986. assert_eq!(&normalize_metrics_timestamp(&expected)?, &actual_main);
  987. Ok(())
  988. }
  989. /// Tests that resetting an empty metrics store gracefully handles
  990. /// the operation without errors and ensures the store remains empty.
  991. #[test]
  992. fn test_reset_empty_store() -> Result<()> {
  993. const RESET_HEIGHT: u32 = 6;
  994. // Setup test, returning initialized metrics store
  995. let store = setup()?;
  996. // Reset metrics with an empty store
  997. store.reset_gas_metrics(RESET_HEIGHT)?;
  998. // Verify no metrics with the expected counts
  999. assert_eq!(store.len_by_height(), 0);
  1000. assert_eq!(store.len(), 0);
  1001. // Verify that metrics by height is empty
  1002. let actual_by_height = store.get_last_by_height()?;
  1003. assert!(actual_by_height.is_none(), "Expected get_last_by_height to return None");
  1004. // Verify main metrics is empty
  1005. let actual_main = store.get_last()?;
  1006. assert!(actual_main.is_none(), "Expected get_last to return None");
  1007. Ok(())
  1008. }
  1009. /// Sets up a test case for metrics store testing by initializing the logger,
  1010. /// creating a temporary database, and returning an initialized metrics store.
  1011. fn setup() -> Result<MetricsStore> {
  1012. // Initialize logger to show execution output
  1013. init_logger(simplelog::LevelFilter::Off, vec!["sled", "runtime", "net"]);
  1014. // Create a temporary directory for the sled database
  1015. let db =
  1016. sled::Config::new().temporary(true).open().expect("Unable to open test sled database");
  1017. // Initialize the metrics store
  1018. let metrics_store = MetricsStore::new(&db.clone())?;
  1019. Ok(metrics_store)
  1020. }
  1021. /// Loads random test gas metrics data into the given metrics store, simulating height 0 as a
  1022. /// genesis block with no metrics.
  1023. ///
  1024. /// Computes the starting block timestamp from the current system time for the first metric,
  1025. /// then inserts each subsequent metric at intervals of [`GAS_METRICS_KEY_TIME_INTERVAL`],
  1026. /// resulting in metrics being inserted one hour apart. The function iterates through a predefined
  1027. /// height range, as defined by [`HEIGHT`], to accumulate and insert gas metrics. After each
  1028. /// metric is stored, the `metric_loaded` closure is invoked, allowing the caller to perform
  1029. /// specific actions as the data is loaded.
  1030. ///
  1031. /// NOTE: A fixed transaction hash is used to insert the metrics, as this test data is solely intended
  1032. /// to validate gas metrics and not transaction-specific gas data.
  1033. ///
  1034. /// Upon success, it returns a list of snapshots of the accumulated metrics that were loaded.
  1035. fn load_random_metrics<F>(
  1036. metrics_store: &MetricsStore,
  1037. mut metrics_loaded: F,
  1038. ) -> Result<Vec<GasMetrics>>
  1039. where
  1040. F: FnMut(u32, &GasMetrics),
  1041. {
  1042. // Calculate the start block timestamp
  1043. let start_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
  1044. // Define variables to store accumulated loaded data
  1045. let mut accumulated_metrics = GasMetrics::default();
  1046. let mut metrics = Vec::with_capacity(HEIGHT as usize);
  1047. // Iterate and load data
  1048. for height in 0..HEIGHT {
  1049. let timestamp_secs = (UNIX_EPOCH +
  1050. Duration::from_secs(start_time + height as u64 * GAS_METRICS_KEY_TIME_INTERVAL))
  1051. .duration_since(UNIX_EPOCH)?
  1052. .as_secs();
  1053. // Initialize simulated block_timestamp
  1054. let block_timestamp = Timestamp::from(timestamp_secs);
  1055. accumulated_metrics.timestamp = block_timestamp;
  1056. // Simulate genesis block, metrics are stored after height 0
  1057. if height > 0 {
  1058. let tx_gas_data = random_gas_data(height as u64 + start_time);
  1059. accumulated_metrics.add(&[tx_gas_data.clone()]);
  1060. metrics_store.insert_gas_metrics(
  1061. height,
  1062. &block_timestamp,
  1063. &[*TX_HASH],
  1064. &[tx_gas_data],
  1065. )?;
  1066. }
  1067. // Invoke passed in metrics loaded closure
  1068. metrics_loaded(height, &accumulated_metrics);
  1069. // Add a snapshot of the accumulated metrics
  1070. metrics.push(accumulated_metrics.clone());
  1071. }
  1072. Ok(metrics)
  1073. }
  1074. /// Loads fixed test data into the metrics store using fixed timestamps,
  1075. /// returning snapshots of accumulated [`GasMetrics`] with corresponding [`GasData`]
  1076. /// used to update the metrics.
  1077. ///
  1078. /// Currently, this function only loads a single record but is designed to be extendable
  1079. /// to insert additional records in the future without affecting the method's return signature,
  1080. /// making it suitable for use in tests.
  1081. fn load_fixed_metrics(metrics_store: &MetricsStore) -> Result<Vec<(GasMetrics, GasData)>> {
  1082. // Convert the fixed timestamp constant to a Timestamp object
  1083. let fixed_timestamp = Timestamp::from_u64(FIXED_TIMESTAMP);
  1084. // Initialize an empty GasMetrics object to accumulate the data
  1085. let height: u32 = 1;
  1086. let mut accumulated_metrics = GasMetrics::default();
  1087. let mut metrics_vec = Vec::with_capacity(HEIGHT as usize);
  1088. // Initialize the block_timestamp using the fixed timestamp
  1089. let block_timestamp = fixed_timestamp;
  1090. accumulated_metrics.timestamp = block_timestamp;
  1091. // Generate random gas data for the given height
  1092. let gas_data = random_gas_data(height as u64);
  1093. accumulated_metrics.add(&[gas_data.clone()]);
  1094. // Insert the gas metrics into the metrics store
  1095. metrics_store.insert_gas_metrics(
  1096. height,
  1097. &block_timestamp,
  1098. &[*TX_HASH],
  1099. &[gas_data.clone()],
  1100. )?;
  1101. metrics_vec.push((accumulated_metrics, gas_data));
  1102. Ok(metrics_vec)
  1103. }
  1104. /// Loads reset test data into the store, returning the accumulated gas metrics at the specified reset height.
  1105. fn load_reset_metrics(metrics_store: &MetricsStore, reset_height: u32) -> Result<GasMetrics> {
  1106. let mut reset_metrics = GasMetrics::default();
  1107. // Load metrics, passing in a closure to store the reset metrics
  1108. _ = load_random_metrics(metrics_store, |height, acc_metrics| {
  1109. // Store accumulated metrics at reset height
  1110. if reset_height == height || reset_height >= HEIGHT {
  1111. reset_metrics = acc_metrics.clone();
  1112. }
  1113. })?;
  1114. Ok(reset_metrics)
  1115. }
  1116. /// Generates random [`GasData`] based on the provided seed value, allowing for the simulation
  1117. /// of varied gas data values.
  1118. fn random_gas_data(seed: u64) -> GasData {
  1119. /// Defines a limit for gas data values.
  1120. const GAS_LIMIT: u64 = 100_000;
  1121. // Initialize gas usage with the provided seed
  1122. let mut gas_used = seed;
  1123. // Closure to generate a random gas value
  1124. let mut random_gas = || {
  1125. // Introduce variability using the seed and current gas_used
  1126. let variation = seed.wrapping_add(gas_used);
  1127. gas_used = gas_used.wrapping_mul(6364136223846793005).wrapping_add(variation);
  1128. gas_used
  1129. };
  1130. // Create GasData with random values constrained by GAS_LIMIT
  1131. GasData {
  1132. paid: random_gas() % GAS_LIMIT,
  1133. wasm: random_gas() % GAS_LIMIT,
  1134. zk_circuits: random_gas() % GAS_LIMIT,
  1135. signatures: random_gas() % GAS_LIMIT,
  1136. deployments: random_gas() % GAS_LIMIT,
  1137. }
  1138. }
  1139. /// Normalizes the [`GasMetrics`] timestamp to the start of the hour for test comparisons.
  1140. fn normalize_metrics_timestamp(metrics: &GasMetrics) -> Result<GasMetrics> {
  1141. let mut normalized_metrics = metrics.clone();
  1142. normalized_metrics.timestamp = GasMetricsKey::normalize_timestamp(&metrics.timestamp)?;
  1143. Ok(normalized_metrics)
  1144. }
  1145. }