From daa2676c5af7a92041cc3bcdadfe7edf448253b9 Mon Sep 17 00:00:00 2001 From: ksrichard Date: Mon, 9 Sep 2024 13:25:39 +0200 Subject: [PATCH] Added minimum target difficulty (+ validation) and chain ID --- Cargo.lock | 1 + Cargo.toml | 1 + src/server/grpc/error.rs | 2 + src/server/grpc/p2pool.rs | 24 ++++++++++- src/server/http/server.rs | 4 -- src/server/http/stats/cache.rs | 18 ++++---- src/server/p2p/network.rs | 3 +- src/server/p2p/peer_store.rs | 3 +- src/server/server.rs | 3 +- src/sharechain/block.rs | 19 ++++++++- src/sharechain/in_memory.rs | 76 +++++++++++++++++++++++++--------- src/sharechain/mod.rs | 4 ++ 12 files changed, 117 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f82b4346..bb746ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4308,6 +4308,7 @@ dependencies = [ "hex", "hickory-resolver", "itertools 0.13.0", + "lazy_static", "libp2p", "log", "log4rs", diff --git a/Cargo.toml b/Cargo.toml index a642ce93..b99f55f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ hex = "0.4.3" serde_json = "1.0.122" hickory-resolver = { version = "*", features = ["dns-over-rustls"] } convert_case = "0.6.0" +lazy_static = "1.5.0" [package.metadata.cargo-machete] ignored = ["log4rs"] diff --git a/src/server/grpc/error.rs b/src/server/grpc/error.rs index 89f8b730..931e75cc 100644 --- a/src/server/grpc/error.rs +++ b/src/server/grpc/error.rs @@ -7,6 +7,8 @@ use thiserror::Error; pub enum Error { #[error("Tonic error: {0}")] Tonic(#[from] TonicError), + #[error("No consensus constants found")] + NoConsensusConstants, #[error("Shutdown")] Shutdown, } diff --git a/src/server/grpc/p2pool.rs b/src/server/grpc/p2pool.rs index a68e92b7..427c2c3f 100644 --- a/src/server/grpc/p2pool.rs +++ b/src/server/grpc/p2pool.rs @@ -10,7 +10,9 @@ use minotari_app_grpc::tari_rpc::{ base_node_client::BaseNodeClient, sha_p2_pool_server::ShaP2Pool, GetNewBlockRequest, GetNewBlockResponse, GetNewBlockTemplateWithCoinbasesRequest, NewBlockTemplateRequest, SubmitBlockRequest, SubmitBlockResponse, }; +use tari_common::configuration::Network; use tari_common_types::types::FixedHash; +use tari_core::consensus; use tari_core::consensus::ConsensusManager; use tari_core::proof_of_work::randomx_factory::RandomXFactory; use tari_core::proof_of_work::{randomx_difficulty, sha3x_difficulty, PowAlgorithm}; @@ -35,6 +37,21 @@ use crate::{ const LOG_TARGET: &str = "p2pool::server::grpc::p2pool"; +pub fn min_difficulty(pow: PowAlgorithm) -> Result { + let network = Network::get_current_or_user_setting_or_default(); + let consensus_constants = match network { + Network::MainNet => consensus::ConsensusConstants::mainnet(), + Network::StageNet => consensus::ConsensusConstants::stagenet(), + Network::NextNet => consensus::ConsensusConstants::nextnet(), + Network::LocalNet => consensus::ConsensusConstants::localnet(), + Network::Igor => consensus::ConsensusConstants::igor(), + Network::Esmeralda => consensus::ConsensusConstants::esmeralda(), + }; + let consensus_constants = consensus_constants.first().ok_or(Error::NoConsensusConstants)?; + + Ok(consensus_constants.min_pow_difficulty(pow).as_u64()) +} + /// P2Pool specific gRPC service to provide `get_new_block` and `submit_block` functionalities. pub struct ShaP2PoolGrpc where @@ -183,7 +200,12 @@ where .await .insert(height, miner_data.target_difficulty); } - let target_difficulty = miner_data.target_difficulty / SHARE_COUNT; + let min_difficulty = + min_difficulty(pow_algo).map_err(|_| Status::internal("failed to get minimum difficulty"))?; + let mut target_difficulty = miner_data.target_difficulty / SHARE_COUNT; + if target_difficulty < min_difficulty { + target_difficulty = min_difficulty; + } if let Some(mut miner_data) = response.miner_data { miner_data.target_difficulty = target_difficulty; response.miner_data = Some(miner_data); diff --git a/src/server/http/server.rs b/src/server/http/server.rs index 510a5fa8..59b5c2bc 100644 --- a/src/server/http/server.rs +++ b/src/server/http/server.rs @@ -3,7 +3,6 @@ use crate::server::http::stats::cache::StatsCache; use crate::server::http::stats::handlers; -use crate::server::http::stats::models::{BlockStats, EstimatedEarnings, Stats, TribeDetails}; use crate::server::http::{health, version}; use crate::server::p2p::peer_store::PeerStore; use crate::server::p2p::Tribe; @@ -13,12 +12,9 @@ use axum::routing::get; use axum::Router; use log::info; use std::sync::Arc; -use std::time::Duration; use tari_shutdown::ShutdownSignal; use thiserror::Error; use tokio::io; -use tokio::sync::{Mutex, RwLock, RwLockWriteGuard}; -use tokio::time::Instant; const LOG_TARGET: &str = "p2pool::server::stats"; diff --git a/src/server/http/stats/cache.rs b/src/server/http/stats/cache.rs index e2d6dbc4..4325fbbb 100644 --- a/src/server/http/stats/cache.rs +++ b/src/server/http/stats/cache.rs @@ -14,14 +14,6 @@ impl CachedStats { pub fn new(stats: Stats, last_update: Instant) -> Self { Self { stats, last_update } } - - pub fn stats(&self) -> &Stats { - &self.stats - } - - pub fn last_update(&self) -> Instant { - self.last_update - } } pub struct StatsCache { @@ -29,6 +21,12 @@ pub struct StatsCache { stats: Arc>>, } +impl Default for StatsCache { + fn default() -> Self { + Self::new(Duration::from_secs(10)) + } +} + impl StatsCache { pub fn new(ttl: Duration) -> Self { Self { @@ -52,7 +50,9 @@ impl StatsCache { pub async fn stats(&self) -> Option { let lock = self.stats.read().await; - if lock.is_some() && Instant::now().duration_since(lock.as_ref()?.last_update) > self.ttl { + let last_update = lock.as_ref()?.last_update; + if lock.is_some() && Instant::now().duration_since(last_update) > self.ttl { + drop(lock); let mut lock = self.stats.write().await; *lock = None; return None; diff --git a/src/server/p2p/network.rs b/src/server/p2p/network.rs index 4f6c287c..d36edc21 100644 --- a/src/server/p2p/network.rs +++ b/src/server/p2p/network.rs @@ -48,6 +48,7 @@ use tokio::{ }; use crate::server::p2p::messages::LocalShareChainSyncRequest; +use crate::sharechain::block::CURRENT_CHAIN_ID; use crate::{ server::{ config, @@ -392,7 +393,7 @@ where /// blocks and peers with different Tari networks and the given tribe name. fn tribe_topic(tribe: &Tribe, topic: &str) -> String { let network = Network::get_current_or_user_setting_or_default().as_key_str(); - format!("{network}_{tribe}_{topic}") + format!("{network}_{}_{tribe}_{topic}", CURRENT_CHAIN_ID.clone()) } /// Subscribing to a gossipsub topic. diff --git a/src/server/p2p/peer_store.rs b/src/server/p2p/peer_store.rs index e7e56b1a..3d87106f 100644 --- a/src/server/p2p/peer_store.rs +++ b/src/server/p2p/peer_store.rs @@ -3,9 +3,8 @@ use itertools::Itertools; use libp2p::PeerId; -use log::{debug, info, warn}; +use log::{debug, warn}; use moka::future::{Cache, CacheBuilder}; -use std::ops::Add; use std::{ sync::RwLock, time::{Duration, Instant}, diff --git a/src/server/server.rs b/src/server/server.rs index 8b2dacb8..fad1bec8 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -4,7 +4,6 @@ use log::{error, info}; use minotari_app_grpc::tari_rpc::{base_node_server::BaseNodeServer, sha_p2_pool_server::ShaP2PoolServer}; use std::sync::atomic::AtomicBool; -use std::time::Duration; use std::{ net::{AddrParseError, SocketAddr}, str::FromStr, @@ -113,7 +112,7 @@ where p2pool_server = Some(ShaP2PoolServer::new(p2pool_grpc_service)); } - let http_stats_cache = Arc::new(StatsCache::new(Duration::from_secs(10))); + let http_stats_cache = Arc::new(StatsCache::default()); let stats_server = if config.http_server.enabled { Some(Arc::new(HttpServer::new( diff --git a/src/sharechain/block.rs b/src/sharechain/block.rs index 2ad2b851..b10b66c4 100644 --- a/src/sharechain/block.rs +++ b/src/sharechain/block.rs @@ -1,20 +1,34 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use crate::impl_conversions; +use crate::sharechain::CHAIN_ID; use blake2::Blake2b; use digest::consts::U32; +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; use tari_common_types::{tari_address::TariAddress, types::BlockHash}; +use tari_core::blocks::genesis_block::get_genesis_block; use tari_core::{ blocks::{BlockHeader, BlocksHashDomain}, consensus::DomainSeparatedConsensusHasher, }; use tari_utilities::epoch_time::EpochTime; - -use crate::impl_conversions; +use tari_utilities::hex::Hex; + +lazy_static! { + pub static ref CURRENT_CHAIN_ID: String = { + let network = Network::get_current_or_user_setting_or_default(); + let network_genesis_block = get_genesis_block(network); + let network_genesis_block_hash = network_genesis_block.block().header.hash().to_hex(); + format!("{network_genesis_block_hash}_{CHAIN_ID}") + }; +} #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct Block { + chain_id: String, hash: BlockHash, timestamp: EpochTime, prev_hash: BlockHash, @@ -92,6 +106,7 @@ impl BlockBuilder { pub fn new() -> Self { Self { block: Block { + chain_id: CURRENT_CHAIN_ID.clone(), hash: Default::default(), timestamp: EpochTime::now(), prev_hash: Default::default(), diff --git a/src/sharechain/in_memory.rs b/src/sharechain/in_memory.rs index 4090a7f6..cc52e95f 100644 --- a/src/sharechain/in_memory.rs +++ b/src/sharechain/in_memory.rs @@ -6,6 +6,12 @@ use std::slice::Iter; use std::str::FromStr; use std::{collections::HashMap, sync::Arc}; +use crate::server::grpc::p2pool::min_difficulty; +use crate::sharechain::{ + error::{BlockConvertError, Error}, + Block, BlockValidationParams, ShareChain, ShareChainResult, SubmitBlockResult, ValidateBlockResult, BLOCKS_WINDOW, + MAX_BLOCKS_COUNT, SHARE_COUNT, +}; use async_trait::async_trait; use itertools::Itertools; use log::{debug, error, info, warn}; @@ -14,16 +20,10 @@ use num::{BigUint, Integer, Zero}; use tari_common_types::tari_address::TariAddress; use tari_common_types::types::BlockHash; use tari_core::blocks; -use tari_core::proof_of_work::{randomx_difficulty, sha3x_difficulty, PowAlgorithm}; +use tari_core::proof_of_work::{randomx_difficulty, sha3x_difficulty, Difficulty, PowAlgorithm}; use tari_utilities::{epoch_time::EpochTime, hex::Hex}; use tokio::sync::{RwLock, RwLockWriteGuard}; -use crate::sharechain::{ - error::{BlockConvertError, Error}, - Block, BlockValidationParams, ShareChain, ShareChainResult, SubmitBlockResult, ValidateBlockResult, BLOCKS_WINDOW, - MAX_BLOCKS_COUNT, SHARE_COUNT, -}; - const LOG_TARGET: &str = "p2pool::sharechain::in_memory"; pub struct InMemoryShareChain { @@ -115,16 +115,16 @@ impl InMemoryShareChain { params.genesis_block_hash(), params.consensus_manager(), ) - .map_err(Error::RandomXDifficulty)?; + .map_err(Error::RandomXDifficulty)?; Ok(difficulty.as_u64()) } else { Ok(0) } - } + }, PowAlgorithm::Sha3x => { let difficulty = sha3x_difficulty(block.original_block_header()).map_err(Error::Difficulty)?; Ok(difficulty.as_u64()) - } + }, } } @@ -167,6 +167,28 @@ impl InMemoryShareChain { result } + fn validate_min_difficulty( + &self, + pow: PowAlgorithm, + curr_difficulty: Difficulty, + ) -> ShareChainResult { + match min_difficulty(pow) { + Ok(min_difficulty) => { + if curr_difficulty.as_u64() < min_difficulty { + warn!(target: LOG_TARGET, "[{:?}] ❌ Too low difficulty!", self.pow_algo); + return Ok(ValidateBlockResult::new(false, false)); + } + }, + Err(error) => { + warn!(target: LOG_TARGET, "[{:?}] ❌ Can't get min difficulty!", self.pow_algo); + debug!(target: LOG_TARGET, "[{:?}] ❌ Can't get min difficulty: {error:?}", self.pow_algo); + return Ok(ValidateBlockResult::new(false, false)); + }, + } + + Ok(ValidateBlockResult::new(true, false)) + } + /// Validating a new block. async fn validate_block( &self, @@ -204,29 +226,43 @@ impl InMemoryShareChain { match block.original_block_header().pow.pow_algo { PowAlgorithm::RandomX => match params { Some(params) => { - if let Err(error) = randomx_difficulty( + match randomx_difficulty( block.original_block_header(), params.random_x_factory(), params.genesis_block_hash(), params.consensus_manager(), ) { - warn!(target: LOG_TARGET, "[{:?}] ❌ Invalid PoW!", self.pow_algo); - debug!(target: LOG_TARGET, "[{:?}] Failed to calculate RandomX difficulty: {error:?}", self.pow_algo); - return Ok(ValidateBlockResult::new(false, false)); + Ok(curr_difficulty) => { + let result = self.validate_min_difficulty(PowAlgorithm::RandomX, curr_difficulty)?; + if !result.valid { + return Ok(result); + } + }, + Err(error) => { + warn!(target: LOG_TARGET, "[{:?}] ❌ Invalid PoW!", self.pow_algo); + debug!(target: LOG_TARGET, "[{:?}] Failed to calculate RandomX difficulty: {error:?}", self.pow_algo); + return Ok(ValidateBlockResult::new(false, false)); + }, } - } + }, None => { error!(target: LOG_TARGET, "[{:?}] ❌ Cannot calculate PoW! Missing validation parameters!", self.pow_algo); return Ok(ValidateBlockResult::new(false, false)); - } + }, }, - PowAlgorithm::Sha3x => { - if let Err(error) = sha3x_difficulty(block.original_block_header()) { + PowAlgorithm::Sha3x => match sha3x_difficulty(block.original_block_header()) { + Ok(curr_difficulty) => { + let result = self.validate_min_difficulty(PowAlgorithm::Sha3x, curr_difficulty)?; + if !result.valid { + return Ok(result); + } + }, + Err(error) => { warn!(target: LOG_TARGET, "[{:?}] ❌ Invalid PoW!", self.pow_algo); debug!(target: LOG_TARGET, "[{:?}] Failed to calculate SHA3x difficulty: {error:?}", self.pow_algo); return Ok(ValidateBlockResult::new(false, false)); - } - } + }, + }, } // TODO: check here for miners diff --git a/src/sharechain/mod.rs b/src/sharechain/mod.rs index c3b77606..ac6d5448 100644 --- a/src/sharechain/mod.rs +++ b/src/sharechain/mod.rs @@ -9,6 +9,10 @@ use tari_common_types::types::FixedHash; use tari_core::consensus::ConsensusManager; use tari_core::proof_of_work::randomx_factory::RandomXFactory; +/// Chain ID is an identifier which makes sure we apply the same rules to blocks. +/// Note: This must be updated when new logic applied to blocks handling. +pub const CHAIN_ID: usize = 1; + /// How many blocks to keep overall. pub const MAX_BLOCKS_COUNT: usize = 240;