diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index ac20b8ad39d1a628e447d0a7929c0c835893033c..aef16434d5382b3e730d8eadcbbbc49ee8034290 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -1587,24 +1587,28 @@ impl<'a, B, E, Block, RA> consensus::BlockImport<Block> for &'a Client<B, E, Blo } } - match self.block_status(&BlockId::Hash(parent_hash)) - .map_err(|e| ConsensusError::ClientImport(e.to_string()))? - { - BlockStatus::InChainWithState | BlockStatus::Queued => {}, - BlockStatus::Unknown => return Ok(ImportResult::UnknownParent), - BlockStatus::InChainPruned if allow_missing_state => {}, - BlockStatus::InChainPruned => return Ok(ImportResult::MissingState), - BlockStatus::KnownBad => return Ok(ImportResult::KnownBad), - } - + // Own status must be checked first. If the block and ancestry is pruned + // this function must return `AlreadyInChain` rather than `MissingState` match self.block_status(&BlockId::Hash(hash)) .map_err(|e| ConsensusError::ClientImport(e.to_string()))? { BlockStatus::InChainWithState | BlockStatus::Queued => return Ok(ImportResult::AlreadyInChain), - BlockStatus::Unknown | BlockStatus::InChainPruned => {}, + BlockStatus::InChainPruned => return Ok(ImportResult::AlreadyInChain), + BlockStatus::Unknown => {}, BlockStatus::KnownBad => return Ok(ImportResult::KnownBad), } + match self.block_status(&BlockId::Hash(parent_hash)) + .map_err(|e| ConsensusError::ClientImport(e.to_string()))? + { + BlockStatus::InChainWithState | BlockStatus::Queued => {}, + BlockStatus::Unknown => return Ok(ImportResult::UnknownParent), + BlockStatus::InChainPruned if allow_missing_state => {}, + BlockStatus::InChainPruned => return Ok(ImportResult::MissingState), + BlockStatus::KnownBad => return Ok(ImportResult::KnownBad), + } + + Ok(ImportResult::imported(false)) } } @@ -1919,7 +1923,7 @@ pub(crate) mod tests { use super::*; use primitives::blake2_256; use sr_primitives::DigestItem; - use consensus::{BlockOrigin, SelectChain}; + use consensus::{BlockOrigin, SelectChain, BlockImport}; use test_client::{ prelude::*, client_db::{Backend, DatabaseSettings, DatabaseSettingsSrc, PruningMode}, @@ -2889,4 +2893,103 @@ pub(crate) mod tests { expected_err.to_string(), ); } + + #[test] + fn returns_status_for_pruned_blocks() { + let _ = env_logger::try_init(); + let tmp = tempfile::tempdir().unwrap(); + + // set to prune after 1 block + // states + let backend = Arc::new(Backend::new( + DatabaseSettings { + state_cache_size: 1 << 20, + state_cache_child_ratio: None, + pruning: PruningMode::keep_blocks(1), + source: DatabaseSettingsSrc::Path { + path: tmp.path().into(), + cache_size: None, + } + }, + u64::max_value(), + ).unwrap()); + + let mut client = TestClientBuilder::with_backend(backend).build(); + + let a1 = client.new_block_at(&BlockId::Number(0), Default::default()) + .unwrap().bake().unwrap(); + + let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default()).unwrap(); + + // b1 is created, but not imported + b1.push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 1, + nonce: 0, + }).unwrap(); + let b1 = b1.bake().unwrap(); + + let check_block_a1 = BlockCheckParams { + hash: a1.hash().clone(), + number: 0, + parent_hash: a1.header().parent_hash().clone(), + allow_missing_state: false + }; + + assert_eq!(client.check_block(check_block_a1.clone()).unwrap(), ImportResult::imported(false)); + assert_eq!(client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), BlockStatus::Unknown); + + client.import_as_final(BlockOrigin::Own, a1.clone()).unwrap(); + + assert_eq!(client.check_block(check_block_a1.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), BlockStatus::InChainWithState); + + let a2 = client.new_block_at(&BlockId::Hash(a1.hash()), Default::default()) + .unwrap().bake().unwrap(); + client.import_as_final(BlockOrigin::Own, a2.clone()).unwrap(); + + let check_block_a2 = BlockCheckParams { + hash: a2.hash().clone(), + number: 1, + parent_hash: a1.header().parent_hash().clone(), + allow_missing_state: false + }; + + assert_eq!(client.check_block(check_block_a1.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), BlockStatus::InChainPruned); + assert_eq!(client.check_block(check_block_a2.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a2.hash)).unwrap(), BlockStatus::InChainWithState); + + let a3 = client.new_block_at(&BlockId::Hash(a2.hash()), Default::default()) + .unwrap().bake().unwrap(); + + client.import_as_final(BlockOrigin::Own, a3.clone()).unwrap(); + let check_block_a3 = BlockCheckParams { + hash: a3.hash().clone(), + number: 2, + parent_hash: a2.header().parent_hash().clone(), + allow_missing_state: false + }; + + // a1 and a2 are both pruned at this point + assert_eq!(client.check_block(check_block_a1.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a1.hash)).unwrap(), BlockStatus::InChainPruned); + assert_eq!(client.check_block(check_block_a2.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a2.hash)).unwrap(), BlockStatus::InChainPruned); + assert_eq!(client.check_block(check_block_a3.clone()).unwrap(), ImportResult::AlreadyInChain); + assert_eq!(client.block_status(&BlockId::hash(check_block_a3.hash)).unwrap(), BlockStatus::InChainWithState); + + let mut check_block_b1 = BlockCheckParams { + hash: b1.hash().clone(), + number: 0, + parent_hash: b1.header().parent_hash().clone(), + allow_missing_state: false + }; + assert_eq!(client.check_block(check_block_b1.clone()).unwrap(), ImportResult::MissingState); + check_block_b1.allow_missing_state = true; + assert_eq!(client.check_block(check_block_b1.clone()).unwrap(), ImportResult::imported(false)); + check_block_b1.parent_hash = H256::random(); + assert_eq!(client.check_block(check_block_b1.clone()).unwrap(), ImportResult::UnknownParent); + } } diff --git a/substrate/core/consensus/common/src/block_import.rs b/substrate/core/consensus/common/src/block_import.rs index 14450841a32d116b76c1f391192d0953d376699e..79d9be7b84e2363388d1b0c4431784d656c7c2de 100644 --- a/substrate/core/consensus/common/src/block_import.rs +++ b/substrate/core/consensus/common/src/block_import.rs @@ -95,6 +95,7 @@ pub enum ForkChoiceStrategy { } /// Data required to check validity of a Block. +#[derive(Debug, PartialEq, Eq, Clone)] pub struct BlockCheckParams<Block: BlockT> { /// Hash of the block that we verify. pub hash: Block::Hash, diff --git a/substrate/core/consensus/common/src/import_queue.rs b/substrate/core/consensus/common/src/import_queue.rs index 15be6f230a1b0c53511b0c7d069249b3248d6485..4bc986e1a3ee60a5ee5cb8238fdadcc0248ca28c 100644 --- a/substrate/core/consensus/common/src/import_queue.rs +++ b/substrate/core/consensus/common/src/import_queue.rs @@ -163,6 +163,8 @@ pub enum BlockImportError { VerificationFailed(Option<Origin>, String), /// Block is known to be Bad BadBlock(Option<Origin>), + /// Parent state is missing. + MissingState, /// Block has an unknown parent UnknownParent, /// Block import has been cancelled. This can happen if the parent block fails to be imported. @@ -207,7 +209,7 @@ pub fn import_single_block<B: BlockT, V: Verifier<B>>( Ok(ImportResult::Imported(aux)) => Ok(BlockImportResult::ImportedUnknown(number, aux, peer.clone())), Ok(ImportResult::MissingState) => { debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash); - Err(BlockImportError::UnknownParent) + Err(BlockImportError::MissingState) }, Ok(ImportResult::UnknownParent) => { debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent_hash); diff --git a/substrate/core/network/src/protocol.rs b/substrate/core/network/src/protocol.rs index f06f0cd937b5f60497cca382aead8049728ff9cd..3715d3c9d068a1b929d375ea1248e51430f4b473 100644 --- a/substrate/core/network/src/protocol.rs +++ b/substrate/core/network/src/protocol.rs @@ -1009,12 +1009,14 @@ impl<B: BlockT, S: NetworkSpecialization<B>, H: ExHashT> Protocol<B, S, H> { behaviour: &mut self.behaviour, peerset: self.peerset_handle.clone(), }, who.clone(), status.roles, status.best_number); - match self.sync.new_peer(who.clone(), info) { - Ok(None) => (), - Ok(Some(req)) => self.send_request(&who, GenericMessage::BlockRequest(req)), - Err(sync::BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id); - self.peerset_handle.report_peer(id, repu) + if info.roles.is_full() { + match self.sync.new_peer(who.clone(), info.best_hash, info.best_number) { + Ok(None) => (), + Ok(Some(req)) => self.send_request(&who, GenericMessage::BlockRequest(req)), + Err(sync::BadPeer(id, repu)) => { + self.behaviour.disconnect_peer(&id); + self.peerset_handle.report_peer(id, repu) + } } } let mut context = ProtocolContext::new(&mut self.context_data, &mut self.behaviour, &self.peerset_handle); @@ -1341,12 +1343,10 @@ impl<B: BlockT, S: NetworkSpecialization<B>, H: ExHashT> Protocol<B, S, H> { count: usize, results: Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)> ) { - let peers = self.context_data.peers.clone(); let results = self.sync.on_blocks_processed( imported, count, results, - |peer_id| peers.get(peer_id).map(|i| i.info.clone()) ); for result in results { match result { diff --git a/substrate/core/network/src/protocol/sync.rs b/substrate/core/network/src/protocol/sync.rs index a7e6139e48dd37e4a116e3fce3b99ec44d07d8ef..a51a91403363842323f366492c41537ba75ed620 100644 --- a/substrate/core/network/src/protocol/sync.rs +++ b/substrate/core/network/src/protocol/sync.rs @@ -37,7 +37,6 @@ use crate::{ config::{Roles, BoxFinalityProofRequestBuilder}, message::{self, generic::FinalityProofRequest, BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse, FinalityProofResponse}, - protocol }; use either::Either; use extra_requests::ExtraRequests; @@ -347,23 +346,22 @@ impl<B: BlockT> ChainSync<B> { /// Handle a new connected peer. /// /// Call this method whenever we connect to a new peer. - pub fn new_peer(&mut self, who: PeerId, info: protocol::PeerInfo<B>) -> Result<Option<BlockRequest<B>>, BadPeer> { + pub fn new_peer(&mut self, who: PeerId, best_hash: B::Hash, best_number: NumberFor<B>) + -> Result<Option<BlockRequest<B>>, BadPeer> + { // There is nothing sync can get from the node that has no blockchain data. - if !info.roles.is_full() { - return Ok(None) - } - match self.block_status(&info.best_hash) { + match self.block_status(&best_hash) { Err(e) => { debug!(target:"sync", "Error reading blockchain: {:?}", e); Err(BadPeer(who, BLOCKCHAIN_STATUS_READ_ERROR_REPUTATION_CHANGE)) } Ok(BlockStatus::KnownBad) => { - info!("New peer with known bad best block {} ({}).", info.best_hash, info.best_number); + info!("New peer with known bad best block {} ({}).", best_hash, best_number); Err(BadPeer(who, i32::min_value())) } Ok(BlockStatus::Unknown) => { - if info.best_number.is_zero() { - info!("New peer with unknown genesis hash {} ({}).", info.best_hash, info.best_number); + if best_number.is_zero() { + info!("New peer with unknown genesis hash {} ({}).", best_hash, best_number); return Err(BadPeer(who, i32::min_value())) } // If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have @@ -378,8 +376,8 @@ impl<B: BlockT> ChainSync<B> { ); self.peers.insert(who, PeerSync { common_number: self.best_queued_number, - best_hash: info.best_hash, - best_number: info.best_number, + best_hash, + best_number, state: PeerSyncState::Available, recently_announced: Default::default() }); @@ -388,11 +386,11 @@ impl<B: BlockT> ChainSync<B> { // If we are at genesis, just start downloading. if self.best_queued_number.is_zero() { - debug!(target:"sync", "New peer with best hash {} ({}).", info.best_hash, info.best_number); + debug!(target:"sync", "New peer with best hash {} ({}).", best_hash, best_number); self.peers.insert(who.clone(), PeerSync { common_number: Zero::zero(), - best_hash: info.best_hash, - best_number: info.best_number, + best_hash, + best_number, state: PeerSyncState::Available, recently_announced: Default::default(), }); @@ -400,18 +398,18 @@ impl<B: BlockT> ChainSync<B> { return Ok(None) } - let common_best = std::cmp::min(self.best_queued_number, info.best_number); + let common_best = std::cmp::min(self.best_queued_number, best_number); debug!(target:"sync", "New peer with unknown best hash {} ({}), searching for common ancestor.", - info.best_hash, - info.best_number + best_hash, + best_number ); self.peers.insert(who, PeerSync { common_number: Zero::zero(), - best_hash: info.best_hash, - best_number: info.best_number, + best_hash, + best_number, state: PeerSyncState::AncestorSearch( common_best, AncestorSearchState::ExponentialBackoff(One::one()) @@ -423,11 +421,11 @@ impl<B: BlockT> ChainSync<B> { Ok(Some(ancestry_request::<B>(common_best))) } Ok(BlockStatus::Queued) | Ok(BlockStatus::InChainWithState) | Ok(BlockStatus::InChainPruned) => { - debug!(target:"sync", "New peer with known best hash {} ({}).", info.best_hash, info.best_number); + debug!(target:"sync", "New peer with known best hash {} ({}).", best_hash, best_number); self.peers.insert(who.clone(), PeerSync { - common_number: info.best_number, - best_hash: info.best_hash, - best_number: info.best_number, + common_number: best_number, + best_hash, + best_number, state: PeerSyncState::Available, recently_announced: Default::default(), }); @@ -849,7 +847,6 @@ impl<B: BlockT> ChainSync<B> { imported: usize, count: usize, results: Vec<(Result<BlockImportResult<NumberFor<B>>, BlockImportError>, B::Hash)>, - mut peer_info: impl FnMut(&PeerId) -> Option<protocol::PeerInfo<B>> ) -> impl Iterator<Item = Result<(PeerId, BlockRequest<B>), BadPeer>> + 'a { trace!(target: "sync", "Imported {} of {}", imported, count); @@ -902,27 +899,33 @@ impl<B: BlockT> ChainSync<B> { if let Some(peer) = who { info!("Peer sent block with incomplete header to import"); output.push(Err(BadPeer(peer, INCOMPLETE_HEADER_REPUTATION_CHANGE))); - output.extend(self.restart(&mut peer_info)); + output.extend(self.restart()); } }, Err(BlockImportError::VerificationFailed(who, e)) => { if let Some(peer) = who { info!("Verification failed from peer: {}", e); output.push(Err(BadPeer(peer, VERIFICATION_FAIL_REPUTATION_CHANGE))); - output.extend(self.restart(&mut peer_info)); + output.extend(self.restart()); } }, Err(BlockImportError::BadBlock(who)) => { if let Some(peer) = who { info!("Bad block"); output.push(Err(BadPeer(peer, BAD_BLOCK_REPUTATION_CHANGE))); - output.extend(self.restart(&mut peer_info)); + output.extend(self.restart()); } }, + Err(BlockImportError::MissingState) => { + // This may happen if the chain we were requesting upon has been discarded + // in the meantime becasue other chain has been finalized. + // Don't mark it as bad as it still may be synced if explicitly requested. + trace!(target: "sync", "Obsolete block"); + }, Err(BlockImportError::UnknownParent) | Err(BlockImportError::Cancelled) | Err(BlockImportError::Other(_)) => { - output.extend(self.restart(&mut peer_info)); + output.extend(self.restart()); }, }; } @@ -1121,9 +1124,7 @@ impl<B: BlockT> ChainSync<B> { } /// Restart the sync process. - fn restart<'a, F> - (&'a mut self, mut peer_info: F) -> impl Iterator<Item = Result<(PeerId, BlockRequest<B>), BadPeer>> + 'a - where F: FnMut(&PeerId) -> Option<protocol::PeerInfo<B>> + 'a + fn restart<'a>(&'a mut self) -> impl Iterator<Item = Result<(PeerId, BlockRequest<B>), BadPeer>> + 'a { self.queue_blocks.clear(); self.best_importing_number = Zero::zero(); @@ -1134,9 +1135,8 @@ impl<B: BlockT> ChainSync<B> { self.is_idle = false; debug!(target:"sync", "Restarted with {} ({})", self.best_queued_number, self.best_queued_hash); let old_peers = std::mem::replace(&mut self.peers, HashMap::new()); - old_peers.into_iter().filter_map(move |(id, _)| { - let info = peer_info(&id)?; - match self.new_peer(id.clone(), info) { + old_peers.into_iter().filter_map(move |(id, p)| { + match self.new_peer(id.clone(), p.best_hash, p.best_number) { Ok(None) => None, Ok(Some(x)) => Some(Ok((id, x))), Err(e) => Some(Err(e)) diff --git a/substrate/core/test-client/src/client_ext.rs b/substrate/core/test-client/src/client_ext.rs index b3bc702afb0a65ddd723dfeca3587c763e945c26..8b6286b57699a488fea7bebf722734fb4b4e0dbc 100644 --- a/substrate/core/test-client/src/client_ext.rs +++ b/substrate/core/test-client/src/client_ext.rs @@ -38,6 +38,10 @@ pub trait ClientExt<Block: BlockT>: Sized { fn import_as_best(&self, origin: BlockOrigin, block: Block) -> Result<(), ConsensusError>; + /// Import a block and finalize it. + fn import_as_final(&self, origin: BlockOrigin, block: Block) + -> Result<(), ConsensusError>; + /// Import block with justification, finalizes block. fn import_justified( &self, @@ -102,6 +106,25 @@ impl<B, E, RA, Block> ClientExt<Block> for Client<B, E, Block, RA> BlockImport::import_block(&mut (&*self), import, HashMap::new()).map(|_| ()) } + fn import_as_final(&self, origin: BlockOrigin, block: Block) + -> Result<(), ConsensusError> + { + let (header, extrinsics) = block.deconstruct(); + let import = BlockImportParams { + origin, + header, + justification: None, + post_digests: vec![], + body: Some(extrinsics), + finalized: true, + auxiliary: Vec::new(), + fork_choice: ForkChoiceStrategy::Custom(true), + allow_missing_state: false, + }; + + BlockImport::import_block(&mut (&*self), import, HashMap::new()).map(|_| ()) + } + fn import_justified( &self, origin: BlockOrigin,