From 4363494d2a4432e0646b3dd81c1c76fe21c08904 Mon Sep 17 00:00:00 2001 From: Richard Bertok Date: Mon, 5 Aug 2024 13:43:22 +0200 Subject: [PATCH] feat(stats): added miner and p2pool block stats (#26) Description --- We need to know whether how many blocks we sent to both p2pool and the target network. Motivation and Context --- How Has This Been Tested? --- - Normally start mining with p2pool - Check stats at http://127.0.0.1:19000/stats Example output: ```json { "connected": true, "connected_since": 1722856949, "num_of_miners": 2, "last_block_won": { "hash": "fbdbf4a36888c6f5ad92c67f40987ab9cc1313809ea98859bc42eda68d54c832", "height": 5403, "timestamp": 1722857161, "miner_wallet_address": "f27mEvFXUZJCNFQM1MbkRwBnNJHwsK6JBxzrwVyYNm6bgGHpL1xDj7Gjevwj9caFrp23iLGUcysK6decdDL87sQCtL2" }, "share_chain_height": 5406, "pool_hash_rate": [4235994], "pool_total_earnings": 55525928731, "pool_total_estimated_earnings": { "1min": 12123240, "1h": 727394400, "1d": 17457465600, "1w": 122202259200, "30d": 523723968000 }, "total_earnings": { "f2CrtWaTZE3xWSxCkR1mR9Bszx321Ar2S1fPcBLNSNWiLBVwqkeankFjeTmdxYLuyeHg8oM4vSgsV1tjL4GSKEuy9pk": 9716846540, "f27mEvFXUZJCNFQM1MbkRwBnNJHwsK6JBxzrwVyYNm6bgGHpL1xDj7Gjevwj9caFrp23iLGUcysK6decdDL87sQCtL2": 58426310963 }, "estimated_earnings": { "f2CrtWaTZE3xWSxCkR1mR9Bszx321Ar2S1fPcBLNSNWiLBVwqkeankFjeTmdxYLuyeHg8oM4vSgsV1tjL4GSKEuy9pk": { "1min": 1728660, "1h": 103719600, "1d": 2489270400, "1w": 17424892800, "30d": 74678112000 }, "f27mEvFXUZJCNFQM1MbkRwBnNJHwsK6JBxzrwVyYNm6bgGHpL1xDj7Gjevwj9caFrp23iLGUcysK6decdDL87sQCtL2": { "1min": 10394520, "1h": 623671200, "1d": 14968108800, "1w": 104776761600, "30d": 449043264000 } }, "miner_block_stats": { "accepted": 154, "rejected": 0, "submitted": 154 }, "p2pool_block_stats": { "accepted": 2, "rejected": 77, "submitted": 79 } } ``` What process can a PR reviewer use to test or verify this change? --- Check previous section. Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- src/server/grpc/p2pool.rs | 43 +++++++++++++++++++++++++------ src/server/http/stats/handlers.rs | 24 ++++++++++++++++- src/server/http/stats/mod.rs | 5 ++++ src/server/http/stats/models.rs | 19 ++++++++++++++ src/server/http/stats/server.rs | 7 ++++- src/server/mod.rs | 1 + src/server/server.rs | 4 +++ src/server/stats_store.rs | 39 ++++++++++++++++++++++++++++ 8 files changed, 132 insertions(+), 10 deletions(-) create mode 100644 src/server/stats_store.rs diff --git a/src/server/grpc/p2pool.rs b/src/server/grpc/p2pool.rs index 3302a259..3b002475 100644 --- a/src/server/grpc/p2pool.rs +++ b/src/server/grpc/p2pool.rs @@ -14,6 +14,11 @@ use tari_utilities::hex::Hex; use tokio::sync::Mutex; use tonic::{Request, Response, Status}; +use crate::server::http::stats::{ + MINER_STAT_ACCEPTED_BLOCKS_COUNT, MINER_STAT_REJECTED_BLOCKS_COUNT, P2POOL_STAT_ACCEPTED_BLOCKS_COUNT, + P2POOL_STAT_REJECTED_BLOCKS_COUNT, +}; +use crate::server::stats_store::StatsStore; use crate::{ server::{ grpc::{error::Error, util}, @@ -35,6 +40,8 @@ where p2p_client: p2p::ServiceClient, /// Current share chain share_chain: Arc, + /// Stats store + stats_store: Arc, } impl ShaP2PoolGrpc @@ -45,24 +52,37 @@ where base_node_address: String, p2p_client: p2p::ServiceClient, share_chain: Arc, + stats_store: Arc, ) -> Result { Ok(Self { client: Arc::new(Mutex::new(util::connect_base_node(base_node_address).await?)), p2p_client, share_chain, + stats_store, }) } /// Submits a new block to share chain and broadcasts to the p2p network. pub async fn submit_share_chain_block(&self, block: &Block) -> Result<(), Status> { - if let Err(error) = self.share_chain.submit_block(block).await { - warn!(target: LOG_TARGET, "Failed to add new block: {error:?}"); + match self.share_chain.submit_block(block).await { + Ok(_) => { + self.stats_store + .inc(&MINER_STAT_ACCEPTED_BLOCKS_COUNT.to_string(), 1) + .await; + info!(target: LOG_TARGET, "Broadcast new block: {:?}", block.hash().to_hex()); + self.p2p_client + .broadcast_block(block) + .await + .map_err(|error| Status::internal(error.to_string())) + }, + Err(error) => { + warn!(target: LOG_TARGET, "Failed to add new block: {error:?}"); + self.stats_store + .inc(&MINER_STAT_REJECTED_BLOCKS_COUNT.to_string(), 1) + .await; + Ok(()) + }, } - info!(target: LOG_TARGET, "Broadcast new block: {:?}", block.hash().to_hex()); - self.p2p_client - .broadcast_block(block) - .await - .map_err(|error| Status::internal(error.to_string())) } } @@ -176,12 +196,19 @@ where let grpc_request = Request::from_parts(metadata, extensions, grpc_request_payload); match self.client.lock().await.submit_block(grpc_request).await { Ok(resp) => { + self.stats_store + .inc(&P2POOL_STAT_ACCEPTED_BLOCKS_COUNT.to_string(), 1) + .await; info!("💰 New matching block found and sent to network!"); block.set_sent_to_main_chain(true); self.submit_share_chain_block(&block).await?; Ok(resp) }, - Err(_) => { + Err(error) => { + warn!("Failed to submit block to Tari network: {error:?}"); + self.stats_store + .inc(&P2POOL_STAT_REJECTED_BLOCKS_COUNT.to_string(), 1) + .await; block.set_sent_to_main_chain(false); self.submit_share_chain_block(&block).await?; Ok(Response::new(SubmitBlockResponse { diff --git a/src/server/http/stats/handlers.rs b/src/server/http/stats/handlers.rs index 354e0e7b..5042b893 100644 --- a/src/server/http/stats/handlers.rs +++ b/src/server/http/stats/handlers.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: BSD-3-Clause use std::collections::HashMap; +use std::sync::Arc; use axum::extract::State; use axum::http::StatusCode; @@ -13,8 +14,13 @@ use tari_core::consensus::ConsensusManager; use tari_core::transactions::tari_amount::MicroMinotari; use tari_utilities::epoch_time::EpochTime; -use crate::server::http::stats::models::{EstimatedEarnings, Stats}; +use crate::server::http::stats::models::{BlockStats, EstimatedEarnings, Stats}; use crate::server::http::stats::server::AppState; +use crate::server::http::stats::{ + MINER_STAT_ACCEPTED_BLOCKS_COUNT, MINER_STAT_REJECTED_BLOCKS_COUNT, P2POOL_STAT_ACCEPTED_BLOCKS_COUNT, + P2POOL_STAT_REJECTED_BLOCKS_COUNT, +}; +use crate::server::stats_store::StatsStore; use crate::sharechain::SHARE_COUNT; const LOG_TARGET: &str = "p2pool::server::stats::get"; @@ -136,5 +142,21 @@ pub async fn handle_get_stats(State(state): State) -> Result) -> BlockStats { + BlockStats::new( + stats_store.get(&MINER_STAT_ACCEPTED_BLOCKS_COUNT.to_string()).await, + stats_store.get(&MINER_STAT_REJECTED_BLOCKS_COUNT.to_string()).await, + ) +} + +async fn p2pool_block_stats(stats_store: Arc) -> BlockStats { + BlockStats::new( + stats_store.get(&P2POOL_STAT_ACCEPTED_BLOCKS_COUNT.to_string()).await, + stats_store.get(&P2POOL_STAT_REJECTED_BLOCKS_COUNT.to_string()).await, + ) +} diff --git a/src/server/http/stats/mod.rs b/src/server/http/stats/mod.rs index a61e3188..72c40743 100644 --- a/src/server/http/stats/mod.rs +++ b/src/server/http/stats/mod.rs @@ -1,6 +1,11 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +pub const MINER_STAT_ACCEPTED_BLOCKS_COUNT: &str = "miner_accepted_blocks_count"; +pub const MINER_STAT_REJECTED_BLOCKS_COUNT: &str = "miner_rejected_blocks_count"; +pub const P2POOL_STAT_ACCEPTED_BLOCKS_COUNT: &str = "p2pool_accepted_blocks_count"; +pub const P2POOL_STAT_REJECTED_BLOCKS_COUNT: &str = "p2pool_rejected_blocks_count"; + pub mod handlers; pub mod models; pub mod server; diff --git a/src/server/http/stats/models.rs b/src/server/http/stats/models.rs index 6bb478e6..2dd9bd35 100644 --- a/src/server/http/stats/models.rs +++ b/src/server/http/stats/models.rs @@ -56,6 +56,23 @@ impl EstimatedEarnings { } } +#[derive(Serialize, Deserialize)] +pub struct BlockStats { + pub accepted: u64, + pub rejected: u64, + pub submitted: u64, +} + +impl BlockStats { + pub fn new(accepted: u64, rejected: u64) -> Self { + Self { + accepted, + rejected, + submitted: accepted + rejected, + } + } +} + #[derive(Serialize, Deserialize)] pub struct Stats { pub connected: bool, @@ -68,4 +85,6 @@ pub struct Stats { pub pool_total_estimated_earnings: EstimatedEarnings, pub total_earnings: HashMap, pub estimated_earnings: HashMap, + pub miner_block_stats: BlockStats, + pub p2pool_block_stats: BlockStats, } diff --git a/src/server/http/stats/server.rs b/src/server/http/stats/server.rs index 10deece6..91986c42 100644 --- a/src/server/http/stats/server.rs +++ b/src/server/http/stats/server.rs @@ -11,6 +11,7 @@ use tokio::io; use crate::server::http::stats::handlers; use crate::server::p2p::peer_store::PeerStore; +use crate::server::stats_store::StatsStore; use crate::sharechain::ShareChain; const LOG_TARGET: &str = "p2pool::server::stats"; @@ -42,6 +43,7 @@ where { share_chain: Arc, peer_store: Arc, + stats_store: Arc, port: u16, } @@ -49,16 +51,18 @@ where pub struct AppState { pub share_chain: Arc, pub peer_store: Arc, + pub stats_store: Arc, } impl StatsServer where S: ShareChain, { - pub fn new(share_chain: Arc, peer_store: Arc, port: u16) -> Self { + pub fn new(share_chain: Arc, peer_store: Arc, stats_store: Arc, port: u16) -> Self { Self { share_chain, peer_store, + stats_store, port, } } @@ -70,6 +74,7 @@ where .with_state(AppState { share_chain: self.share_chain.clone(), peer_store: self.peer_store.clone(), + stats_store: self.stats_store.clone(), }) } diff --git a/src/server/mod.rs b/src/server/mod.rs index 5bab830e..13adbc7c 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -12,3 +12,4 @@ mod server; pub mod grpc; pub mod http; pub mod p2p; +pub mod stats_store; diff --git a/src/server/server.rs b/src/server/server.rs index a92643ce..1c29ab4f 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -14,6 +14,7 @@ use thiserror::Error; use crate::server::http::stats::server::StatsServer; use crate::server::p2p::peer_store::PeerStore; +use crate::server::stats_store::StatsStore; use crate::{ server::{ config, grpc, @@ -56,6 +57,7 @@ where let share_chain = Arc::new(share_chain); let sync_in_progress = Arc::new(AtomicBool::new(true)); let peer_store = Arc::new(PeerStore::new(&config.peer_store)); + let stats_store = Arc::new(StatsStore::new()); let mut p2p_service: p2p::Service = p2p::Service::new( &config, @@ -78,6 +80,7 @@ where config.base_node_address.clone(), p2p_service.client(), share_chain.clone(), + stats_store.clone(), ) .await .map_err(Error::Grpc)?; @@ -88,6 +91,7 @@ where Some(Arc::new(StatsServer::new( share_chain.clone(), peer_store.clone(), + stats_store.clone(), config.stats_server.port, ))) } else { diff --git a/src/server/stats_store.rs b/src/server/stats_store.rs new file mode 100644 index 00000000..e521a780 --- /dev/null +++ b/src/server/stats_store.rs @@ -0,0 +1,39 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::collections::HashMap; + +use tokio::sync::RwLock; + +pub struct StatsStore { + stats: RwLock>, +} + +impl StatsStore { + pub fn new() -> Self { + Self { + stats: RwLock::new(HashMap::new()), + } + } + + /// Returns one stat by [`key`]. + pub async fn get(&self, key: &String) -> u64 { + let read_lock = self.stats.read().await; + read_lock.get(key).copied().unwrap_or(0) + } + + /// Increments stat with given key. + /// If the value is not found by key, simply create new value. + pub async fn inc(&self, key: &String, by: u64) { + let mut write_lock = self.stats.write().await; + match write_lock.get(key) { + Some(stat) => { + let value = stat + by; + write_lock.insert(key.clone(), value); + }, + None => { + write_lock.insert(key.clone(), by); + }, + } + } +}