From ddd7368b73e7d47cff8b51776ad4c0bc4fb007c9 Mon Sep 17 00:00:00 2001 From: Wei Tang Date: Thu, 3 Oct 2019 11:02:20 +0800 Subject: [PATCH] Cumulative fixes to make working with consensus-pow easier (#3617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * consensus-pow: add difficulty data to auxiliary * Timestamp api * Implement FinalityProofProvider for () * Add DifficultyApi * Remove assumption that Difficulty is u128 * Use a separate trait for add instead of hard-code it as Saturating * Some convenience functions to work with PowVerifier * Try to fix mining unstability * Fix generic resolution * Unused best_header variable * Fix hash calculation * Remove artificial sleep * Tweak proposer waiting time * Revert sleep removal The reason why it was there is because when mine_loop returns, it means an error happened. In that case, we'd better sleep for a moment before trying again, because immediately trying would most likely just fail. * Pass sync oracle to mining So that it does not mine when major syncing * Expose build time as a parameter Instead of hardcode it as previously 100ms. * Update lock file * Fix compile * Support skipping check_inherents for ancient blocks For PoW, older blocks are secured by the work, and can mostly be considered to be finalized. Thus we can save both code complexity and validation time by skipping checking inherents for them. * Move difficulty fetch function out of loop To make things faster * Remove seed from mining Each engine can use its own Rng source. * Better comments * Add TotalDifficulty definition for U256 and u128 * Update core/consensus/pow/src/lib.rs Co-Authored-By: André Silva * Rename TotalDifficulty::add -> increment * Use SelectChain to fetch the best header/hash * Update lock file --- Cargo.lock | 1 + core/consensus/pow/primitives/Cargo.toml | 2 + core/consensus/pow/primitives/src/lib.rs | 45 +++++-- core/consensus/pow/src/lib.rs | 164 +++++++++++++++++------ core/network/src/chain.rs | 6 + 5 files changed, 168 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9d80227547e01..70433417519d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4861,6 +4861,7 @@ dependencies = [ name = "substrate-consensus-pow-primitives" version = "2.0.0" dependencies = [ + "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 2.0.0", "sr-std 2.0.0", "substrate-client 2.0.0", diff --git a/core/consensus/pow/primitives/Cargo.toml b/core/consensus/pow/primitives/Cargo.toml index a7e0d284b09fa..021e4c80a754c 100644 --- a/core/consensus/pow/primitives/Cargo.toml +++ b/core/consensus/pow/primitives/Cargo.toml @@ -10,6 +10,7 @@ substrate-client = { path = "../../../client", default-features = false } rstd = { package = "sr-std", path = "../../../sr-std", default-features = false } sr-primitives = { path = "../../../sr-primitives", default-features = false } primitives = { package = "substrate-primitives", path = "../../../primitives", default-features = false } +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } [features] default = ["std"] @@ -18,4 +19,5 @@ std = [ "substrate-client/std", "sr-primitives/std", "primitives/std", + "codec/std", ] diff --git a/core/consensus/pow/primitives/src/lib.rs b/core/consensus/pow/primitives/src/lib.rs index 807a7b2df2c90..2079b1cbe7e88 100644 --- a/core/consensus/pow/primitives/src/lib.rs +++ b/core/consensus/pow/primitives/src/lib.rs @@ -20,17 +20,46 @@ use rstd::vec::Vec; use sr_primitives::ConsensusEngineId; +use codec::Decode; +use substrate_client::decl_runtime_apis; /// The `ConsensusEngineId` of PoW. pub const POW_ENGINE_ID: ConsensusEngineId = [b'p', b'o', b'w', b'_']; -/// Type of difficulty. -/// -/// For runtime designed for Substrate, it's always possible to fit its total -/// difficulty range under `u128::max_value()` because it can be freely scaled -/// up or scaled down. Very few PoW chains use difficulty values -/// larger than `u128::max_value()`. -pub type Difficulty = u128; - /// Type of seal. pub type Seal = Vec; + +/// Define methods that total difficulty should implement. +pub trait TotalDifficulty { + fn increment(&mut self, other: Self); +} + +impl TotalDifficulty for primitives::U256 { + fn increment(&mut self, other: Self) { + let ret = self.saturating_add(other); + *self = ret; + } +} + +impl TotalDifficulty for u128 { + fn increment(&mut self, other: Self) { + let ret = self.saturating_add(other); + *self = ret; + } +} + +decl_runtime_apis! { + /// API necessary for timestamp-based difficulty adjustment algorithms. + pub trait TimestampApi { + /// Return the timestamp in the current block. + fn timestamp() -> Moment; + } + + /// API for those chains that put their difficulty adjustment algorithm directly + /// onto runtime. Note that while putting difficulty adjustment algorithm to + /// runtime is safe, putting the PoW algorithm on runtime is not. + pub trait DifficultyApi { + /// Return the target difficulty of the next block. + fn difficulty() -> Difficulty; + } +} diff --git a/core/consensus/pow/src/lib.rs b/core/consensus/pow/src/lib.rs index 8bc0d3593a026..7f282e20523bf 100644 --- a/core/consensus/pow/src/lib.rs +++ b/core/consensus/pow/src/lib.rs @@ -41,12 +41,12 @@ use sr_primitives::Justification; use sr_primitives::generic::{BlockId, Digest, DigestItem}; use sr_primitives::traits::{Block as BlockT, Header as HeaderT, ProvideRuntimeApi}; use srml_timestamp::{TimestampInherentData, InherentError as TIError}; -use pow_primitives::{Difficulty, Seal, POW_ENGINE_ID}; +use pow_primitives::{Seal, TotalDifficulty, POW_ENGINE_ID}; use primitives::H256; use inherents::{InherentDataProviders, InherentData}; use consensus_common::{ - BlockImportParams, BlockOrigin, ForkChoiceStrategy, - Environment, Proposer, + BlockImportParams, BlockOrigin, ForkChoiceStrategy, SyncOracle, Environment, Proposer, + SelectChain, }; use consensus_common::import_queue::{BoxBlockImport, BasicQueue, Verifier}; use codec::{Encode, Decode}; @@ -63,59 +63,78 @@ fn aux_key(hash: &H256) -> Vec { /// Auxiliary storage data for PoW. #[derive(Encode, Decode, Clone, Debug, Default)] -pub struct PowAux { - /// Total difficulty. +pub struct PowAux { + /// Difficulty of the current block. + pub difficulty: Difficulty, + /// Total difficulty up to current block. pub total_difficulty: Difficulty, } -impl PowAux { +impl PowAux where + Difficulty: Decode + Default, +{ /// Read the auxiliary from client. pub fn read(client: &C, hash: &H256) -> Result { let key = aux_key(hash); match client.get_aux(&key).map_err(|e| format!("{:?}", e))? { - Some(bytes) => PowAux::decode(&mut &bytes[..]).map_err(|e| format!("{:?}", e)), - None => Ok(PowAux::default()), + Some(bytes) => Self::decode(&mut &bytes[..]) + .map_err(|e| format!("{:?}", e)), + None => Ok(Self::default()), } } } /// Algorithm used for proof of work. pub trait PowAlgorithm { + /// Difficulty for the algorithm. + type Difficulty: TotalDifficulty + Default + Encode + Decode + Ord + Clone + Copy; + /// Get the next block's difficulty. - fn difficulty(&self, parent: &BlockId) -> Result; + fn difficulty(&self, parent: &BlockId) -> Result; /// Verify proof of work against the given difficulty. fn verify( &self, parent: &BlockId, pre_hash: &H256, seal: &Seal, - difficulty: Difficulty, + difficulty: Self::Difficulty, ) -> Result; /// Mine a seal that satisfy the given difficulty. fn mine( &self, parent: &BlockId, pre_hash: &H256, - seed: &H256, - difficulty: Difficulty, + difficulty: Self::Difficulty, round: u32, ) -> Result, String>; } /// A verifier for PoW blocks. -pub struct PowVerifier { +pub struct PowVerifier, C, S, Algorithm> { client: Arc, algorithm: Algorithm, inherent_data_providers: inherents::InherentDataProviders, + select_chain: Option, + check_inherents_after: <::Header as HeaderT>::Number, } -impl PowVerifier { - fn check_header>( +impl, C, S, Algorithm> PowVerifier { + pub fn new( + client: Arc, + algorithm: Algorithm, + check_inherents_after: <::Header as HeaderT>::Number, + select_chain: Option, + inherent_data_providers: inherents::InherentDataProviders, + ) -> Self { + Self { client, algorithm, inherent_data_providers, select_chain, check_inherents_after } + } + + fn check_header( &self, mut header: B::Header, parent_block_id: BlockId, - ) -> Result<(B::Header, Difficulty, DigestItem), String> where + ) -> Result<(B::Header, Algorithm::Difficulty, DigestItem), String> where Algorithm: PowAlgorithm, { let hash = header.hash(); @@ -146,7 +165,7 @@ impl PowVerifier { Ok((header, difficulty, seal)) } - fn check_inherents>( + fn check_inherents( &self, block: B, block_id: BlockId, @@ -157,6 +176,10 @@ impl PowVerifier { { const MAX_TIMESTAMP_DRIFT_SECS: u64 = 60; + if *block.header().number() < self.check_inherents_after { + return Ok(()) + } + let inherent_res = self.client.runtime_api().check_inherents( &block_id, block, @@ -183,9 +206,10 @@ impl PowVerifier { } } -impl, C, Algorithm> Verifier for PowVerifier where +impl, C, S, Algorithm> Verifier for PowVerifier where C: ProvideRuntimeApi + Send + Sync + HeaderBackend + AuxStore + ProvideCache + BlockOf, C::Api: BlockBuilderApi, + S: SelectChain, Algorithm: PowAlgorithm + Send + Sync, { fn verify( @@ -199,17 +223,23 @@ impl, C, Algorithm> Verifier for PowVerifier select_chain.best_chain() + .map_err(|e| format!("Fetch best chain failed via select chain: {:?}", e))? + .hash(), + None => self.client.info().best_hash, + }; let hash = header.hash(); let parent_hash = *header.parent_hash(); let best_aux = PowAux::read(self.client.as_ref(), &best_hash)?; let mut aux = PowAux::read(self.client.as_ref(), &parent_hash)?; - let (checked_header, difficulty, seal) = self.check_header::( + let (checked_header, difficulty, seal) = self.check_header( header, BlockId::Hash(parent_hash), )?; - aux.total_difficulty = aux.total_difficulty.saturating_add(difficulty); + aux.difficulty = difficulty; + aux.total_difficulty.increment(difficulty); if let Some(inner_body) = body.take() { let block = B::new(checked_header.clone(), inner_body); @@ -241,7 +271,7 @@ impl, C, Algorithm> Verifier for PowVerifier Result<(), consensus_common::Error> { if !inherent_data_providers.has_provider(&srml_timestamp::INHERENT_IDENTIFIER) { @@ -258,10 +288,12 @@ fn register_pow_inherent_data_provider( pub type PowImportQueue = BasicQueue; /// Import queue for PoW engine. -pub fn import_queue( +pub fn import_queue( block_import: BoxBlockImport, client: Arc, algorithm: Algorithm, + check_inherents_after: <::Header as HeaderT>::Number, + select_chain: Option, inherent_data_providers: InherentDataProviders, ) -> Result, consensus_common::Error> where B: BlockT, @@ -269,14 +301,17 @@ pub fn import_queue( C: Send + Sync + AuxStore + 'static, C::Api: BlockBuilderApi, Algorithm: PowAlgorithm + Send + Sync + 'static, + S: SelectChain + 'static, { register_pow_inherent_data_provider(&inherent_data_providers)?; - let verifier = PowVerifier { - client: client.clone(), + let verifier = PowVerifier::new( + client.clone(), algorithm, + check_inherents_after, + select_chain, inherent_data_providers, - }; + ); Ok(BasicQueue::new( verifier, @@ -296,19 +331,24 @@ pub fn import_queue( /// information, or just be a graffiti. `round` is for number of rounds the /// CPU miner runs each time. This parameter should be tweaked so that each /// mining round is within sub-second time. -pub fn start_mine, C, Algorithm, E>( +pub fn start_mine, C, Algorithm, E, SO, S>( mut block_import: BoxBlockImport, client: Arc, algorithm: Algorithm, mut env: E, preruntime: Option>, round: u32, + mut sync_oracle: SO, + build_time: std::time::Duration, + select_chain: Option, inherent_data_providers: inherents::InherentDataProviders, ) where C: HeaderBackend + AuxStore + 'static, Algorithm: PowAlgorithm + Send + Sync + 'static, E: Environment + Send + Sync + 'static, E::Error: std::fmt::Debug, + SO: SyncOracle + Send + Sync + 'static, + S: SelectChain + 'static, { if let Err(_) = register_pow_inherent_data_provider(&inherent_data_providers) { warn!("Registering inherent data provider for timestamp failed"); @@ -323,6 +363,9 @@ pub fn start_mine, C, Algorithm, E>( &mut env, preruntime.as_ref(), round, + &mut sync_oracle, + build_time.clone(), + select_chain.as_ref(), &inherent_data_providers ) { Ok(()) => (), @@ -331,31 +374,52 @@ pub fn start_mine, C, Algorithm, E>( e ), } - std::thread::sleep(std::time::Duration::new(1, 0)); } }); } -fn mine_loop, C, Algorithm, E>( +fn mine_loop, C, Algorithm, E, SO, S>( block_import: &mut BoxBlockImport, client: &C, algorithm: &Algorithm, env: &mut E, preruntime: Option<&Vec>, round: u32, + sync_oracle: &mut SO, + build_time: std::time::Duration, + select_chain: Option<&S>, inherent_data_providers: &inherents::InherentDataProviders, ) -> Result<(), String> where C: HeaderBackend + AuxStore, Algorithm: PowAlgorithm, E: Environment, E::Error: std::fmt::Debug, + SO: SyncOracle, + S: SelectChain, { 'outer: loop { - let best_hash = client.info().best_hash; - let best_header = client.header(BlockId::Hash(best_hash)) - .map_err(|e| format!("Fetching best header failed: {:?}", e))? - .ok_or("Best header does not exist")?; + if sync_oracle.is_major_syncing() { + debug!(target: "pow", "Skipping proposal due to sync."); + std::thread::sleep(std::time::Duration::new(1, 0)); + continue 'outer + } + + let (best_hash, best_header) = match select_chain { + Some(select_chain) => { + let header = select_chain.best_chain() + .map_err(|e| format!("Fetching best header failed using select chain: {:?}", e))?; + let hash = header.hash(); + (hash, header) + }, + None => { + let hash = client.info().best_hash; + let header = client.header(BlockId::Hash(hash)) + .map_err(|e| format!("Fetching best header failed: {:?}", e))? + .ok_or("Best header does not exist")?; + (hash, header) + }, + }; let mut aux = PowAux::read(client, &best_hash)?; let mut proposer = env.init(&best_header).map_err(|e| format!("{:?}", e))?; @@ -368,21 +432,19 @@ fn mine_loop, C, Algorithm, E>( let block = futures::executor::block_on(proposer.propose( inherent_data, inherent_digest, - std::time::Duration::new(0, 0) + build_time.clone(), )).map_err(|e| format!("Block proposing error: {:?}", e))?; let (header, body) = block.deconstruct(); - let seed = H256::random(); let (difficulty, seal) = { - loop { - let difficulty = algorithm.difficulty( - &BlockId::Hash(best_hash), - )?; + let difficulty = algorithm.difficulty( + &BlockId::Hash(best_hash), + )?; + loop { let seal = algorithm.mine( &BlockId::Hash(best_hash), &header.hash(), - &seed, difficulty, round, )?; @@ -397,10 +459,28 @@ fn mine_loop, C, Algorithm, E>( } }; - aux.total_difficulty = aux.total_difficulty.saturating_add(difficulty); - let hash = header.hash(); + aux.difficulty = difficulty; + aux.total_difficulty.increment(difficulty); + let hash = { + let mut header = header.clone(); + header.digest_mut().push(DigestItem::Seal(POW_ENGINE_ID, seal.clone())); + header.hash() + }; let key = aux_key(&hash); + let best_hash = match select_chain { + Some(select_chain) => select_chain.best_chain() + .map_err(|e| format!("Fetch best hash failed via select chain: {:?}", e))? + .hash(), + None => client.info().best_hash, + }; + let best_aux = PowAux::::read(client, &best_hash)?; + + // if the best block has changed in the meantime drop our proposal + if best_aux.total_difficulty > aux.total_difficulty { + continue 'outer + } + let import_block = BlockImportParams { origin: BlockOrigin::Own, header, diff --git a/core/network/src/chain.rs b/core/network/src/chain.rs index 1a1f649cae2dd..f68942fd96d38 100644 --- a/core/network/src/chain.rs +++ b/core/network/src/chain.rs @@ -83,6 +83,12 @@ pub trait FinalityProofProvider: Send + Sync { fn prove_finality(&self, for_block: Block::Hash, request: &[u8]) -> Result>, Error>; } +impl FinalityProofProvider for () { + fn prove_finality(&self, _for_block: Block::Hash, _request: &[u8]) -> Result>, Error> { + Ok(None) + } +} + impl Client for SubstrateClient where B: client::backend::Backend + Send + Sync + 'static, E: CallExecutor + Send + Sync + 'static,