From 9d58b467ba37ba7c64bb287292f7c7897bedf828 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 28 Jan 2019 14:49:18 +0300 Subject: [PATCH 01/42] GrandpaLightBlockImport --- Cargo.lock | 2 + core/client/src/error.rs | 8 +- core/finality-grandpa/src/lib.rs | 53 ++- core/finality-grandpa/src/light_import.rs | 327 ++++++++++++++++++ .../src/service_integration.rs | 21 +- node/cli/src/service.rs | 23 +- 6 files changed, 400 insertions(+), 34 deletions(-) create mode 100644 core/finality-grandpa/src/light_import.rs diff --git a/Cargo.lock b/Cargo.lock index 9a008f2e7185e..a78dec45b914d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. [[package]] name = "MacTypes-sys" version = "2.1.0" diff --git a/core/client/src/error.rs b/core/client/src/error.rs index f8ac292c25ae7..7517072ca25b5 100644 --- a/core/client/src/error.rs +++ b/core/client/src/error.rs @@ -80,7 +80,13 @@ error_chain! { display("Genesis config provided is invalid"), } - /// Bad justification for header. + /// Justification for the header has invalid encoding. + JustificationDecode { + description("Error decoding justification for header"), + display("Error decoding justification header"), + } + + /// Justification for the header is properly encoded, but invalid. BadJustification(h: String) { description("bad justification for header"), display("bad justification for header: {}", &*h), diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 7cec57d045928..0a2a18a7edb6e 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -120,14 +120,16 @@ pub use fg_primitives::ScheduledChange; mod authorities; mod communication; mod finality_proof; +mod light_import; mod until_imported; #[cfg(feature="service-integration")] mod service_integration; #[cfg(feature="service-integration")] -pub use service_integration::{LinkHalfForService, BlockImportForService}; +pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService}; pub use finality_proof::{prove_finality, check_finality_proof}; +pub use light_import::light_block_import; #[cfg(test)] mod tests; @@ -327,6 +329,11 @@ impl ConsensusChanges { ConsensusChanges { pending_changes: Vec::new(), } } + /// Returns reference to all pending changes. + pub fn pending_changes(&self) -> &[(N, H)] { + &self.pending_changes + } + /// Note unfinalized change of consensus-related data. pub fn note_change(&mut self, at: (N, H)) { let idx = self.pending_changes @@ -734,16 +741,15 @@ impl> GrandpaJustification { /// Decode a GRANDPA justification and validate the commit and the votes' /// ancestry proofs. fn decode_and_verify( - encoded: Vec, + encoded: &[u8], set_id: u64, voters: &HashMap, ) -> Result, ClientError> where NumberFor: grandpa::BlockNumberOps, { - GrandpaJustification::::decode(&mut &*encoded).ok_or_else(|| { - let msg = "failed to decode grandpa justification".to_string(); - ClientErrorKind::BadJustification(msg).into() - }).and_then(|just| just.verify(set_id, voters).map(|_| just)) + GrandpaJustification::::decode(&mut &*encoded).ok_or_else(|| + ClientErrorKind::JustificationDecode.into() + ).and_then(|just| just.verify(set_id, voters).map(|_| just)) } /// Validate the commit and the votes' ancestry proofs. @@ -1258,7 +1264,7 @@ impl, RA, PRA> enacts_change: bool, ) -> Result<(), ConsensusError> { let justification = GrandpaJustification::decode_and_verify( - justification, + &justification, self.authority_set.set_id(), &self.authority_set.current_authorities(), ); @@ -1393,11 +1399,31 @@ impl grandpa::Chain> for AncestryCh } } +/// Load consensus changes from aux store OR create default if it is missing. +pub(crate) fn load_consensus_changes, RA>( + client: &Client, + key: &[u8], +) -> Result>, ClientError> + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + let consensus_changes = Backend::get_aux(&**client.backend(), key)?; + match consensus_changes { + Some(raw) => ConsensusChanges::decode(&mut &raw[..]) + .ok_or_else(|| ::client::error::ErrorKind::Backend( + format!("GRANDPA consensus changes kept in invalid format") + ).into()), + None => Ok(ConsensusChanges::empty()), + } +} + /// Make block importer and link half necessary to tie the background voter /// to it. pub fn block_import, RA, PRA>( client: Arc>, - api: Arc + api: Arc, ) -> Result<(GrandpaBlockImport, LinkHalf), ClientError> where B: Backend + 'static, @@ -1431,14 +1457,9 @@ pub fn block_import, RA, PRA>( .into(), }; - let consensus_changes = Backend::get_aux(&**client.backend(), CONSENSUS_CHANGES_KEY)?; - let consensus_changes = Arc::new(parking_lot::Mutex::new(match consensus_changes { - Some(raw) => ConsensusChanges::decode(&mut &raw[..]) - .ok_or_else(|| ::client::error::ErrorKind::Backend( - format!("GRANDPA consensus changes kept in invalid format") - ))?, - None => ConsensusChanges::empty(), - })); + let consensus_changes = Arc::new(parking_lot::Mutex::new( + load_consensus_changes(&*client, CONSENSUS_CHANGES_KEY)?, + )); let (authority_set_change_tx, authority_set_change_rx) = mpsc::unbounded(); diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs new file mode 100644 index 0000000000000..9cf079f70999c --- /dev/null +++ b/core/finality-grandpa/src/light_import.rs @@ -0,0 +1,327 @@ +use std::collections::HashMap; +use std::sync::Arc; +use parking_lot::RwLock; + +use client::{ + CallExecutor, Client, backend::Backend, + error::Error as ClientError, error::ErrorKind as ClientErrorKind, +}; +use client::blockchain::HeaderBackend; +use codec::{Encode, Decode}; +use consensus_common::{BlockImport, JustificationImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; +use runtime_primitives::Justification; +use runtime_primitives::traits::{ + NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, + DigestItem, DigestFor, DigestItemFor, +}; +use fg_primitives::GrandpaApi; +use runtime_primitives::generic::BlockId; +use substrate_primitives::{H256, Ed25519AuthorityId, Blake2Hasher}; + +use {load_consensus_changes, canonical_at_height, ConsensusChanges, GrandpaJustification}; + +/// LightAuthoritySet is saved under this key in aux storage. +const LIGHT_AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters"; +/// ConsensusChanges is saver under this key in aux storage. +const LIGHT_CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes"; + +/// Create light block importer. +pub fn light_block_import, RA, PRA>( + client: Arc>, + api: Arc, +) -> Result, ClientError> + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi +{ + use runtime_primitives::traits::Zero; + let authority_set = match Backend::get_aux(&**client.backend(), LIGHT_AUTHORITY_SET_KEY)? { + None => { + info!(target: "afg", "Loading GRANDPA authorities \ + from genesis on what appears to be first startup."); + + // no authority set on disk: fetch authorities from genesis state. + // if genesis state is not available, we may be a light client, but these + // are unsupported for following GRANDPA directly. + let genesis_authorities = api.runtime_api() + .grandpa_authorities(&BlockId::number(Zero::zero()))?; + + let authority_set = LightAuthoritySet::genesis(genesis_authorities); + let encoded = authority_set.encode(); + Backend::insert_aux(&**client.backend(), &[(LIGHT_AUTHORITY_SET_KEY, &encoded[..])], &[])?; + + authority_set + }, + Some(raw) => LightAuthoritySet::decode(&mut &raw[..]) + .ok_or_else(|| ::client::error::ErrorKind::Backend( + format!("GRANDPA authority set kept in invalid format") + ))? + .into(), + }; + + let consensus_changes = load_consensus_changes(&*client, LIGHT_CONSENSUS_CHANGES_KEY)?; + + Ok(GrandpaLightBlockImport { + client, + data: Arc::new(RwLock::new(LightImportData { + authority_set, + consensus_changes, + })), + }) +} + +/// A light block-import handler for GRANDPA. +/// +/// It is responsible for +/// - checking GRANDPA justifications; +/// - requiring GRANDPA justifications for blocks that are enacting consensus changes; +pub struct GrandpaLightBlockImport, RA> { + client: Arc>, + data: Arc>>, +} + +impl, RA> JustificationImport + for GrandpaLightBlockImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + RA: Send + Sync, +{ + type Error = ConsensusError; + + fn on_start(&self, link: &::consensus_common::import_queue::Link) { + let chain_info = match self.client.info() { + Ok(info) => info.chain, + _ => return, + }; + + let data = self.data.read(); + for (pending_number, pending_hash) in data.consensus_changes.pending_changes() { + if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number { + link.request_justification(pending_hash, *pending_number); + } + } + } + + fn import_justification( + &self, + hash: Block::Hash, + number: NumberFor, + justification: Justification, + ) -> Result<(), Self::Error> { + self.do_import_justification(hash, number, justification) + } +} + +impl, RA> BlockImport + for GrandpaLightBlockImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + RA: Send + Sync, +{ + type Error = ConsensusError; + + fn import_block(&self, mut block: ImportBlock, new_authorities: Option>) + -> Result + { + let hash = block.post_header().hash(); + let number = block.header.number().clone(); + + // we don't want to finalize on `inner.import_block` + let justification = block.justification.take(); + let enacts_consensus_change = new_authorities.is_some(); + let import_result = self.client.import_block(block, new_authorities); + + let import_result = { + match import_result { + Ok(ImportResult::Queued) => ImportResult::Queued, + Ok(r) => return Ok(r), + Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), + } + }; + + match justification { + Some(justification) => { + self.do_import_justification(hash, number, justification)?; + Ok(import_result) + }, + None if enacts_consensus_change => { + // remember that we need justification for this block + self.data.write().consensus_changes.note_change((number, hash)); + Ok(ImportResult::NeedsJustification) + }, + None => Ok(import_result), + } + } +} + +impl, RA> + GrandpaLightBlockImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + + /// Import a block justification and finalize the block. + fn do_import_justification( + &self, + hash: Block::Hash, + number: NumberFor, + encoded_justification: Justification, + ) -> Result<(), ConsensusError> { + let mut data = self.data.write(); + + // with justification, we have two cases + // + // optimistic: the same GRANDPA authorities set has generated intermediate justification + // => justification is verified using current authorities set + we could proceed further + // + // pessimistic scenario: the GRANDPA authorities set has changed + // => we need to fetch new authorities set from remote node (the set ID increases by one) + + // first, try to behave optimistically + let authority_set_id = data.authority_set.set_id(); + let justification = GrandpaJustification::::decode_and_verify( + &encoded_justification, + authority_set_id, + &data.authority_set.authorities(), + ); + + // BadJustification error means that justification has been successfully decoded, but + // it isn't valid within current authority set + let (justification, new_grandpa_authorities) = match justification { + Err(ClientError(ClientErrorKind::BadJustification(_), _)) => { + let into_err = |e: ClientError| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())); + + // fetch GRANDPA authority set from remote node + let header = self.client.backend().blockchain().expect_header(BlockId::Hash(hash)).map_err(into_err)?; + let parent_block_id = BlockId::Hash(*header.parent_hash()); + let grandpa_authorities = self.client.executor().call(&parent_block_id, "GrandpaApi_grandpa_authorities", &[]) + .map_err(into_err)?; + let grandpa_authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &grandpa_authorities[..]) + .ok_or_else(|| ConsensusErrorKind::ClientImport( + ClientErrorKind::BadJustification("failed to decode GRANDPA authorities set proof".into()).to_string() + ))?; + + // ... and try to reverify justification + let justification = GrandpaJustification::decode_and_verify( + &encoded_justification, + authority_set_id + 1, + &grandpa_authorities.iter().cloned().collect(), + ).map_err(into_err)?; + + (justification, Some(grandpa_authorities)) + }, + Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), + Ok(justification) => (justification, None), + }; + + // finalize the block + self.client.finalize_block(BlockId::Hash(hash), Some(justification.encode()), true).map_err(|e| { + warn!(target: "finality", "Error applying finality to block {:?}: {:?}", (hash, number), e); + ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())) + })?; + + // forget obsoleted consensus changes + let consensus_finalization_res = data.consensus_changes + .finalize((number, hash), |at_height| canonical_at_height(&self.client, (hash, number), at_height)); + match consensus_finalization_res { + Ok((true, _)) => require_insert_aux( + &self.client, + LIGHT_CONSENSUS_CHANGES_KEY, + &data.consensus_changes, + "consensus changes", + )?, + Ok(_) => (), + Err(error) => return on_post_finalization_error(error, "consensus changes"), + } + + // ... and finally update the authority set + if let Some(new_grandpa_authorities) = new_grandpa_authorities { + data.authority_set.update_authorities(new_grandpa_authorities); + + require_insert_aux( + &self.client, + LIGHT_AUTHORITY_SET_KEY, + &data.authority_set, + "authority set", + )?; + } + + Ok(()) + } +} + +/// Mutable data of light block importer. +struct LightImportData> { + authority_set: LightAuthoritySet, + consensus_changes: ConsensusChanges>, +} + +/// Latest authority set tracker. +#[derive(Debug, Encode, Decode)] +struct LightAuthoritySet { + set_id: u64, + authorities: Vec<(Ed25519AuthorityId, u64)>, +} + +impl LightAuthoritySet { + /// Get a genesis set with given authorities. + pub fn genesis(initial: Vec<(Ed25519AuthorityId, u64)>) -> Self { + LightAuthoritySet { + set_id: 0, + authorities: initial, + } + } + + /// Get latest set id. + pub fn set_id(&self) -> u64 { + self.set_id + } + + /// Get latest authorities set. + pub fn authorities(&self) -> HashMap { + self.authorities.iter().cloned().collect() + } + + /// Apply new authorities set. + pub fn update_authorities(&mut self, authorities: Vec<(Ed25519AuthorityId, u64)>) { + self.set_id += 1; + std::mem::replace(&mut self.authorities, authorities); + } +} + +fn require_insert_aux, RA>( + client: &Client, + key: &[u8], + value: &T, + value_type: &str, +) -> Result<(), ConsensusError> + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, +{ + let backend = &**client.backend(); + let encoded = value.encode(); + let update_res = Backend::insert_aux(backend, &[(key, &encoded[..])], &[]); + if let Err(error) = update_res { + return on_post_finalization_error(error, value_type); + } + + Ok(()) +} + +fn on_post_finalization_error(error: ClientError, value_type: &str) -> Result<(), ConsensusError> { + warn!(target: "finality", "Failed to write updated {} to disk. Bailing.", value_type); + warn!(target: "finality", "Node is in a potentially inconsistent state."); + Err(ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string()))) +} diff --git a/core/finality-grandpa/src/service_integration.rs b/core/finality-grandpa/src/service_integration.rs index c4f5398312663..ff3f4a51bccf7 100644 --- a/core/finality-grandpa/src/service_integration.rs +++ b/core/finality-grandpa/src/service_integration.rs @@ -17,7 +17,7 @@ /// Integrate grandpa finality with substrate service use client; -use service::{FullBackend, FullExecutor, ServiceFactory}; +use service::{FullBackend, FullExecutor, LightBackend, LightExecutor, ServiceFactory}; pub type BlockImportForService = ::GrandpaBlockImport< FullBackend, @@ -25,11 +25,11 @@ pub type BlockImportForService = ::GrandpaBlockImport< ::Block, ::RuntimeApi, client::Client< - FullBackend, - FullExecutor, - ::Block, - ::RuntimeApi - >, + FullBackend, + FullExecutor, + ::Block, + ::RuntimeApi + >, >; pub type LinkHalfForService = ::LinkHalf< @@ -37,4 +37,11 @@ pub type LinkHalfForService = ::LinkHalf< FullExecutor, ::Block, ::RuntimeApi ->; \ No newline at end of file +>; + +pub type BlockImportForLightService = ::light_import::GrandpaLightBlockImport< + LightBackend, + LightExecutor, + ::Block, + ::RuntimeApi, +>; diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index eb0e5a4f80a6e..4fe91e82fef5c 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -149,16 +149,19 @@ construct_service_factory! { NothingExtra, > { |config: &FactoryFullConfiguration, client: Arc>| { - import_queue( - SlotDuration::get_or_compute(&*client)?, - client.clone(), - None, - client, - NothingExtra, - config.custom.inherent_data_providers.clone(), - ).map_err(Into::into) - } - }, + let block_import = Arc::new(grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>( + client.clone(), client.clone() + )?); + + import_queue( + SlotDuration::get_or_compute(&*client)?, + block_import.clone(), + Some(block_import), + client, + NothingExtra, + config.custom.inherent_data_providers.clone(), + ).map_err(Into::into) + }}, } } From 0b3443b17260bbb113294bf40895d385ad8976d7 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 28 Jan 2019 14:58:18 +0300 Subject: [PATCH 02/42] extract authorities in AuraVerifier --- core/consensus/aura/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index 25bf983de67c3..8a43e4ad42e58 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -48,7 +48,7 @@ use inherents::{InherentDataProviders, InherentData, RuntimeString}; use futures::{Stream, Future, IntoFuture, future::{self, Either}}; use tokio::timer::Timeout; use slots::Slots; -use ::log::{warn, debug, log, info, trace}; +use ::log::{warn, debug, info, trace}; use srml_aura::{ InherentType as AuraInherent, AuraInherentData, @@ -566,6 +566,10 @@ impl Verifier for AuraVerifier where extra_verification.into_future().wait()?; + let new_authorities = pre_header.digest() + .log(DigestItem::as_authorities_change) + .map(|digest| digest.to_vec()); + let import_block = ImportBlock { origin, header: pre_header, @@ -577,8 +581,7 @@ impl Verifier for AuraVerifier where fork_choice: ForkChoiceStrategy::LongestChain, }; - // FIXME: extract authorities - https://github.com/paritytech/substrate/issues/1019 - Ok((import_block, None)) + Ok((import_block, new_authorities)) } CheckedHeader::Deferred(a, b) => { debug!(target: "aura", "Checking {:?} failed; {:?}, {:?}.", hash, a, b); @@ -797,7 +800,7 @@ mod tests { let mut runtime = current_thread::Runtime::new().unwrap(); for (peer_id, key) in peers { - let mut client = net.lock().peer(*peer_id).client().clone(); + let client = net.lock().peer(*peer_id).client().clone(); let environ = Arc::new(DummyFactory(client.clone())); import_notifications.push( client.import_notification_stream() From da6c11e7fb54aaccd152712d6298825dac2b9c64 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 29 Jan 2019 12:33:26 +0300 Subject: [PATCH 03/42] post-merge fix --- core/finality-grandpa/src/light_import.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 9cf079f70999c..8d9516f43decc 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::Arc; use parking_lot::RwLock; @@ -9,6 +8,7 @@ use client::{ use client::blockchain::HeaderBackend; use codec::{Encode, Decode}; use consensus_common::{BlockImport, JustificationImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; +use grandpa::VoterSet; use runtime_primitives::Justification; use runtime_primitives::traits::{ NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, @@ -46,8 +46,7 @@ pub fn light_block_import, RA, PRA>( // no authority set on disk: fetch authorities from genesis state. // if genesis state is not available, we may be a light client, but these // are unsupported for following GRANDPA directly. - let genesis_authorities = api.runtime_api() - .grandpa_authorities(&BlockId::number(Zero::zero()))?; + let genesis_authorities = api.runtime_api().grandpa_authorities(&BlockId::number(Zero::zero()))?; let authority_set = LightAuthoritySet::genesis(genesis_authorities); let encoded = authority_set.encode(); @@ -289,7 +288,7 @@ impl LightAuthoritySet { } /// Get latest authorities set. - pub fn authorities(&self) -> HashMap { + pub fn authorities(&self) -> VoterSet { self.authorities.iter().cloned().collect() } From d42315b64a1150c614fd66c7ebadb131625fc24b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 1 Feb 2019 15:13:16 +0300 Subject: [PATCH 04/42] restore authorities cache --- core/client/db/src/cache/mod.rs | 12 ++++++++++-- core/client/db/src/light.rs | 6 +++--- core/client/src/client.rs | 18 +++++++++++++++++- core/client/src/light/blockchain.rs | 2 +- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/core/client/db/src/cache/mod.rs b/core/client/db/src/cache/mod.rs index 8df8e42518347..e09590a87022f 100644 --- a/core/client/db/src/cache/mod.rs +++ b/core/client/db/src/cache/mod.rs @@ -135,11 +135,19 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { mut self, parent: ComplexBlockId, block: ComplexBlockId, - authorities_at: Option>>, + new_authorities: Option>>, is_final: bool, ) -> ClientResult { assert!(self.authorities_at_op.is_none()); + // new_authorities are only passed when they're changed + // but the cache is currenty tracks value changes itself && we should call it with + // value for parent block + let authorities = match new_authorities { + Some(new_authorities) => Some(new_authorities), + None => self.cache.authorities_at.value_at_block(&parent)?, + }; + self.authorities_at_op = self.cache.authorities_at.on_block_insert( &mut self::list_storage::DbStorageTransaction::new( self.cache.authorities_at.storage(), @@ -147,7 +155,7 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { ), parent, block, - authorities_at, + authorities, is_final, )?; diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 4221edfa91fcf..64085975edcdf 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -307,7 +307,7 @@ impl LightBlockchainStorage for LightStorage fn import_header( &self, header: Block::Header, - authorities: Option>>, + new_authorities: Option>>, leaf_state: NewBlockState, aux_ops: Vec<(Vec, Option>)>, ) -> ClientResult<()> { @@ -403,7 +403,7 @@ impl LightBlockchainStorage for LightStorage .on_block_insert( ComplexBlockId::new(*header.parent_hash(), if number.is_zero() { Zero::zero() } else { number - One::one() }), ComplexBlockId::new(hash, number), - authorities, + new_authorities, finalized, )? .into_ops(); @@ -465,7 +465,7 @@ impl LightBlockchainStorage for LightStorage } fn cache(&self) -> Option<&BlockchainCache> { - None + Some(&self.cache) } } diff --git a/core/client/src/client.rs b/core/client/src/client.rs index e8c46156f701a..16253441c5f03 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -38,7 +38,7 @@ use crate::runtime_api::{CallRuntimeAt, ConstructRuntimeApi}; use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash, NeverNativeValue}; use primitives::storage::{StorageKey, StorageData}; use primitives::storage::well_known_keys; -use codec::{Encode, Decode}; +use codec::{Encode, Decode, KeyedVec}; use state_machine::{ DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId, ExecutionStrategy, ExecutionManager, prove_read, @@ -255,10 +255,23 @@ impl Client where ) -> error::Result { if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() { let (genesis_storage, children_genesis_storage) = build_genesis_storage.build_storage()?; + let genesis_authorities_len: Option = genesis_storage + .get(well_known_keys::AUTHORITY_COUNT) + .and_then(|v| Decode::decode(&mut &v[..])); + let genesis_authorities = genesis_authorities_len.map(|len| { + (0..len).filter_map(|i| { + let auth_key = i.to_keyed_vec(well_known_keys::AUTHORITY_PREFIX); + let auth: Option> = genesis_storage.get(&auth_key) + .and_then(|v| Decode::decode(&mut &v[..])); + auth + }).collect() + }); + let mut op = backend.begin_operation()?; backend.begin_state_operation(&mut op, BlockId::Hash(Default::default()))?; let state_root = op.reset_storage(genesis_storage, children_genesis_storage)?; let genesis_block = genesis::construct_genesis_block::(state_root.into()); + info!("Initialising Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), genesis_block.header().hash()); op.set_block_data( genesis_block.deconstruct().0, @@ -266,6 +279,9 @@ impl Client where None, crate::backend::NewBlockState::Final )?; + if let Some(genesis_authorities) = genesis_authorities{ + op.update_authorities(genesis_authorities); + } backend.commit_operation(op)?; } diff --git a/core/client/src/light/blockchain.rs b/core/client/src/light/blockchain.rs index 89fe5f8a3a4d7..7f53997a10193 100644 --- a/core/client/src/light/blockchain.rs +++ b/core/client/src/light/blockchain.rs @@ -40,7 +40,7 @@ pub trait Storage: AuxStore + BlockchainHeaderBackend { fn import_header( &self, header: Block::Header, - authorities: Option>>, + new_authorities: Option>>, state: NewBlockState, aux_ops: Vec<(Vec, Option>)>, ) -> ClientResult<()>; From 4cbefb669085b040336443f59a00e1a2aa5df7b4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 4 Feb 2019 11:38:22 +0300 Subject: [PATCH 05/42] license --- core/finality-grandpa/src/light_import.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 8d9516f43decc..177a03503eaa3 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -1,3 +1,19 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + use std::sync::Arc; use parking_lot::RwLock; From 5344c615bd2575eb90dc3da7c2e746ea01a8df22 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 8 Feb 2019 12:14:27 +0300 Subject: [PATCH 06/42] new finality proof draft --- core/finality-grandpa/src/finality_proof.rs | 378 +++++++++++++------- core/finality-grandpa/src/lib.rs | 1 - core/finality-grandpa/src/light_import.rs | 48 +++ 3 files changed, 302 insertions(+), 125 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 3ba9973c0a8b7..51fc445969a48 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -33,8 +33,7 @@ use grandpa::VoterSet; use client::{ blockchain::Backend as BlockchainBackend, - error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}, - light::fetcher::RemoteCallRequest, + error::{ErrorKind as ClientErrorKind, Result as ClientResult}, }; use codec::{Encode, Decode}; use grandpa::BlockNumberOps; @@ -46,174 +45,304 @@ use substrate_primitives::{Ed25519AuthorityId, H256}; use GrandpaJustification; -/// Prepare proof-of-finality for the given block. +/// The effects of block finality. +pub struct FinalityEffects { + /// The (ordered) set of headers that could be imported. + pub headers_to_import: Vec
, + /// The hash of the block that could be finalized. + pub block: Header::Hash, + /// The justification for the block. + pub justification: J, + /// New authorities set id that should be applied starting from block. + pub new_set_id: u64, + /// New authorities set that should be applied starting from block. + pub new_authorities: VoterSet, +} + +/// Single fragment of proof-of-finality. +/// +/// Finality for block B is proved by providing: +/// 1) the justification for the descendant block F; +/// 2) headers sub-chain (U; F], where U is the last block known to the caller; +/// 3) proof of GRANDPA::authorities() if the set changes at block F. +#[derive(Debug, PartialEq, Encode, Decode)] +struct FinalityProofFragment { + /// The hash of block F for which justification is provided. + pub block: Header::Hash, + /// Justification of the block F. + pub justification: Justification, + /// The set of headers in the range (U; F] that we believe are unknown to the caller. Ordered. + pub unknown_headers: Vec
, + /// Optional proof of execution of GRANDPA::authorities(). + pub authorities_proof: Option>>, +} + +/// Proof of finality is the ordered set of finality fragments, where: +/// - last fragment provides justification for the best possible block from the requested range; +/// - all other fragments provide justifications for GRANDPA authorities set changes within requested range. +type FinalityProof = Vec>; + +/// Prepare proof-of-finality for the best possible block in the range: (begin; end]. +/// +/// It is assumed that the caller already have a proof-of-finality for the block 'begin'. +/// It is assumed that the caller already knows all blocks in the range (begin; end]. /// -/// The proof is the serialized `FinalityProof` constructed using earliest known -/// justification of the block. None is returned if there's no known justification atm. -pub fn prove_finality( +/// Returns None if there are no finalized blocks unknown to the caller. +pub fn prove_finality, B, GetAuthorities, ProveAuthorities>( blockchain: &B, - generate_execution_proof: G, - block: Block::Hash, + get_authorities: GetAuthorities, + prove_authorities: ProveAuthorities, + begin: Block::Hash, + end: Block::Hash, ) -> ::client::error::Result>> where B: BlockchainBackend, - G: Fn(&BlockId, &str, &[u8]) -> ClientResult>>, + GetAuthorities: Fn(&BlockId) -> ClientResult>, + ProveAuthorities: Fn(&BlockId) -> ClientResult>>, { - let block_id = BlockId::Hash(block); - let mut block_number = blockchain.expect_block_number_from_id(&block_id)?; + let begin_id = BlockId::Hash(begin); + let begin_number = blockchain.expect_block_number_from_id(&begin_id)?; - // early-return if we sure that the block isn't finalized yet + // early-return if we sure that there are no blocks finalized AFTER begin block let info = blockchain.info()?; - if info.finalized_number < block_number { + if info.finalized_number <= begin_number { return Ok(None); } + // check if blocks range is valid. It is the caller responsibility to ensure + // that it only asks peers that know about whole blocks range + let end_number = blockchain.expect_block_number_from_id(&BlockId::Hash(end))?; + if begin_number + One::one() > end_number { + return Err(ClientErrorKind::Backend( + format!("Cannot generate finality proof for invalid range: {}..{}", begin_number, end_number), + ).into()); + } + // early-return if we sure that the block is NOT a part of canonical chain - let canonical_block = blockchain.expect_block_hash_from_id(&BlockId::Number(block_number))?; - if block != canonical_block { + let canonical_begin = blockchain.expect_block_hash_from_id(&BlockId::Number(begin_number))?; + if begin != canonical_begin { return Err(ClientErrorKind::Backend( - "Cannot generate finality proof for non-canonical block".into() + format!("Cannot generate finality proof for non-canonical block: {}", begin), ).into()); } - // now that we know that the block is finalized, we can generate finalization proof - - // we need to prove grandpa authorities set that has generated justification - // BUT since `GrandpaApi::grandpa_authorities` call returns the set that becames actual - // at the next block, the proof-of execution is generated using parent block' state - // (this will fail if we're trying to prove genesis finality, but such the call itself is redundant) - let mut current_header = blockchain.expect_header(BlockId::Hash(block))?; - let parent_block_id = BlockId::Hash(*current_header.parent_hash()); - let authorities_proof = generate_execution_proof( - &parent_block_id, - "GrandpaApi_grandpa_authorities", - &[], - )?; - - // search for earliest post-block (inclusive) justification - let mut finalization_path = Vec::new(); + // iterate justifications && try to prove finality + let mut current_authorities = get_authorities(&begin_id)?; + let mut current_number = begin_number + One::one(); + let mut finality_proof = Vec::new(); + let mut unknown_headers = Vec::new(); + let mut latest_proof_fragment = None; loop { - finalization_path.push(current_header); + let current_id = BlockId::Number(current_number); + + // check if header is unknown to the caller + if current_number > end_number { + let unknown_header = blockchain.expect_header(current_id)?; + unknown_headers.push(unknown_header); + } - match blockchain.justification(BlockId::Number(block_number))? { - Some(justification) => return Ok(Some(FinalityProof { - finalization_path, + if let Some(justification) = blockchain.justification(current_id)? { + // check if the current block enacts new GRANDPA authorities set + let parent_id = BlockId::Number(current_number - One::one()); + let new_authorities = get_authorities(&parent_id)?; + let new_authorities_proof = if current_authorities != new_authorities { + current_authorities = new_authorities; + Some(prove_authorities(&parent_id)?) + } else { + None + }; + + // prepare finality proof for the current block + let current = blockchain.expect_block_hash_from_id(&BlockId::Number(current_number))?; + let proof_fragment = FinalityProofFragment { + block: current, justification, - authorities_proof, - }.encode())), - None if block_number == info.finalized_number => break, - None => { - block_number = block_number + One::one(); - current_header = blockchain.expect_header(BlockId::Number(block_number))?; - }, + unknown_headers: ::std::mem::replace(&mut unknown_headers, Vec::new()), + authorities_proof: new_authorities_proof, + }; + + // append justification to finality proof if required + let justifies_end_block = current_number >= end_number; + let justifies_authority_set_change = proof_fragment.authorities_proof.is_some(); + if justifies_end_block || justifies_authority_set_change { + finality_proof.push(proof_fragment); + latest_proof_fragment = None; + } else { + latest_proof_fragment = Some(proof_fragment); + } + + // we don't need to provide more justifications + if justifies_end_block { + break; + } + } + + // we can't provide more justifications + if current_number == info.finalized_number { + // append last justification - even if we can't generate finality proof for + // the end block, we try to generate it for the latest possible block + if let Some(latest_proof_fragment) = latest_proof_fragment.take() { + finality_proof.push(latest_proof_fragment); + } + break; } + + // else search for the next justification + current_number = current_number + One::one(); } - Err(ClientErrorKind::Backend( - "cannot find justification for finalized block".into() - ).into()) + if finality_proof.is_empty() { + Ok(None) + } else { + Ok(Some(finality_proof.encode())) + } } /// Check proof-of-finality for the given block. /// -/// Returns the vector of headers (including `block` header, ordered by ASC block number) that MUST be -/// validated + imported at once (i.e. within single db transaction). If at least one of those headers +/// Returns the vector of headers that MUST be validated + imported +/// AND. If at least one of those headers /// is invalid, all other MUST be considered invalid. -pub fn check_finality_proof, C>( - check_execution_proof: C, - parent_header: Block::Header, - block: (NumberFor, Block::Hash), - set_id: u64, +pub(crate) fn check_finality_proof, B, CheckAuthoritiesProof>( + blockchain: &B, + current_set_id: u64, + current_authorities: VoterSet, + check_authorities_proof: CheckAuthoritiesProof, remote_proof: Vec, -) -> ClientResult> +) -> ClientResult>> where - NumberFor: grandpa::BlockNumberOps, - C: Fn(&RemoteCallRequest) -> ClientResult>, + NumberFor: BlockNumberOps, + B: BlockchainBackend, + CheckAuthoritiesProof: Fn( + Block::Hash, + Block::Header, + Vec>, + ) -> ClientResult>, { - do_check_finality_proof::>( - check_execution_proof, - parent_header, - block, - set_id, - remote_proof, - ) + do_check_finality_proof( + blockchain, + current_set_id, + current_authorities, + check_authorities_proof, + remote_proof) } -/// Check proof-of-finality using given justification type. -fn do_check_finality_proof, C, J>( - check_execution_proof: C, - parent_header: Block::Header, - block: (NumberFor, Block::Hash), - set_id: u64, +/// Check proof-of-finality for the given block. +/// +/// Returns the vector of headers that MUST be validated + imported +/// AND. If at least one of those headers +/// is invalid, all other MUST be considered invalid. +fn do_check_finality_proof, B, J, CheckAuthoritiesProof>( + blockchain: &B, + current_set_id: u64, + current_authorities: VoterSet, + check_authorities_proof: CheckAuthoritiesProof, remote_proof: Vec, -) -> ClientResult> +) -> ClientResult> where - NumberFor: grandpa::BlockNumberOps, - C: Fn(&RemoteCallRequest) -> ClientResult>, + NumberFor: BlockNumberOps, + B: BlockchainBackend, J: ProvableJustification, + CheckAuthoritiesProof: Fn( + Block::Hash, + Block::Header, + Vec>, + ) -> ClientResult>, { // decode finality proof let proof = FinalityProof::::decode(&mut &remote_proof[..]) .ok_or_else(|| ClientErrorKind::BadJustification("failed to decode finality proof".into()))?; - // check that the first header in finalization path is the block itself - { - let finalized_header = proof.finalization_path.first() - .ok_or_else(|| ClientError::from(ClientErrorKind::BadJustification( - "finality proof: finalized path is empty".into() - )))?; - if *finalized_header.number() != block.0 || finalized_header.hash() != block.1 { - return Err(ClientErrorKind::BadJustification( - "finality proof: block is not a part of finalized path".into() - ).into()); - } + // empty proof can't prove anything + if proof.is_empty() { + return Err(ClientErrorKind::BadJustification("empty proof of finality".into()).into()); } - // check that the last header in finalization path is the jsutification target block - let just_block = proof.justification.target_block(); - { - let finalized_header = proof.finalization_path.last() - .expect("checked above that proof.finalization_path is not empty; qed"); - if *finalized_header.number() != just_block.0 || finalized_header.hash() != just_block.1 { - return Err(ClientErrorKind::BadJustification( - "finality proof: target jsutification block is not a part of finalized path".into() - ).into()); + // iterate and verify proof fragments + let last_fragment_index = proof.len() - 1; + let mut authorities = AuthoritiesOrEffects::Authorities(current_set_id, current_authorities); + for (proof_fragment_index, proof_fragment) in proof.into_iter().enumerate() { + // check that proof is non-redundant. The proof still can be valid, but + // we do not want peer to spam us with redundant data + if proof_fragment_index == last_fragment_index { + let has_unknown_headers = proof_fragment.unknown_headers.is_empty(); + let has_new_authorities = proof_fragment.authorities_proof.is_some(); + if has_unknown_headers || !has_new_authorities { + return Err(ClientErrorKind::BadJustification("redundant proof of finality".into()).into()); + } } + + authorities = check_finality_proof_fragment( + blockchain, + authorities, + &check_authorities_proof, + proof_fragment)?; } - // check authorities set proof && get grandpa authorities that should have signed justification - let grandpa_authorities = check_execution_proof(&RemoteCallRequest { - block: just_block.1, - header: parent_header, - method: "GrandpaApi_grandpa_authorities".into(), - call_data: vec![], - retry_count: None, - })?; - let grandpa_authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &grandpa_authorities[..]) - .ok_or_else(|| ClientErrorKind::BadJustification("failed to decode GRANDPA authorities set proof".into()))?; - - // and now check justification - proof.justification.verify(set_id, &grandpa_authorities.into_iter().collect())?; - - Ok(proof.finalization_path) + Ok(authorities.extract_effects().expect("at least one loop iteration is guaranteed because proof is not empty;\ + check_finality_proof_fragment is called on every iteration;\ + check_finality_proof_fragment always returns FinalityEffects;\ + qed")) } -/// Proof of finality. -/// -/// Finality of block B is proved by providing: -/// 1) valid headers sub-chain from the block B to the block F; -/// 2) proof of `GrandpaApi::grandpa_authorities()` call at the block F; -/// 3) valid (with respect to proved authorities) GRANDPA justification of the block F. -#[derive(Debug, PartialEq, Encode, Decode)] -struct FinalityProof { - /// Headers-path (ordered by block number, ascending) from the block we're gathering proof for - /// (inclusive) to the target block of the justification (inclusive). - pub finalization_path: Vec
, - /// Justification (finalization) of the last block from the `finalization_path`. - pub justification: Justification, - /// Proof of `GrandpaApi::grandpa_authorities` call execution at the - /// justification' target block. - pub authorities_proof: Vec>, +/// Check finality proof for the single block. +fn check_finality_proof_fragment, B, J, CheckAuthoritiesProof>( + blockchain: &B, + authority_set: AuthoritiesOrEffects, + check_authorities_proof: &CheckAuthoritiesProof, + proof_fragment: FinalityProofFragment, +) -> ClientResult> + where + NumberFor: BlockNumberOps, + B: BlockchainBackend, + J: ProvableJustification, + CheckAuthoritiesProof: Fn( + Block::Hash, + Block::Header, + Vec>, + ) -> ClientResult>, +{ + // verify justification using previous authorities set + let (mut current_set_id, mut current_authorities) = authority_set.extract_authorities(); + proof_fragment.justification.verify(current_set_id, ¤t_authorities)?; + + // and now verify new authorities proof (if provided) + if let Some(new_authorities_proof) = proof_fragment.authorities_proof { + // it is safe to query header here, because its non-finality proves that it can't be pruned + let header = blockchain.expect_header(BlockId::Hash(proof_fragment.block))?; + current_authorities = check_authorities_proof(proof_fragment.block, header, new_authorities_proof)?; + current_set_id = current_set_id + 1; + } + + Ok(AuthoritiesOrEffects::Effects(FinalityEffects { + headers_to_import: proof_fragment.unknown_headers, + block: proof_fragment.block, + justification: proof_fragment.justification, + new_set_id: current_set_id, + new_authorities: current_authorities, + })) +} + +/// Authorities set from initial authorities set or finality effects. +enum AuthoritiesOrEffects { + Authorities(u64, VoterSet), + Effects(FinalityEffects), +} + +impl AuthoritiesOrEffects { + pub fn extract_authorities(self) -> (u64, VoterSet) { + match self { + AuthoritiesOrEffects::Authorities(set_id, authorities) => (set_id, authorities), + AuthoritiesOrEffects::Effects(effects) => (effects.new_set_id, effects.new_authorities), + } + } + + pub fn extract_effects(self) -> Option> { + match self { + AuthoritiesOrEffects::Authorities(_, _) => None, + AuthoritiesOrEffects::Effects(effects) => Some(effects), + } + } } /// Justification used to prove block finality. @@ -238,7 +367,7 @@ impl> ProvableJustification for GrandpaJ } } -#[cfg(test)] +/*#[cfg(test)] mod tests { use test_client::runtime::{Block, Header}; use test_client::client::backend::NewBlockState; @@ -426,3 +555,4 @@ mod tests { ).unwrap(), vec![header(2), header(3)]); } } +*/ \ No newline at end of file diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 1a8934f18c4fb..41220ab132a4a 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -128,7 +128,6 @@ mod service_integration; #[cfg(feature="service-integration")] pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService}; -pub use finality_proof::{prove_finality, check_finality_proof}; pub use light_import::light_block_import; #[cfg(test)] diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 177a03503eaa3..dc7d2ced8e4a7 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -20,6 +20,7 @@ use parking_lot::RwLock; use client::{ CallExecutor, Client, backend::Backend, error::Error as ClientError, error::ErrorKind as ClientErrorKind, + light::fetcher::{FetchChecker, RemoteCallRequest}, }; use client::blockchain::HeaderBackend; use codec::{Encode, Decode}; @@ -44,6 +45,7 @@ const LIGHT_CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes"; /// Create light block importer. pub fn light_block_import, RA, PRA>( client: Arc>, + fetch_checker: Arc>, api: Arc, ) -> Result, ClientError> where @@ -81,6 +83,7 @@ pub fn light_block_import, RA, PRA>( Ok(GrandpaLightBlockImport { client, + fetch_checker, data: Arc::new(RwLock::new(LightImportData { authority_set, consensus_changes, @@ -95,6 +98,7 @@ pub fn light_block_import, RA, PRA>( /// - requiring GRANDPA justifications for blocks that are enacting consensus changes; pub struct GrandpaLightBlockImport, RA> { client: Arc>, + fetch_checker: Arc>, data: Arc>>, } @@ -185,6 +189,50 @@ impl, RA> E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, { + fn import_finality_proof( + &self, + _hash: Block::Hash, + _number: NumberFor, + finality_proof: Vec, + ) -> Result<(), ConsensusError> { + // TODO: ensure that the proof is for non-finalzie block + let data = self.data.write(); + + let authority_set_id = data.authority_set.set_id(); + let authorities = data.authority_set.authorities(); + let finality_effects = ::finality_proof::check_finality_proof( + &*self.client.backend().blockchain(), + authority_set_id, + authorities, + |hash, header, authorities_proof| { + let request = RemoteCallRequest { + block: hash, + header, + method: "GrandpaApi_grandpa_authorities".into(), + call_data: vec![], + retry_count: None, + }; + + self.fetch_checker.check_execution_proof(&request, authorities_proof) + .and_then(|authorities| { + let authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &authorities[..]) + .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( + "failed to decode GRANDPA authorities set proof".into(), + )))?; + Ok(authorities.into_iter().collect()) + }) + }, + finality_proof, + ).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; + + for header_to_import in finality_effects.headers_to_import { + // import block + } + + // import justification + + // apply new authorities set + } /// Import a block justification and finalize the block. fn do_import_justification( From c3a812545f2fbf1e1d1e50bde5c98b21bb1a2792 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 8 Feb 2019 16:12:40 +0300 Subject: [PATCH 07/42] generalized PendingJustifications --- core/consensus/common/src/import_queue.rs | 2 + core/network/src/extra_requests.rs | 314 ++++++++++++++++++++++ core/network/src/lib.rs | 1 + core/network/src/service.rs | 4 + core/network/src/sync.rs | 216 ++------------- 5 files changed, 346 insertions(+), 191 deletions(-) create mode 100644 core/network/src/extra_requests.rs diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 67dbf6318654b..6227326a6c436 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -299,6 +299,8 @@ pub trait Link: Send { fn block_imported(&self, _hash: &B::Hash, _number: NumberFor) { } /// Request a justification for the given block. fn request_justification(&self, _hash: &B::Hash, _number: NumberFor) { } + /// Request a finality proof for the given block. + fn request_finality_proof(&self, _hash: &B::Hash, _number: NumberFor) { } /// Maintain sync. fn maintain_sync(&self) { } /// Disconnect from peer. diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs new file mode 100644 index 0000000000000..f68fb0550cf1e --- /dev/null +++ b/core/network/src/extra_requests.rs @@ -0,0 +1,314 @@ +// Copyright 2017-2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use std::collections::{HashMap, HashSet, VecDeque}; +use std::time::{Duration, Instant}; +use protocol::Context; +use network_libp2p::{Severity, NodeIndex}; +use consensus::import_queue::ImportQueue; +use runtime_primitives::Justification; +use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use message::{self, Message, generic::Message as GenericMessage}; +use sync::{PeerSync, PeerSyncState}; + +// Time to wait before trying to get the same extra data from the same peer. +const EXTRA_RETRY_WAIT: Duration = Duration::from_secs(10); + +/// Pending extra data request for the given block (hash and number). +type ExtraRequest = (::Hash, NumberFor); + +/// Extra requests processor. +trait ExtraRequestsEssence { + type Response; + + /// Prepare network message corresponding to the request. + fn into_network_request(&self, request: ExtraRequest) -> Message; + /// Accept response. + fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Self::Response) -> ExtraResponseKind; + /// + fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState; +} + +/// Manages all extra data requests required for sync. +pub(crate) struct ExtraRequestsAggregator { + /// Manages justifications requests. + justifications: ExtraRequests, + /// Manages finality proof requests. + finality_proofs: ExtraRequests, +} + +impl ExtraRequestsAggregator { + pub fn new() -> Self { + ExtraRequestsAggregator { + justifications: ExtraRequests::new(JustificationsRequestsEssence), + finality_proofs: ExtraRequests::new(FinalityProofRequestsEssence), + } + } + + pub fn request_justification(&mut self, request: &ExtraRequest, peers: &mut HashMap>, protocol: &mut Context) { + self.justifications.queue_request(request); + self.justifications.dispatch(peers, protocol); + } + + pub fn request_finality_proof(&mut self, request: &ExtraRequest, peers: &mut HashMap>, protocol: &mut Context) { + self.finality_proofs.queue_request(request); + self.finality_proofs.dispatch(peers, protocol); + } + + pub fn on_justification(&mut self, who: NodeIndex, justification: Option, protocol: &mut Context, import_queue: &ImportQueue) { + self.justifications.on_response(who, justification, protocol, import_queue); + } + + pub fn dispatch(&mut self, peers: &mut HashMap>, protocol: &mut Context) { + self.justifications.dispatch(peers, protocol); + self.finality_proofs.dispatch(peers, protocol); + } + + pub fn collect_garbage(&mut self, best_finalized: NumberFor) { + self.justifications.collect_garbage(best_finalized); + self.finality_proofs.collect_garbage(best_finalized); + } + + pub fn peer_disconnected(&mut self, who: NodeIndex) { + self.justifications.peer_disconnected(who); + self.finality_proofs.peer_disconnected(who); + } +} + +/// Manages pending extra data requests of single type. +struct ExtraRequests { + requests: HashSet>, + pending_requests: VecDeque>, + peer_requests: HashMap>, + previous_requests: HashMap, Vec<(NodeIndex, Instant)>>, + essence: Essence, +} + +impl> ExtraRequests { + fn new(essence: Essence) -> Self { + ExtraRequests { + requests: HashSet::new(), + pending_requests: VecDeque::new(), + peer_requests: HashMap::new(), + previous_requests: HashMap::new(), + essence, + } + } + + /// Dispatches all possible pending requests to the given peers. Peers are + /// filtered according to the current known best block (i.e. we won't send a + /// extra request for block #10 to a peer at block #2), and we also + /// throttle requests to the same peer if a previous extra request + /// yielded no results. + fn dispatch(&mut self, peers: &mut HashMap>, protocol: &mut Context) { + if self.pending_requests.is_empty() { + return; + } + + // clean up previous failed requests so we can retry again + for (_, requests) in self.previous_requests.iter_mut() { + requests.retain(|(_, instant)| instant.elapsed() < EXTRA_RETRY_WAIT); + } + + let mut available_peers = peers.iter().filter_map(|(peer, sync)| { + // don't request to any peers that already have pending requests or are unavailable + if sync.state != PeerSyncState::Available || self.peer_requests.contains_key(&peer) { + None + } else { + Some((*peer, sync.best_number)) + } + }).collect::>(); + + let mut last_peer = available_peers.back().map(|p| p.0); + let mut unhandled_requests = VecDeque::new(); + + loop { + let (peer, peer_best_number) = match available_peers.pop_front() { + Some(p) => p, + _ => break, + }; + + // only ask peers that have synced past the block number that we're + // asking the justification for and to whom we haven't already made + // the same request recently + let peer_eligible = { + let request = match self.pending_requests.front() { + Some(r) => r.clone(), + _ => break, + }; + + peer_best_number >= request.1 && + !self.previous_requests + .get(&request) + .map(|requests| requests.iter().any(|i| i.0 == peer)) + .unwrap_or(false) + }; + + if !peer_eligible { + available_peers.push_back((peer, peer_best_number)); + + // we tried all peers and none can answer this request + if Some(peer) == last_peer { + last_peer = available_peers.back().map(|p| p.0); + + let request = self.pending_requests.pop_front() + .expect("verified to be Some in the beginning of the loop; qed"); + + unhandled_requests.push_back(request); + } + + continue; + } + + last_peer = available_peers.back().map(|p| p.0); + + let request = self.pending_requests.pop_front() + .expect("verified to be Some in the beginning of the loop; qed"); + + self.peer_requests.insert(peer, request); + + peers.get_mut(&peer) + .expect("peer was is taken from available_peers; available_peers is a subset of peers; qed") + .state = self.essence.peer_downloading_state(request.0); + + trace!(target: "sync", "Requesting extra for block #{} from {}", request.0, peer); + let request = self.essence.into_network_request(request); + + protocol.send_message(peer, request); + } + + self.pending_requests.append(&mut unhandled_requests); + } + + /// Queue a justification request (without dispatching it). + fn queue_request(&mut self, request: &ExtraRequest) { + if !self.requests.insert(*request) { + return; + } + self.pending_requests.push_back(*request); + } + + /// Retry any pending request if a peer disconnected. + fn peer_disconnected(&mut self, who: NodeIndex) { + if let Some(request) = self.peer_requests.remove(&who) { + self.pending_requests.push_front(request); + } + } + + /// Processes the response for the request previously sent to the given + /// peer. Queues a retry in case the import fails or the given justification + /// was `None`. + fn on_response( + &mut self, + who: NodeIndex, + response: Essence::Response, + protocol: &mut Context, + import_queue: &ImportQueue, + ) { + // we assume that the request maps to the given response, this is + // currently enforced by the outer network protocol before passing on + // messages to chain sync. + if let Some(request) = self.peer_requests.remove(&who) { + match self.essence.accept_response(request, import_queue, response) { + ExtraResponseKind::Accepted => { + self.requests.remove(&request); + self.previous_requests.remove(&request); + return; + }, + ExtraResponseKind::Invalid => { + protocol.report_peer( + who, + Severity::Bad(&format!("Invalid extra data provided for #{}", request.0)), + ); + }, + ExtraResponseKind::Missing => { + self.previous_requests + .entry(request) + .or_insert(Vec::new()) + .push((who, Instant::now())); + }, + } + + self.pending_requests.push_front(request); + } + } + + /// Removes any pending justification requests for blocks lower than the + /// given best finalized. + fn collect_garbage(&mut self, best_finalized: NumberFor) { + self.requests.retain(|(_, n)| *n > best_finalized); + self.pending_requests.retain(|(_, n)| *n > best_finalized); + self.peer_requests.retain(|_, (_, n)| *n > best_finalized); + self.previous_requests.retain(|(_, n), _| *n > best_finalized); + } +} + +enum ExtraResponseKind { + Accepted, + Invalid, + Missing, +} + +struct JustificationsRequestsEssence; + +impl ExtraRequestsEssence for JustificationsRequestsEssence { + type Response = Option; + + fn into_network_request(&self, request: ExtraRequest) -> Message { + GenericMessage::BlockRequest(message::generic::BlockRequest { + id: 0, + fields: message::BlockAttributes::JUSTIFICATION, + from: message::FromBlock::Hash(request.0), + to: None, + direction: message::Direction::Ascending, + max: Some(1), + }) + } + + fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Option) -> ExtraResponseKind { + if let Some(justification) = response { + if import_queue.import_justification(request.0, request.1, justification) { + ExtraResponseKind::Accepted + } else { + ExtraResponseKind::Invalid + } + } else { + ExtraResponseKind::Missing + } + } + + fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState { + PeerSyncState::DownloadingJustification(block) + } +} + +struct FinalityProofRequestsEssence; + +impl ExtraRequestsEssence for FinalityProofRequestsEssence { + type Response = Option>; + + fn into_network_request(&self, request: ExtraRequest) -> Message { + unimplemented!() + } + + fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Option) -> ExtraResponseKind { + unimplemented!() + } + + fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState { + PeerSyncState::DownloadingFinalityProof(block) + } +} \ No newline at end of file diff --git a/core/network/src/lib.rs b/core/network/src/lib.rs index 95b6d68113d4a..6c04bd52036bc 100644 --- a/core/network/src/lib.rs +++ b/core/network/src/lib.rs @@ -54,6 +54,7 @@ mod io; mod chain; mod blocks; mod on_demand; +mod extra_requests; pub mod config; pub mod consensus_gossip; pub mod error; diff --git a/core/network/src/service.rs b/core/network/src/service.rs index 05b455aead06a..bd437281d5f8c 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -98,6 +98,10 @@ impl> Link for NetworkLink { self.with_sync(|sync, protocol| sync.request_justification(hash, number, protocol)) } + fn request_finality_proof(&self, hash: &B::Hash, number: NumberFor) { + self.with_sync(|sync, protocol| sync.request_finality_proof(hash, number, protocol)) + } + fn maintain_sync(&self) { self.with_sync(|sync, protocol| sync.maintain_sync(protocol)) } diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index dc929899c5357..012f5f80edf84 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, VecDeque}; use std::sync::Arc; -use std::time::{Duration, Instant}; use protocol::Context; use network_libp2p::{Severity, NodeIndex}; use client::{BlockStatus, ClientInfo}; @@ -24,9 +23,9 @@ use consensus::BlockOrigin; use consensus::import_queue::{ImportQueue, IncomingBlock}; use client::error::Error as ClientError; use blocks::BlockCollection; -use runtime_primitives::Justification; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, NumberFor, Zero}; use runtime_primitives::generic::BlockId; +use extra_requests::ExtraRequestsAggregator; use message::{self, generic::Message as GenericMessage}; use config::Roles; @@ -36,15 +35,13 @@ const MAX_BLOCKS_TO_REQUEST: usize = 128; const MAX_IMPORTING_BLOCKS: usize = 2048; // Number of blocks in the queue that prevents ancestry search. const MAJOR_SYNC_BLOCKS: usize = 5; -// Time to wait before trying to get a justification from the same peer. -const JUSTIFICATION_RETRY_WAIT: Duration = Duration::from_secs(10); // Number of recently announced blocks to track for each peer. const ANNOUNCE_HISTORY_SIZE: usize = 64; // Max number of blocks to download for unknown forks. // TODO: this should take finality into account. See https://github.com/paritytech/substrate/issues/1606 const MAX_UNKNOWN_FORK_DOWNLOAD_LEN: u32 = 32; -struct PeerSync { +pub(crate) struct PeerSync { pub common_number: NumberFor, pub best_hash: B::Hash, pub best_number: NumberFor, @@ -53,186 +50,13 @@ struct PeerSync { } #[derive(Copy, Clone, Eq, PartialEq, Debug)] -enum PeerSyncState { +pub(crate) enum PeerSyncState { AncestorSearch(NumberFor), Available, DownloadingNew(NumberFor), DownloadingStale(B::Hash), DownloadingJustification(B::Hash), -} - -/// Pending justification request for the given block (hash and number). -type PendingJustification = (::Hash, NumberFor); - -/// Manages pending block justification requests. -struct PendingJustifications { - justifications: HashSet>, - pending_requests: VecDeque>, - peer_requests: HashMap>, - previous_requests: HashMap, Vec<(NodeIndex, Instant)>>, -} - -impl PendingJustifications { - fn new() -> PendingJustifications { - PendingJustifications { - justifications: HashSet::new(), - pending_requests: VecDeque::new(), - peer_requests: HashMap::new(), - previous_requests: HashMap::new(), - } - } - - /// Dispatches all possible pending requests to the given peers. Peers are - /// filtered according to the current known best block (i.e. we won't send a - /// justification request for block #10 to a peer at block #2), and we also - /// throttle requests to the same peer if a previous justification request - /// yielded no results. - fn dispatch(&mut self, peers: &mut HashMap>, protocol: &mut Context) { - if self.pending_requests.is_empty() { - return; - } - - // clean up previous failed requests so we can retry again - for (_, requests) in self.previous_requests.iter_mut() { - requests.retain(|(_, instant)| instant.elapsed() < JUSTIFICATION_RETRY_WAIT); - } - - let mut available_peers = peers.iter().filter_map(|(peer, sync)| { - // don't request to any peers that already have pending requests or are unavailable - if sync.state != PeerSyncState::Available || self.peer_requests.contains_key(&peer) { - None - } else { - Some((*peer, sync.best_number)) - } - }).collect::>(); - - let mut last_peer = available_peers.back().map(|p| p.0); - let mut unhandled_requests = VecDeque::new(); - - loop { - let (peer, peer_best_number) = match available_peers.pop_front() { - Some(p) => p, - _ => break, - }; - - // only ask peers that have synced past the block number that we're - // asking the justification for and to whom we haven't already made - // the same request recently - let peer_eligible = { - let request = match self.pending_requests.front() { - Some(r) => r.clone(), - _ => break, - }; - - peer_best_number >= request.1 && - !self.previous_requests - .get(&request) - .map(|requests| requests.iter().any(|i| i.0 == peer)) - .unwrap_or(false) - }; - - if !peer_eligible { - available_peers.push_back((peer, peer_best_number)); - - // we tried all peers and none can answer this request - if Some(peer) == last_peer { - last_peer = available_peers.back().map(|p| p.0); - - let request = self.pending_requests.pop_front() - .expect("verified to be Some in the beginning of the loop; qed"); - - unhandled_requests.push_back(request); - } - - continue; - } - - last_peer = available_peers.back().map(|p| p.0); - - let request = self.pending_requests.pop_front() - .expect("verified to be Some in the beginning of the loop; qed"); - - self.peer_requests.insert(peer, request); - - peers.get_mut(&peer) - .expect("peer was is taken from available_peers; available_peers is a subset of peers; qed") - .state = PeerSyncState::DownloadingJustification(request.0); - - trace!(target: "sync", "Requesting justification for block #{} from {}", request.0, peer); - let request = message::generic::BlockRequest { - id: 0, - fields: message::BlockAttributes::JUSTIFICATION, - from: message::FromBlock::Hash(request.0), - to: None, - direction: message::Direction::Ascending, - max: Some(1), - }; - - protocol.send_message(peer, GenericMessage::BlockRequest(request)); - } - - self.pending_requests.append(&mut unhandled_requests); - } - - /// Queue a justification request (without dispatching it). - fn queue_request(&mut self, justification: &PendingJustification) { - if !self.justifications.insert(*justification) { - return; - } - self.pending_requests.push_back(*justification); - } - - /// Retry any pending request if a peer disconnected. - fn peer_disconnected(&mut self, who: NodeIndex) { - if let Some(request) = self.peer_requests.remove(&who) { - self.pending_requests.push_front(request); - } - } - - /// Processes the response for the request previously sent to the given - /// peer. Queues a retry in case the import fails or the given justification - /// was `None`. - fn on_response( - &mut self, - who: NodeIndex, - justification: Option, - protocol: &mut Context, - import_queue: &ImportQueue, - ) { - // we assume that the request maps to the given response, this is - // currently enforced by the outer network protocol before passing on - // messages to chain sync. - if let Some(request) = self.peer_requests.remove(&who) { - if let Some(justification) = justification { - if import_queue.import_justification(request.0, request.1, justification) { - self.justifications.remove(&request); - self.previous_requests.remove(&request); - return; - } else { - protocol.report_peer( - who, - Severity::Bad(&format!("Invalid justification provided for #{}", request.0)), - ); - } - } else { - self.previous_requests - .entry(request) - .or_insert(Vec::new()) - .push((who, Instant::now())); - } - - self.pending_requests.push_front(request); - } - } - - /// Removes any pending justification requests for blocks lower than the - /// given best finalized. - fn collect_garbage(&mut self, best_finalized: NumberFor) { - self.justifications.retain(|(_, n)| *n > best_finalized); - self.pending_requests.retain(|(_, n)| *n > best_finalized); - self.peer_requests.retain(|_, (_, n)| *n > best_finalized); - self.previous_requests.retain(|(_, n), _| *n > best_finalized); - } + DownloadingFinalityProof(B::Hash), } /// Relay chain sync strategy. @@ -244,7 +68,7 @@ pub struct ChainSync { best_queued_hash: B::Hash, required_block_attributes: message::BlockAttributes, import_queue: Arc>, - justifications: PendingJustifications, + extra_requests: ExtraRequestsAggregator, } /// Reported sync state. @@ -290,7 +114,7 @@ impl ChainSync { blocks: BlockCollection::new(), best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash), best_queued_number: info.best_queued_number.unwrap_or(info.chain.best_number), - justifications: PendingJustifications::new(), + extra_requests: ExtraRequestsAggregator::new(), required_block_attributes, import_queue, } @@ -465,7 +289,7 @@ impl ChainSync { } } }, - PeerSyncState::Available | PeerSyncState::DownloadingJustification(..) => Vec::new(), + PeerSyncState::Available | PeerSyncState::DownloadingJustification(..) | PeerSyncState::DownloadingFinalityProof(..) => Vec::new(), } } else { Vec::new() @@ -513,7 +337,7 @@ impl ChainSync { return; } - self.justifications.on_response( + self.extra_requests.on_justification( who, response.justification, protocol, @@ -542,20 +366,30 @@ impl ChainSync { for peer in peers { self.download_new(protocol, peer); } - self.justifications.dispatch(&mut self.peers, protocol); + self.extra_requests.dispatch(&mut self.peers, protocol); } /// Called periodically to perform any time-based actions. pub fn tick(&mut self, protocol: &mut Context) { - self.justifications.dispatch(&mut self.peers, protocol); + self.extra_requests.dispatch(&mut self.peers, protocol); } /// Request a justification for the given block. /// /// Queues a new justification request and tries to dispatch all pending requests. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { - self.justifications.queue_request(&(*hash, number)); - self.justifications.dispatch(&mut self.peers, protocol); + self.extra_requests.request_justification(&(*hash, number), &mut self.peers, protocol); +/* self.justifications.queue_request(&(*hash, number)); + self.justifications.dispatch(&mut self.peers, protocol);*/ + } + + /// Request a finality_proof for the given block. + /// + /// Queues a new finality proof request and tries to dispatch all pending requests. + pub fn request_finality_proof(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { + self.extra_requests.request_finality_proof(&(*hash, number), &mut self.peers, protocol); + /*self.finality_proofs.queue_request(&(*hash, number)); + self.finality_proofs.dispatch(&mut self.peers, protocol);*/ } /// Notify about successful import of the given block. @@ -565,7 +399,7 @@ impl ChainSync { /// Notify about finalization of the given block. pub fn block_finalized(&mut self, _hash: &B::Hash, number: NumberFor) { - self.justifications.collect_garbage(number); + self.extra_requests.collect_garbage(number); } fn block_queued(&mut self, hash: &B::Hash, number: NumberFor) { @@ -655,7 +489,7 @@ impl ChainSync { pub(crate) fn peer_disconnected(&mut self, protocol: &mut Context, who: NodeIndex) { self.blocks.clear_peer_download(who); self.peers.remove(&who); - self.justifications.peer_disconnected(who); + self.extra_requests.peer_disconnected(who); self.maintain_sync(protocol); } From 2c4ae739a1d27e2cc04ff2594b04a7282e607115 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Feb 2019 12:16:52 +0300 Subject: [PATCH 08/42] finality proof messages --- core/consensus/common/src/block_import.rs | 16 ++++++++++ core/consensus/common/src/import_queue.rs | 22 ++++++++++++-- core/finality-grandpa/src/light_import.rs | 36 +++++++++++++++++++++++ core/network/src/extra_requests.rs | 18 +++++++++--- core/network/src/message.rs | 20 +++++++++++++ core/network/src/test/block_import.rs | 2 +- core/network/src/test/mod.rs | 25 ++++++++++++++-- 7 files changed, 129 insertions(+), 10 deletions(-) diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index 1fc4dc720d621..927d469ec36a8 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -167,3 +167,19 @@ pub trait JustificationImport { justification: Justification, ) -> Result<(), Self::Error>; } + +/// Finality proof import trait. +pub trait FinalityProofImport { + type Error: ::std::error::Error + Send + 'static; + + /// Called by the import queue when it is started. + fn on_start(&self, _link: &crate::import_queue::Link) { } + + /// Import a Block justification and finalize the given block. + fn import_finality_proof( + &self, + hash: B::Hash, + number: NumberFor, + finality_proof: Vec, + ) -> Result<(), Self::Error>; +} diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 6227326a6c436..acab69fcf1ad4 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -24,7 +24,7 @@ //! The `BasicQueue` and `BasicVerifier` traits allow serial queues to be //! instantiated simply. -use crate::block_import::{ImportBlock, BlockImport, JustificationImport, ImportResult, BlockOrigin}; +use crate::block_import::{ImportBlock, BlockImport, JustificationImport, FinalityProofImport, ImportResult, BlockOrigin}; use std::collections::{HashSet, VecDeque}; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -42,6 +42,9 @@ pub type SharedBlockImport = Arc + S /// Shared justification import struct used by the queue. pub type SharedJustificationImport = Arc + Send + Sync>; +/// Shared finality proof import struct used by the queue. +pub type SharedFinalityProofImport = Arc + Send + Sync>; + /// Maps to the Origin used by the network. pub type Origin = usize; @@ -98,6 +101,8 @@ pub trait ImportQueue: Send + Sync { fn import_blocks(&self, origin: BlockOrigin, blocks: Vec>); /// Import a block justification. fn import_justification(&self, hash: B::Hash, number: NumberFor, justification: Justification) -> bool; + /// Import block finality proof. + fn import_finality_proof(&self, hash: B::Hash, number: NumberFor, finality_proof: Vec) -> bool; } /// Import queue status. It isn't completely accurate. @@ -116,6 +121,7 @@ pub struct BasicQueue> { verifier: Arc, block_import: SharedBlockImport, justification_import: Option>, + finality_proof_import: Option>, } /// Locks order: queue, queue_blocks, best_importing_number @@ -129,13 +135,19 @@ pub struct AsyncImportQueueData { impl> BasicQueue { /// Instantiate a new basic queue, with given verifier and justification import. - pub fn new(verifier: Arc, block_import: SharedBlockImport, justification_import: Option>) -> Self { + pub fn new( + verifier: Arc, + block_import: SharedBlockImport, + justification_import: Option>, + finality_proof_import: Option>, + ) -> Self { Self { handle: Mutex::new(None), data: Arc::new(AsyncImportQueueData::new()), verifier, block_import, justification_import, + finality_proof_import, } } } @@ -236,6 +248,12 @@ impl> ImportQueue for BasicQueue { justification_import.import_justification(hash, number, justification).is_ok() }).unwrap_or(false) } + + fn import_finality_proof(&self, hash: B::Hash, number: NumberFor, finality_proof: Vec) -> bool { + self.finality_proof_import.as_ref().map(|finality_proof_import| { + finality_proof_import.import_finality_proof(hash, number, finality_proof).is_ok() + }).unwrap_or(false) + } } impl> Drop for BasicQueue { diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index dc7d2ced8e4a7..2324f0d3ed24a 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -137,6 +137,42 @@ impl, RA> JustificationImport } } +impl, RA> FinalityProofImport + for GrandpaLightBlockImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + RA: Send + Sync, +{ + type Error = ConsensusError; + + fn on_start(&self, link: &::consensus_common::import_queue::Link) { +/* let chain_info = match self.client.info() { + Ok(info) => info.chain, + _ => return, + }; + + let data = self.data.read(); + for (pending_number, pending_hash) in data.consensus_changes.pending_changes() { + if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number { + link.request_justification(pending_hash, *pending_number); + } + }*/ + } + + fn import_finality_proof( + &self, + hash: Block::Hash, + number: NumberFor, + finality_proof: Vec, + ) -> Result<(), Self::Error> { +// self.do_import_justification(hash, number, justification) +unimplemented!() + } +} + impl, RA> BlockImport for GrandpaLightBlockImport where NumberFor: grandpa::BlockNumberOps, diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index f68fb0550cf1e..db96a60e62549 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -20,7 +20,7 @@ use protocol::Context; use network_libp2p::{Severity, NodeIndex}; use consensus::import_queue::ImportQueue; use runtime_primitives::Justification; -use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use runtime_primitives::traits::{Block as BlockT, NumberFor}; use message::{self, Message, generic::Message as GenericMessage}; use sync::{PeerSync, PeerSyncState}; @@ -301,11 +301,21 @@ impl ExtraRequestsEssence for FinalityProofRequestsEssence { type Response = Option>; fn into_network_request(&self, request: ExtraRequest) -> Message { - unimplemented!() + GenericMessage::FinalityProofRequest(message::generic::FinalityProofRequest { + block: request.0, + }) } - fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Option) -> ExtraResponseKind { - unimplemented!() + fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Option>) -> ExtraResponseKind { + if let Some(finality_proof) = response { + if import_queue.import_finality_proof(request.0, request.1, finality_proof) { + ExtraResponseKind::Accepted + } else { + ExtraResponseKind::Invalid + } + } else { + ExtraResponseKind::Missing + } } fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState { diff --git a/core/network/src/message.rs b/core/network/src/message.rs index 3a7238f9b59f4..c1ebe8ecc86b0 100644 --- a/core/network/src/message.rs +++ b/core/network/src/message.rs @@ -189,6 +189,10 @@ pub mod generic { RemoteChangesRequest(RemoteChangesRequest), /// Remote changes reponse. RemoteChangesResponse(RemoteChangesResponse), + /// Finality proof request. + FinalityProofRequest(FinalityProofRequest), + /// Finality proof reponse. + FinalityProofResponse(FinalityProofResponse), /// Chain-specific message #[codec(index = "255")] ChainSpecific(Vec), @@ -321,4 +325,20 @@ pub mod generic { /// Missing changes tries roots proof. pub roots_proof: Vec>, } + + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + /// Finality proof request. + pub struct FinalityProofRequest { + /// Hash of the block to request proof for. + pub block: H, + } + + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + /// Finality proof response. + pub struct FinalityProofResponse { + /// Hash of the block (the same as in the FinalityProofRequest). + pub block: H, + /// Finality proof (if available). + pub proof: Option>, + } } diff --git a/core/network/src/test/block_import.rs b/core/network/src/test/block_import.rs index 8ab883124c395..b2791841e02a5 100644 --- a/core/network/src/test/block_import.rs +++ b/core/network/src/test/block_import.rs @@ -164,7 +164,7 @@ fn async_import_queue_drops() { // Perform this test multiple times since it exhibits non-deterministic behavior. for _ in 0..100 { let verifier = Arc::new(PassThroughVerifier(true)); - let queue = BasicQueue::new(verifier, Arc::new(test_client::new()), None); + let queue = BasicQueue::new(verifier, Arc::new(test_client::new()), None, None); queue.start(TestLink::new()).unwrap(); drop(queue); } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index e0887b0df0646..3caebac6bb036 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -40,7 +40,7 @@ use keyring::Keyring; use codec::Encode; use consensus::{BlockOrigin, ImportBlock, JustificationImport, ForkChoiceStrategy, Error as ConsensusError, ErrorKind as ConsensusErrorKind}; use consensus::import_queue::{import_many_blocks, ImportQueue, ImportQueueStatus, IncomingBlock}; -use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, Verifier}; +use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, Verifier}; use specialization::NetworkSpecialization; use consensus_gossip::ConsensusGossip; use service::ExecuteInContext; @@ -121,18 +121,25 @@ pub struct SyncImportQueue> { link: ImportCB, block_import: SharedBlockImport, justification_import: Option>, + finality_proof_import: Option>, } #[cfg(any(test, feature = "test-helpers"))] impl> SyncImportQueue { /// Create a new SyncImportQueue wrapping the given Verifier and block import /// handle. - pub fn new(verifier: Arc, block_import: SharedBlockImport, justification_import: Option>) -> Self { + pub fn new( + verifier: Arc, + block_import: SharedBlockImport, + justification_import: Option>, + finality_proof_import: Option>, + ) -> Self { let queue = SyncImportQueue { verifier, link: ImportCB::new(), block_import, justification_import, + finality_proof_import, }; let v = queue.verifier.clone(); @@ -202,6 +209,18 @@ impl> ImportQueue for SyncImpor justification_import.import_justification(hash, number, justification).is_ok() }).unwrap_or(false) } + + fn import_finality_proof( + &self, + hash: B::Hash, + number: NumberFor, + finality_proof: Vec, + ) -> bool { + self.finality_proof_import.as_ref().map(|finality_proof_import| { + finality_proof_import.import_finality_proof(hash, number, finality_proof).is_ok() + }).unwrap_or(false) + + } } struct DummyContextExecutor(Arc>, Arc>>); @@ -555,7 +574,7 @@ pub trait TestNetFactory: Sized { let verifier = self.make_verifier(client.clone(), config); let (block_import, justification_import, data) = self.make_block_import(client.clone()); - let import_queue = Arc::new(SyncImportQueue::new(verifier, block_import, justification_import)); + let import_queue = Arc::new(SyncImportQueue::new(verifier, block_import, justification_import, None)); let specialization = DummySpecialization { }; let sync = Protocol::new( config.clone(), From fbf49c60071b195153f6b261505a700be449f486 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Feb 2019 13:13:28 +0300 Subject: [PATCH 09/42] fixed compilation --- core/consensus/aura/src/lib.rs | 5 +++-- core/consensus/common/src/lib.rs | 2 +- core/finality-grandpa/src/light_import.rs | 4 +++- core/network/src/on_demand.rs | 5 +++++ node/cli/src/service.rs | 8 +++++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index 98e4ce42a9f5d..570f8c5e1de4e 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -34,7 +34,7 @@ use parity_codec::Encode; use consensus_common::{ Authorities, BlockImport, Environment, Proposer, ForkChoiceStrategy }; -use consensus_common::import_queue::{Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport}; +use consensus_common::import_queue::{Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport}; use client::ChainHead; use client::block_builder::api::BlockBuilder as BlockBuilderApi; use consensus_common::{ImportBlock, BlockOrigin}; @@ -659,6 +659,7 @@ pub fn import_queue( slot_duration: SlotDuration, block_import: SharedBlockImport, justification_import: Option>, + finality_proof_import: Option>, client: Arc, extra: E, inherent_data_providers: InherentDataProviders, @@ -674,7 +675,7 @@ pub fn import_queue( let verifier = Arc::new( AuraVerifier { client: client.clone(), extra, inherent_data_providers } ); - Ok(BasicQueue::new(verifier, block_import, justification_import)) + Ok(BasicQueue::new(verifier, block_import, justification_import, finality_proof_import)) } #[cfg(test)] diff --git a/core/consensus/common/src/lib.rs b/core/consensus/common/src/lib.rs index 73278ca5790fe..2a4969693aea4 100644 --- a/core/consensus/common/src/lib.rs +++ b/core/consensus/common/src/lib.rs @@ -43,7 +43,7 @@ pub mod evaluation; const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024; pub use self::error::{Error, ErrorKind}; -pub use block_import::{BlockImport, JustificationImport, ImportBlock, BlockOrigin, ImportResult, ForkChoiceStrategy}; +pub use block_import::{BlockImport, JustificationImport, FinalityProofImport, ImportBlock, BlockOrigin, ImportResult, ForkChoiceStrategy}; /// Trait for getting the authorities at a given block. pub trait Authorities { diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 2324f0d3ed24a..1cb0b9bdb9810 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -24,7 +24,7 @@ use client::{ }; use client::blockchain::HeaderBackend; use codec::{Encode, Decode}; -use consensus_common::{BlockImport, JustificationImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; +use consensus_common::{BlockImport, JustificationImport, FinalityProofImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; use grandpa::VoterSet; use runtime_primitives::Justification; use runtime_primitives::traits::{ @@ -268,6 +268,8 @@ impl, RA> // import justification // apply new authorities set + + unimplemented!("TODO") } /// Import a block justification and finalize the block. diff --git a/core/network/src/on_demand.rs b/core/network/src/on_demand.rs index a9b00dc1200ca..405eba284f50a 100644 --- a/core/network/src/on_demand.rs +++ b/core/network/src/on_demand.rs @@ -151,6 +151,11 @@ impl OnDemand where } } + /// Get checker reference. + pub fn checker(&self) -> &Arc> { + &self.checker + } + /// Sets weak reference to network service. pub fn set_service_link(&self, service: Weak) { self.core.lock().service = service; diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 3a10e1af7ae2a..1402b1af06db0 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -140,6 +140,7 @@ construct_service_factory! { slot_duration, block_import, Some(justification_import), + None, client, NothingExtra, config.custom.inherent_data_providers.clone(), @@ -151,13 +152,18 @@ construct_service_factory! { NothingExtra, > { |config: &FactoryFullConfiguration, client: Arc>| { + let fetch_checker = client.backend().blockchain().fetcher() + .upgrade() + .map(|fetcher| fetcher.checker().clone()) + .ok_or_else(|| "Trying to start light import queue without active fetch checker")?; let block_import = Arc::new(grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>( - client.clone(), client.clone() + client.clone(), fetch_checker, client.clone() )?); import_queue( SlotDuration::get_or_compute(&*client)?, block_import.clone(), + Some(block_import.clone()), Some(block_import), client, NothingExtra, From ac72a4038bc435a5383af3db80aa833209635137 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Feb 2019 14:07:48 +0300 Subject: [PATCH 10/42] pass verifier to import_finality_proof --- core/consensus/common/src/block_import.rs | 3 + core/consensus/common/src/import_queue.rs | 4 +- core/finality-grandpa/src/light_import.rs | 124 +++++++++------------- core/network/src/test/mod.rs | 2 +- 4 files changed, 57 insertions(+), 76 deletions(-) diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index 927d469ec36a8..b90d803baf7c4 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -20,6 +20,8 @@ use runtime_primitives::traits::{AuthorityIdFor, Block as BlockT, DigestItemFor, use runtime_primitives::Justification; use std::borrow::Cow; +use crate::import_queue::Verifier; + /// Block import result. #[derive(Debug, PartialEq, Eq)] pub enum ImportResult { @@ -181,5 +183,6 @@ pub trait FinalityProofImport { hash: B::Hash, number: NumberFor, finality_proof: Vec, + verifier: &Verifier, ) -> Result<(), Self::Error>; } diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index acab69fcf1ad4..68ab22c578901 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -64,7 +64,7 @@ pub struct IncomingBlock { } /// Verify a justification of a block -pub trait Verifier: Send + Sync + Sized { +pub trait Verifier: Send + Sync { /// Verify the given data and return the ImportBlock and an optional /// new set of validators to import. If not, err with an Error-Message /// presented to the User in the logs. @@ -251,7 +251,7 @@ impl> ImportQueue for BasicQueue { fn import_finality_proof(&self, hash: B::Hash, number: NumberFor, finality_proof: Vec) -> bool { self.finality_proof_import.as_ref().map(|finality_proof_import| { - finality_proof_import.import_finality_proof(hash, number, finality_proof).is_ok() + finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier).is_ok() }).unwrap_or(false) } } diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 1cb0b9bdb9810..36ed13f8bac95 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -102,7 +102,7 @@ pub struct GrandpaLightBlockImport, RA> { data: Arc>>, } -impl, RA> JustificationImport +impl, RA> BlockImport for GrandpaLightBlockImport where NumberFor: grandpa::BlockNumberOps, B: Backend + 'static, @@ -113,27 +113,37 @@ impl, RA> JustificationImport { type Error = ConsensusError; - fn on_start(&self, link: &::consensus_common::import_queue::Link) { - let chain_info = match self.client.info() { - Ok(info) => info.chain, - _ => return, - }; + fn import_block(&self, mut block: ImportBlock, new_authorities: Option>) + -> Result + { + let hash = block.post_header().hash(); + let number = block.header.number().clone(); - let data = self.data.read(); - for (pending_number, pending_hash) in data.consensus_changes.pending_changes() { - if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number { - link.request_justification(pending_hash, *pending_number); + // we don't want to finalize on `inner.import_block` + let justification = block.justification.take(); + let enacts_consensus_change = new_authorities.is_some(); + let import_result = self.client.import_block(block, new_authorities); + + let import_result = { + match import_result { + Ok(ImportResult::Queued) => ImportResult::Queued, + Ok(r) => return Ok(r), + Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), } - } - } + }; - fn import_justification( - &self, - hash: Block::Hash, - number: NumberFor, - justification: Justification, - ) -> Result<(), Self::Error> { - self.do_import_justification(hash, number, justification) + match justification { + Some(justification) => { + self.do_import_justification(hash, number, justification)?; + Ok(import_result) + }, + None if enacts_consensus_change => { + // remember that we need justification for this block + self.data.write().consensus_changes.note_change((number, hash)); + Ok(ImportResult::NeedsJustification) + }, + None => Ok(import_result), + } } } @@ -149,7 +159,7 @@ impl, RA> FinalityProofImport type Error = ConsensusError; fn on_start(&self, link: &::consensus_common::import_queue::Link) { -/* let chain_info = match self.client.info() { + let chain_info = match self.client.info() { Ok(info) => info.chain, _ => return, }; @@ -157,9 +167,9 @@ impl, RA> FinalityProofImport let data = self.data.read(); for (pending_number, pending_hash) in data.consensus_changes.pending_changes() { if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number { - link.request_justification(pending_hash, *pending_number); + link.request_finality_proof(pending_hash, *pending_number); } - }*/ + } } fn import_finality_proof( @@ -167,54 +177,9 @@ impl, RA> FinalityProofImport hash: Block::Hash, number: NumberFor, finality_proof: Vec, + verifier: &Verifier, ) -> Result<(), Self::Error> { -// self.do_import_justification(hash, number, justification) -unimplemented!() - } -} - -impl, RA> BlockImport - for GrandpaLightBlockImport where - NumberFor: grandpa::BlockNumberOps, - B: Backend + 'static, - E: CallExecutor + 'static + Clone + Send + Sync, - DigestFor: Encode, - DigestItemFor: DigestItem, - RA: Send + Sync, -{ - type Error = ConsensusError; - - fn import_block(&self, mut block: ImportBlock, new_authorities: Option>) - -> Result - { - let hash = block.post_header().hash(); - let number = block.header.number().clone(); - - // we don't want to finalize on `inner.import_block` - let justification = block.justification.take(); - let enacts_consensus_change = new_authorities.is_some(); - let import_result = self.client.import_block(block, new_authorities); - - let import_result = { - match import_result { - Ok(ImportResult::Queued) => ImportResult::Queued, - Ok(r) => return Ok(r), - Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), - } - }; - - match justification { - Some(justification) => { - self.do_import_justification(hash, number, justification)?; - Ok(import_result) - }, - None if enacts_consensus_change => { - // remember that we need justification for this block - self.data.write().consensus_changes.note_change((number, hash)); - Ok(ImportResult::NeedsJustification) - }, - None => Ok(import_result), - } + self.do_import_finality_proof(hash, number, justification, verifier) } } @@ -230,8 +195,9 @@ impl, RA> _hash: Block::Hash, _number: NumberFor, finality_proof: Vec, + verifier: &Verifier, ) -> Result<(), ConsensusError> { - // TODO: ensure that the proof is for non-finalzie block + // TODO: ensure that the proof is for non-finalize block let data = self.data.write(); let authority_set_id = data.authority_set.set_id(); @@ -261,15 +227,27 @@ impl, RA> finality_proof, ).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; + // try to import all new headers + let block_origin = BlockOrigin::NetworkBroadcast; for header_to_import in finality_effects.headers_to_import { - // import block + let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?; + assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify"); + self.import_block(block_to_import, new_authorities); } - // import justification + // try to import latest justification + let finalized_block_hash = finality_effects.block; + let finalized_block_number = self.client.backend().blockchain() + .expect_block_number_from_id(BlockId::Hash(finality_effects.block))?; + self.do_import_justification(finalized_block_hash, finalized_block_number, finality_effects.justification)?; // apply new authorities set + data.authority_set.set_authorities( + finality_effects.new_set_id, + finality_effects.new_authorities, + ); - unimplemented!("TODO") + Ok(()) } /// Import a block justification and finalize the block. diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index 3caebac6bb036..b00e24cbc37c4 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -217,7 +217,7 @@ impl> ImportQueue for SyncImpor finality_proof: Vec, ) -> bool { self.finality_proof_import.as_ref().map(|finality_proof_import| { - finality_proof_import.import_finality_proof(hash, number, finality_proof).is_ok() + finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier).is_ok() }).unwrap_or(false) } From d4becc18a87ee9786db068cdfe61cf0aa5a930f4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Feb 2019 15:30:49 +0300 Subject: [PATCH 11/42] do not fetch remote proof from light import directly --- core/consensus/common/src/block_import.rs | 3 + core/consensus/common/src/import_queue.rs | 11 + core/finality-grandpa/src/finality_proof.rs | 24 +- core/finality-grandpa/src/light_import.rs | 429 ++++++++++---------- node/cli/src/service.rs | 2 +- 5 files changed, 247 insertions(+), 222 deletions(-) diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index b90d803baf7c4..554d5b25f29db 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -38,6 +38,9 @@ pub enum ImportResult { /// Added to the import queue but must be justified /// (usually required to safely enact consensus changes). NeedsJustification, + /// Added to the import queue but finality proof is required + /// (usually required to safely enact consensus changes). + NeedsFinalityProof, } /// Block data origin. diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 68ab22c578901..6863819c3355d 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -338,6 +338,8 @@ pub enum BlockImportResult>( trace!(target: "sync", "Block queued but requires justification {}: {:?}", number, hash); Ok(BlockImportResult::ImportedUnjustified(hash, number)) }, + Ok(ImportResult::NeedsFinalityProof) => { + trace!(target: "sync", "Block queued but requires finality proof {}: {:?}", number, hash); + Ok(BlockImportResult::ImportedWithoutFinalityProof(hash, number)) + }, Ok(ImportResult::UnknownParent) => { debug!(target: "sync", "Block with unknown parent {}: {:?}, parent: {:?}", number, hash, parent); Err(BlockImportError::UnknownParent) @@ -490,6 +496,11 @@ pub fn process_import_result( link.request_justification(&hash, number); 1 }, + Ok(BlockImportResult::ImportedWithoutFinalityProof(hash, number)) => { + link.block_imported(&hash, number); + link.request_finality_proof(&hash, number); + 1 + }, Err(BlockImportError::IncompleteHeader(who)) => { if let Some(peer) = who { link.useless_peer(peer, "Sent block with incomplete header to import"); diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 51fc445969a48..48bbb1cee05d3 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -29,8 +29,6 @@ //! The caller should track the `set_id`. The most straightforward way is to fetch finality //! proofs ONLY for blocks on the tip of the chain and track the latest known `set_id`. -use grandpa::VoterSet; - use client::{ blockchain::Backend as BlockchainBackend, error::{ErrorKind as ClientErrorKind, Result as ClientResult}, @@ -56,7 +54,7 @@ pub struct FinalityEffects { /// New authorities set id that should be applied starting from block. pub new_set_id: u64, /// New authorities set that should be applied starting from block. - pub new_authorities: VoterSet, + pub new_authorities: Vec<(Ed25519AuthorityId, u64)>, } /// Single fragment of proof-of-finality. @@ -206,7 +204,7 @@ pub fn prove_finality, B, GetAuthorities, ProveAuthorit pub(crate) fn check_finality_proof, B, CheckAuthoritiesProof>( blockchain: &B, current_set_id: u64, - current_authorities: VoterSet, + current_authorities: Vec<(Ed25519AuthorityId, u64)>, check_authorities_proof: CheckAuthoritiesProof, remote_proof: Vec, ) -> ClientResult>> @@ -217,7 +215,7 @@ pub(crate) fn check_finality_proof, B, CheckAuthorities Block::Hash, Block::Header, Vec>, - ) -> ClientResult>, + ) -> ClientResult>, { do_check_finality_proof( blockchain, @@ -235,7 +233,7 @@ pub(crate) fn check_finality_proof, B, CheckAuthorities fn do_check_finality_proof, B, J, CheckAuthoritiesProof>( blockchain: &B, current_set_id: u64, - current_authorities: VoterSet, + current_authorities: Vec<(Ed25519AuthorityId, u64)>, check_authorities_proof: CheckAuthoritiesProof, remote_proof: Vec, ) -> ClientResult> @@ -247,7 +245,7 @@ fn do_check_finality_proof, B, J, CheckAuthoritiesProof Block::Hash, Block::Header, Vec>, - ) -> ClientResult>, + ) -> ClientResult>, { // decode finality proof let proof = FinalityProof::::decode(&mut &remote_proof[..]) @@ -300,7 +298,7 @@ fn check_finality_proof_fragment, B, J, CheckAuthoritie Block::Hash, Block::Header, Vec>, - ) -> ClientResult>, + ) -> ClientResult>, { // verify justification using previous authorities set let (mut current_set_id, mut current_authorities) = authority_set.extract_authorities(); @@ -325,12 +323,12 @@ fn check_finality_proof_fragment, B, J, CheckAuthoritie /// Authorities set from initial authorities set or finality effects. enum AuthoritiesOrEffects { - Authorities(u64, VoterSet), + Authorities(u64, Vec<(Ed25519AuthorityId, u64)>), Effects(FinalityEffects), } impl AuthoritiesOrEffects { - pub fn extract_authorities(self) -> (u64, VoterSet) { + pub fn extract_authorities(self) -> (u64, Vec<(Ed25519AuthorityId, u64)>) { match self { AuthoritiesOrEffects::Authorities(set_id, authorities) => (set_id, authorities), AuthoritiesOrEffects::Effects(effects) => (effects.new_set_id, effects.new_authorities), @@ -351,7 +349,7 @@ trait ProvableJustification: Encode + Decode { fn target_block(&self) -> (Header::Number, Header::Hash); /// Verify justification with respect to authorities set and authorities set id. - fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()>; + fn verify(&self, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()>; } impl> ProvableJustification for GrandpaJustification @@ -362,8 +360,8 @@ impl> ProvableJustification for GrandpaJ (self.commit.target_number, self.commit.target_hash) } - fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()> { - GrandpaJustification::verify(self, set_id, authorities) + fn verify(&self, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()> { + GrandpaJustification::verify(self, set_id, &authorities.iter().cloned().collect()) } } diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 36ed13f8bac95..9dde71ef6f2e6 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -24,7 +24,7 @@ use client::{ }; use client::blockchain::HeaderBackend; use codec::{Encode, Decode}; -use consensus_common::{BlockImport, JustificationImport, FinalityProofImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; +use consensus_common::{import_queue::Verifier, BlockOrigin, BlockImport, JustificationImport, FinalityProofImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; use grandpa::VoterSet; use runtime_primitives::Justification; use runtime_primitives::traits::{ @@ -53,7 +53,7 @@ pub fn light_block_import, RA, PRA>( E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, PRA: ProvideRuntimeApi, - PRA::Api: GrandpaApi + PRA::Api: GrandpaApi, { use runtime_primitives::traits::Zero; let authority_set = match Backend::get_aux(&**client.backend(), LIGHT_AUTHORITY_SET_KEY)? { @@ -102,6 +102,19 @@ pub struct GrandpaLightBlockImport, RA> { data: Arc>>, } +/// Mutable data of light block importer. +struct LightImportData> { + authority_set: LightAuthoritySet, + consensus_changes: ConsensusChanges>, +} + +/// Latest authority set tracker. +#[derive(Debug, Encode, Decode)] +struct LightAuthoritySet { + set_id: u64, + authorities: Vec<(Ed25519AuthorityId, u64)>, +} + impl, RA> BlockImport for GrandpaLightBlockImport where NumberFor: grandpa::BlockNumberOps, @@ -116,34 +129,7 @@ impl, RA> BlockImport fn import_block(&self, mut block: ImportBlock, new_authorities: Option>) -> Result { - let hash = block.post_header().hash(); - let number = block.header.number().clone(); - - // we don't want to finalize on `inner.import_block` - let justification = block.justification.take(); - let enacts_consensus_change = new_authorities.is_some(); - let import_result = self.client.import_block(block, new_authorities); - - let import_result = { - match import_result { - Ok(ImportResult::Queued) => ImportResult::Queued, - Ok(r) => return Ok(r), - Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), - } - }; - - match justification { - Some(justification) => { - self.do_import_justification(hash, number, justification)?; - Ok(import_result) - }, - None if enacts_consensus_change => { - // remember that we need justification for this block - self.data.write().consensus_changes.note_change((number, hash)); - Ok(ImportResult::NeedsJustification) - }, - None => Ok(import_result), - } + do_import_block(&*self.client, &mut *self.data.write(), block, new_authorities) } } @@ -179,179 +165,10 @@ impl, RA> FinalityProofImport finality_proof: Vec, verifier: &Verifier, ) -> Result<(), Self::Error> { - self.do_import_finality_proof(hash, number, justification, verifier) - } -} - -impl, RA> - GrandpaLightBlockImport where - NumberFor: grandpa::BlockNumberOps, - B: Backend + 'static, - E: CallExecutor + 'static + Clone + Send + Sync, - RA: Send + Sync, -{ - fn import_finality_proof( - &self, - _hash: Block::Hash, - _number: NumberFor, - finality_proof: Vec, - verifier: &Verifier, - ) -> Result<(), ConsensusError> { - // TODO: ensure that the proof is for non-finalize block - let data = self.data.write(); - - let authority_set_id = data.authority_set.set_id(); - let authorities = data.authority_set.authorities(); - let finality_effects = ::finality_proof::check_finality_proof( - &*self.client.backend().blockchain(), - authority_set_id, - authorities, - |hash, header, authorities_proof| { - let request = RemoteCallRequest { - block: hash, - header, - method: "GrandpaApi_grandpa_authorities".into(), - call_data: vec![], - retry_count: None, - }; - - self.fetch_checker.check_execution_proof(&request, authorities_proof) - .and_then(|authorities| { - let authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &authorities[..]) - .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( - "failed to decode GRANDPA authorities set proof".into(), - )))?; - Ok(authorities.into_iter().collect()) - }) - }, - finality_proof, - ).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; - - // try to import all new headers - let block_origin = BlockOrigin::NetworkBroadcast; - for header_to_import in finality_effects.headers_to_import { - let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?; - assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify"); - self.import_block(block_to_import, new_authorities); - } - - // try to import latest justification - let finalized_block_hash = finality_effects.block; - let finalized_block_number = self.client.backend().blockchain() - .expect_block_number_from_id(BlockId::Hash(finality_effects.block))?; - self.do_import_justification(finalized_block_hash, finalized_block_number, finality_effects.justification)?; - - // apply new authorities set - data.authority_set.set_authorities( - finality_effects.new_set_id, - finality_effects.new_authorities, - ); - - Ok(()) + do_import_finality_proof(&*self.client, &*self.fetch_checker, &mut *self.data.write(), hash, number, finality_proof, verifier) } - - /// Import a block justification and finalize the block. - fn do_import_justification( - &self, - hash: Block::Hash, - number: NumberFor, - encoded_justification: Justification, - ) -> Result<(), ConsensusError> { - let mut data = self.data.write(); - - // with justification, we have two cases - // - // optimistic: the same GRANDPA authorities set has generated intermediate justification - // => justification is verified using current authorities set + we could proceed further - // - // pessimistic scenario: the GRANDPA authorities set has changed - // => we need to fetch new authorities set from remote node (the set ID increases by one) - - // first, try to behave optimistically - let authority_set_id = data.authority_set.set_id(); - let justification = GrandpaJustification::::decode_and_verify( - &encoded_justification, - authority_set_id, - &data.authority_set.authorities(), - ); - - // BadJustification error means that justification has been successfully decoded, but - // it isn't valid within current authority set - let (justification, new_grandpa_authorities) = match justification { - Err(ClientError(ClientErrorKind::BadJustification(_), _)) => { - let into_err = |e: ClientError| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())); - - // fetch GRANDPA authority set from remote node - let header = self.client.backend().blockchain().expect_header(BlockId::Hash(hash)).map_err(into_err)?; - let parent_block_id = BlockId::Hash(*header.parent_hash()); - let grandpa_authorities = self.client.executor().call(&parent_block_id, "GrandpaApi_grandpa_authorities", &[]) - .map_err(into_err)?; - let grandpa_authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &grandpa_authorities[..]) - .ok_or_else(|| ConsensusErrorKind::ClientImport( - ClientErrorKind::BadJustification("failed to decode GRANDPA authorities set proof".into()).to_string() - ))?; - - // ... and try to reverify justification - let justification = GrandpaJustification::decode_and_verify( - &encoded_justification, - authority_set_id + 1, - &grandpa_authorities.iter().cloned().collect(), - ).map_err(into_err)?; - - (justification, Some(grandpa_authorities)) - }, - Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), - Ok(justification) => (justification, None), - }; - - // finalize the block - self.client.finalize_block(BlockId::Hash(hash), Some(justification.encode()), true).map_err(|e| { - warn!(target: "finality", "Error applying finality to block {:?}: {:?}", (hash, number), e); - ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())) - })?; - - // forget obsoleted consensus changes - let consensus_finalization_res = data.consensus_changes - .finalize((number, hash), |at_height| canonical_at_height(&self.client, (hash, number), at_height)); - match consensus_finalization_res { - Ok((true, _)) => require_insert_aux( - &self.client, - LIGHT_CONSENSUS_CHANGES_KEY, - &data.consensus_changes, - "consensus changes", - )?, - Ok(_) => (), - Err(error) => return on_post_finalization_error(error, "consensus changes"), - } - - // ... and finally update the authority set - if let Some(new_grandpa_authorities) = new_grandpa_authorities { - data.authority_set.update_authorities(new_grandpa_authorities); - - require_insert_aux( - &self.client, - LIGHT_AUTHORITY_SET_KEY, - &data.authority_set, - "authority set", - )?; - } - - Ok(()) - } -} - -/// Mutable data of light block importer. -struct LightImportData> { - authority_set: LightAuthoritySet, - consensus_changes: ConsensusChanges>, } -/// Latest authority set tracker. -#[derive(Debug, Encode, Decode)] -struct LightAuthoritySet { - set_id: u64, - authorities: Vec<(Ed25519AuthorityId, u64)>, -} impl LightAuthoritySet { /// Get a genesis set with given authorities. @@ -368,17 +185,212 @@ impl LightAuthoritySet { } /// Get latest authorities set. - pub fn authorities(&self) -> VoterSet { - self.authorities.iter().cloned().collect() + pub fn authorities(&self) -> Vec<(Ed25519AuthorityId, u64)> { + self.authorities.clone() } - /// Apply new authorities set. - pub fn update_authorities(&mut self, authorities: Vec<(Ed25519AuthorityId, u64)>) { - self.set_id += 1; + /// Set new authorities set. + pub fn update(&mut self, set_id: u64, authorities: Vec<(Ed25519AuthorityId, u64)>) { + self.set_id = set_id; std::mem::replace(&mut self.authorities, authorities); } } +/// Try to import new block. +fn do_import_block, RA>( + client: &Client, + data: &mut LightImportData, + mut block: ImportBlock, + new_authorities: Option>, +) -> Result + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + NumberFor: grandpa::BlockNumberOps, + DigestFor: Encode, + DigestItemFor: DigestItem, +{ + let hash = block.post_header().hash(); + let number = block.header.number().clone(); + + // we don't want to finalize on `inner.import_block` + let justification = block.justification.take(); + let enacts_consensus_change = new_authorities.is_some(); + let import_result = client.import_block(block, new_authorities); + + let import_result = { + match import_result { + Ok(ImportResult::Queued) => ImportResult::Queued, + Ok(r) => return Ok(r), + Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), + } + }; + + match justification { + Some(justification) => { + do_import_justification(client, data, hash, number, justification)?; + Ok(import_result) + }, + None if enacts_consensus_change => { + // remember that we need justification for this block + data.consensus_changes.note_change((number, hash)); + Ok(ImportResult::NeedsJustification) + }, + None => Ok(import_result), + } +} + +/// Try to import finality proof. +fn do_import_finality_proof, RA>( + client: &Client, + fetch_checker: &FetchChecker, + data: &mut LightImportData, + _hash: Block::Hash, + _number: NumberFor, + finality_proof: Vec, + verifier: &Verifier, +) -> Result<(), ConsensusError> + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + NumberFor: grandpa::BlockNumberOps, +{ + // TODO: ensure that the proof is for non-finalized block + let authority_set_id = data.authority_set.set_id(); + let authorities = data.authority_set.authorities(); + let finality_effects = ::finality_proof::check_finality_proof( + &*client.backend().blockchain(), + authority_set_id, + authorities, + |hash, header, authorities_proof| { + let request = RemoteCallRequest { + block: hash, + header, + method: "GrandpaApi_grandpa_authorities".into(), + call_data: vec![], + retry_count: None, + }; + + fetch_checker.check_execution_proof(&request, authorities_proof) + .and_then(|authorities| { + let authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &authorities[..]) + .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( + "failed to decode GRANDPA authorities set proof".into(), + )))?; + Ok(authorities.into_iter().collect()) + }) + }, + finality_proof, + ).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; + + // try to import all new headers + let block_origin = BlockOrigin::NetworkBroadcast; + for header_to_import in finality_effects.headers_to_import { + let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?; + assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify"); + do_import_block(client, data, block_to_import, new_authorities); + } + + // try to import latest justification + let finalized_block_hash = finality_effects.block; + let finalized_block_number = client.backend().blockchain() + .expect_block_number_from_id(&BlockId::Hash(finality_effects.block)) + .map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; + do_finalize_block(client, data, finalized_block_hash, finalized_block_number, finality_effects.justification.encode())?; + + // apply new authorities set + data.authority_set.update( + finality_effects.new_set_id, + finality_effects.new_authorities, + ); + + Ok(()) +} + +/// Try to import justification. +fn do_import_justification, RA>( + client: &Client, + data: &mut LightImportData, + hash: Block::Hash, + number: NumberFor, + justification: Justification, +) -> Result + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + NumberFor: grandpa::BlockNumberOps, +{ + // with justification, we have two cases + // + // optimistic: the same GRANDPA authorities set has generated intermediate justification + // => justification is verified using current authorities set + we could proceed further + // + // pessimistic scenario: the GRANDPA authorities set has changed + // => we need to fetch new authorities set (i.e. finality proof) from remote node + + // first, try to behave optimistically + let authority_set_id = data.authority_set.set_id(); + let justification = GrandpaJustification::::decode_and_verify( + &justification, + authority_set_id, + &data.authority_set.authorities().into_iter().collect(), + ); + + // BadJustification error means that justification has been successfully decoded, but + // it isn't valid within current authority set + let justification = match justification { + Err(ClientError(ClientErrorKind::BadJustification(_), _)) => return Ok(ImportResult::NeedsFinalityProof), + Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), + Ok(justification) => justification, + }; + + // finalize the block + do_finalize_block(client, data, hash, number, justification.encode()) +} + +/// Finalize the block. +fn do_finalize_block, RA>( + client: &Client, + data: &mut LightImportData, + hash: Block::Hash, + number: NumberFor, + justification: Justification, +) -> Result + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + NumberFor: grandpa::BlockNumberOps, +{ + // finalize the block + client.finalize_block(BlockId::Hash(hash), Some(justification), true).map_err(|e| { + warn!(target: "finality", "Error applying finality to block {:?}: {:?}", (hash, number), e); + ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())) + })?; + + // forget obsoleted consensus changes + let consensus_finalization_res = data.consensus_changes + .finalize((number, hash), |at_height| canonical_at_height(&client, (hash, number), at_height)); + match consensus_finalization_res { + Ok((true, _)) => require_insert_aux( + &client, + LIGHT_CONSENSUS_CHANGES_KEY, + &data.consensus_changes, + "consensus changes", + )?, + Ok(_) => (), + Err(error) => return Err(on_post_finalization_error(error, "consensus changes")), + } + + Ok(ImportResult::Queued) +} + +/// Insert into aux store. If failed, return error && show inconsistency warning. fn require_insert_aux, RA>( client: &Client, key: &[u8], @@ -393,14 +405,15 @@ fn require_insert_aux, RA>( let encoded = value.encode(); let update_res = Backend::insert_aux(backend, &[(key, &encoded[..])], &[]); if let Err(error) = update_res { - return on_post_finalization_error(error, value_type); + return Err(on_post_finalization_error(error, value_type)); } Ok(()) } -fn on_post_finalization_error(error: ClientError, value_type: &str) -> Result<(), ConsensusError> { +/// Display inconsistency warning. +fn on_post_finalization_error(error: ClientError, value_type: &str) -> ConsensusError { warn!(target: "finality", "Failed to write updated {} to disk. Bailing.", value_type); warn!(target: "finality", "Node is in a potentially inconsistent state."); - Err(ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string()))) + ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string())) } diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 1402b1af06db0..6391fa3ce7472 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -163,7 +163,7 @@ construct_service_factory! { import_queue( SlotDuration::get_or_compute(&*client)?, block_import.clone(), - Some(block_import.clone()), + None, Some(block_import), client, NothingExtra, From 41c4376288c96ae00dfc50b3a8906baac2b73a36 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Feb 2019 12:09:15 +0300 Subject: [PATCH 12/42] FinalityProofProvider --- core/finality-grandpa/src/finality_proof.rs | 41 +++++++++++++++++++-- core/finality-grandpa/src/lib.rs | 3 +- core/finality-grandpa/src/light_import.rs | 24 ++++++------ core/network/src/chain.rs | 6 +++ core/network/src/config.rs | 4 +- core/network/src/extra_requests.rs | 21 +++++++++-- core/network/src/lib.rs | 2 +- core/network/src/message.rs | 3 ++ core/network/src/protocol.rs | 35 +++++++++++++++++- core/network/src/service.rs | 1 + core/network/src/sync.rs | 38 +++++++++++++++++-- core/network/src/test/mod.rs | 1 + core/service/src/components.rs | 24 +++++++++++- core/service/src/lib.rs | 11 +++++- node/cli/src/service.rs | 5 ++- 15 files changed, 188 insertions(+), 31 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 48bbb1cee05d3..23f9ab3306b6e 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -29,9 +29,11 @@ //! The caller should track the `set_id`. The most straightforward way is to fetch finality //! proofs ONLY for blocks on the tip of the chain and track the latest known `set_id`. +use std::sync::Arc; + use client::{ - blockchain::Backend as BlockchainBackend, - error::{ErrorKind as ClientErrorKind, Result as ClientResult}, + backend::Backend, blockchain::Backend as BlockchainBackend, CallExecutor, Client, + error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}, }; use codec::{Encode, Decode}; use grandpa::BlockNumberOps; @@ -39,10 +41,41 @@ use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{ NumberFor, Block as BlockT, Header as HeaderT, One, }; -use substrate_primitives::{Ed25519AuthorityId, H256}; +use substrate_primitives::{Ed25519AuthorityId, H256, Blake2Hasher}; use GrandpaJustification; +/// Finality proof provider for serving network requests. +pub struct FinalityProofProvider, RA>(Arc>); + +impl, RA> FinalityProofProvider + where + B: Backend + Send + Sync + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + pub fn new(client: Arc>) -> Self { + FinalityProofProvider(client) + } +} + +impl, RA> network::FinalityProofProvider for FinalityProofProvider + where + B: Backend + Send + Sync + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + fn prove_finality(&self, last_finalized: Block::Hash, for_block: Block::Hash) -> Result>, ClientError> { + prove_finality( + &*self.0.backend().blockchain(), + |block| self.0.executor().call(block, "GrandpaApi_grandpa_authorities", &[]), + |block| self.0.execution_proof(block, "GrandpaApi_grandpa_authorities", &[]).map(|(_, proof)| proof), + last_finalized, + for_block, + ) + } +} + /// The effects of block finality. pub struct FinalityEffects { /// The (ordered) set of headers that could be imported. @@ -78,7 +111,7 @@ struct FinalityProofFragment { /// Proof of finality is the ordered set of finality fragments, where: /// - last fragment provides justification for the best possible block from the requested range; /// - all other fragments provide justifications for GRANDPA authorities set changes within requested range. -type FinalityProof = Vec>; +type FinalityProof = Vec>; /// Prepare proof-of-finality for the best possible block in the range: (begin; end]. /// diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 41220ab132a4a..a2313dc0230e5 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -128,6 +128,7 @@ mod service_integration; #[cfg(feature="service-integration")] pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService}; +pub use finality_proof::FinalityProofProvider; pub use light_import::light_block_import; #[cfg(test)] @@ -1444,8 +1445,6 @@ pub fn block_import, RA, PRA>( from genesis on what appears to be first startup."); // no authority set on disk: fetch authorities from genesis state. - // if genesis state is not available, we may be a light client, but these - // are unsupported for following GRANDPA directly. let genesis_authorities = api.runtime_api() .grandpa_authorities(&BlockId::number(Zero::zero()))?; diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 9dde71ef6f2e6..f110f9c5818c1 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -18,14 +18,18 @@ use std::sync::Arc; use parking_lot::RwLock; use client::{ - CallExecutor, Client, backend::Backend, + CallExecutor, Client, + backend::Backend, + blockchain::HeaderBackend, error::Error as ClientError, error::ErrorKind as ClientErrorKind, light::fetcher::{FetchChecker, RemoteCallRequest}, }; -use client::blockchain::HeaderBackend; use codec::{Encode, Decode}; -use consensus_common::{import_queue::Verifier, BlockOrigin, BlockImport, JustificationImport, FinalityProofImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult}; -use grandpa::VoterSet; +use consensus_common::{ + import_queue::Verifier, + BlockOrigin, BlockImport, FinalityProofImport, ImportBlock, ImportResult, + Error as ConsensusError, ErrorKind as ConsensusErrorKind +}; use runtime_primitives::Justification; use runtime_primitives::traits::{ NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, @@ -61,9 +65,7 @@ pub fn light_block_import, RA, PRA>( info!(target: "afg", "Loading GRANDPA authorities \ from genesis on what appears to be first startup."); - // no authority set on disk: fetch authorities from genesis state. - // if genesis state is not available, we may be a light client, but these - // are unsupported for following GRANDPA directly. + // no authority set on disk: fetch authorities from genesis state let genesis_authorities = api.runtime_api().grandpa_authorities(&BlockId::number(Zero::zero()))?; let authority_set = LightAuthoritySet::genesis(genesis_authorities); @@ -93,9 +95,9 @@ pub fn light_block_import, RA, PRA>( /// A light block-import handler for GRANDPA. /// -/// It is responsible for +/// It is responsible for: /// - checking GRANDPA justifications; -/// - requiring GRANDPA justifications for blocks that are enacting consensus changes; +/// - fetching finality proofs for blocks that are enacting consensus changes. pub struct GrandpaLightBlockImport, RA> { client: Arc>, fetch_checker: Arc>, @@ -126,7 +128,7 @@ impl, RA> BlockImport { type Error = ConsensusError; - fn import_block(&self, mut block: ImportBlock, new_authorities: Option>) + fn import_block(&self, block: ImportBlock, new_authorities: Option>) -> Result { do_import_block(&*self.client, &mut *self.data.write(), block, new_authorities) @@ -292,7 +294,7 @@ fn do_import_finality_proof, RA>( for header_to_import in finality_effects.headers_to_import { let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?; assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify"); - do_import_block(client, data, block_to_import, new_authorities); + do_import_block(client, data, block_to_import, new_authorities)?; } // try to import latest justification diff --git a/core/network/src/chain.rs b/core/network/src/chain.rs index 40edbfbadb9fe..19975f8832748 100644 --- a/core/network/src/chain.rs +++ b/core/network/src/chain.rs @@ -70,6 +70,12 @@ pub trait Client: Send + Sync { ) -> Result, Error>; } +/// +pub trait FinalityProofProvider: Send + Sync { + /// + fn prove_finality(&self, last_finalized: Block::Hash, for_block: Block::Hash) -> Result>, Error>; +} + impl Client for SubstrateClient where B: client::backend::Backend + Send + Sync + 'static, E: CallExecutor + Send + Sync + 'static, diff --git a/core/network/src/config.rs b/core/network/src/config.rs index fa447f19f1e51..a626149d069f1 100644 --- a/core/network/src/config.rs +++ b/core/network/src/config.rs @@ -18,7 +18,7 @@ pub use network_libp2p::{NonReservedPeerMode, NetworkConfiguration, Secret}; -use chain::Client; +use chain::{Client, FinalityProofProvider}; use codec; use on_demand::OnDemandService; use runtime_primitives::traits::{Block as BlockT}; @@ -33,6 +33,8 @@ pub struct Params { pub network_config: NetworkConfiguration, /// Substrate relay chain access point. pub chain: Arc>, + /// Finality proof provider. + pub finality_proof_provider: Option>>, /// On-demand service reference. pub on_demand: Option>>, /// Transaction pool. diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index db96a60e62549..dc4ffeea15cbf 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -35,7 +35,7 @@ trait ExtraRequestsEssence { type Response; /// Prepare network message corresponding to the request. - fn into_network_request(&self, request: ExtraRequest) -> Message; + fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message; /// Accept response. fn accept_response(&self, request: ExtraRequest, import_queue: &ImportQueue, response: Self::Response) -> ExtraResponseKind; /// @@ -72,6 +72,10 @@ impl ExtraRequestsAggregator { self.justifications.on_response(who, justification, protocol, import_queue); } + pub fn on_finality_proof(&mut self, who: NodeIndex, finality_proof: Option>, protocol: &mut Context, import_queue: &ImportQueue) { + self.finality_proofs.on_response(who, finality_proof, protocol, import_queue); + } + pub fn dispatch(&mut self, peers: &mut HashMap>, protocol: &mut Context) { self.justifications.dispatch(peers, protocol); self.finality_proofs.dispatch(peers, protocol); @@ -135,6 +139,14 @@ impl> ExtraRequests { let mut last_peer = available_peers.back().map(|p| p.0); let mut unhandled_requests = VecDeque::new(); + let last_finalzied_hash = match protocol.client().info() { + Ok(info) => info.chain.finalized_hash, + Err(e) => { + debug!(target:"sync", "Cannot dispatch extra requests: error {:?} when reading blockchain", e); + return; + }, + }; + loop { let (peer, peer_best_number) = match available_peers.pop_front() { Some(p) => p, @@ -185,7 +197,7 @@ impl> ExtraRequests { .state = self.essence.peer_downloading_state(request.0); trace!(target: "sync", "Requesting extra for block #{} from {}", request.0, peer); - let request = self.essence.into_network_request(request); + let request = self.essence.into_network_request(request, last_finalzied_hash); protocol.send_message(peer, request); } @@ -267,7 +279,7 @@ struct JustificationsRequestsEssence; impl ExtraRequestsEssence for JustificationsRequestsEssence { type Response = Option; - fn into_network_request(&self, request: ExtraRequest) -> Message { + fn into_network_request(&self, request: ExtraRequest, _last_finalzied_hash: B::Hash) -> Message { GenericMessage::BlockRequest(message::generic::BlockRequest { id: 0, fields: message::BlockAttributes::JUSTIFICATION, @@ -300,9 +312,10 @@ struct FinalityProofRequestsEssence; impl ExtraRequestsEssence for FinalityProofRequestsEssence { type Response = Option>; - fn into_network_request(&self, request: ExtraRequest) -> Message { + fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message { GenericMessage::FinalityProofRequest(message::generic::FinalityProofRequest { block: request.0, + last_finalized: last_finalzied_hash, }) } diff --git a/core/network/src/lib.rs b/core/network/src/lib.rs index 6c04bd52036bc..87203e5712ca9 100644 --- a/core/network/src/lib.rs +++ b/core/network/src/lib.rs @@ -64,7 +64,7 @@ pub mod specialization; #[cfg(any(test, feature = "test-helpers"))] pub mod test; -pub use chain::Client as ClientHandle; +pub use chain::{Client as ClientHandle, FinalityProofProvider}; pub use service::{Service, FetchFuture, TransactionPool, ManageNetwork, SyncProvider, ExHashT}; pub use protocol::{ProtocolStatus, PeerInfo, Context}; pub use sync::{Status as SyncStatus, SyncState}; diff --git a/core/network/src/message.rs b/core/network/src/message.rs index c1ebe8ecc86b0..cc3240815d7af 100644 --- a/core/network/src/message.rs +++ b/core/network/src/message.rs @@ -22,6 +22,7 @@ pub use self::generic::{ BlockAnnounce, RemoteCallRequest, RemoteReadRequest, RemoteHeaderRequest, RemoteHeaderResponse, RemoteChangesRequest, RemoteChangesResponse, + FinalityProofRequest, FinalityProofResponse, FromBlock }; @@ -331,6 +332,8 @@ pub mod generic { pub struct FinalityProofRequest { /// Hash of the block to request proof for. pub block: H, + /// Hash of the last known finalized block. + pub last_finalized: H, } #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] diff --git a/core/network/src/protocol.rs b/core/network/src/protocol.rs index 2506dcd5462ab..74fa6dc4f59de 100644 --- a/core/network/src/protocol.rs +++ b/core/network/src/protocol.rs @@ -34,7 +34,7 @@ use specialization::NetworkSpecialization; use sync::{ChainSync, Status as SyncStatus, SyncState}; use service::{TransactionPool, ExHashT}; use config::{ProtocolConfig, Roles}; -use chain::Client; +use chain::{Client, FinalityProofProvider}; use client::light::fetcher::ChangesProof; use on_demand::OnDemandService; use io::SyncIo; @@ -202,6 +202,7 @@ pub(crate) struct ContextData { // All connected peers peers: RwLock>>, pub chain: Arc>, + pub finality_proof_provider: Option>>, } impl, H: ExHashT> Protocol { @@ -209,6 +210,7 @@ impl, H: ExHashT> Protocol { pub fn new>( config: ProtocolConfig, chain: Arc>, + finality_proof_provider: Option>>, import_queue: Arc, on_demand: Option>>, transaction_pool: Arc>, @@ -223,6 +225,7 @@ impl, H: ExHashT> Protocol { context_data: ContextData { peers: RwLock::new(HashMap::new()), chain, + finality_proof_provider, }, on_demand, genesis_hash: info.chain.genesis_hash, @@ -366,6 +369,8 @@ impl, H: ExHashT> Protocol { GenericMessage::RemoteHeaderResponse(response) => self.on_remote_header_response(io, who, response), GenericMessage::RemoteChangesRequest(request) => self.on_remote_changes_request(io, who, request), GenericMessage::RemoteChangesResponse(response) => self.on_remote_changes_response(io, who, response), + GenericMessage::FinalityProofRequest(request) => self.on_finality_proof_request(io, who, request), + GenericMessage::FinalityProofResponse(response) => self.on_finality_proof_response(io, who, response), GenericMessage::Consensus(topic, msg, broadcast) => { self.consensus_gossip.write().on_incoming(&mut ProtocolContext::new(&self.context_data, io), who, topic, msg, broadcast); }, @@ -886,6 +891,34 @@ impl, H: ExHashT> Protocol { self.on_demand.as_ref().map(|s| s.on_remote_changes_response(io, who, response)); } + fn on_finality_proof_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::FinalityProofRequest) { + trace!(target: "sync", "Finality proof request from {} for {}", who, request.block); + let finality_proof = self.context_data.finality_proof_provider.as_ref() + .ok_or_else(|| String::from("Finality provider is not configured")) + .and_then(|provider| provider.prove_finality(request.last_finalized, request.block) + .map_err(|e| e.to_string())); + let finality_proof = match finality_proof { + Ok(finality_proof) => finality_proof, + Err(error) => { + trace!(target: "sync", "Finality proof request from {} for {} failed with: {}", + who, request.block, error); + None + }, + }; + self.send_message(io, who, GenericMessage::FinalityProofResponse(message::FinalityProofResponse { + block: request.block, + proof: finality_proof, + })); + } + + fn on_finality_proof_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::FinalityProofResponse) { + trace!(target: "sync", "Finality proof response from {} for {}", who, response.block); + self.sync.write().on_block_finality_proof_data( + &mut ProtocolContext::new(&self.context_data, io), + who, + response, + ); + } /// Execute a closure with access to a network context and specialization. pub fn with_spec(&self, io: &mut SyncIo, f: F) -> U diff --git a/core/network/src/service.rs b/core/network/src/service.rs index bd437281d5f8c..5d4d75a9f6226 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -150,6 +150,7 @@ impl, H: ExHashT> Service ChainSync { self.maintain_sync(protocol); } + /// Handle new finality proof data. + pub(crate) fn on_block_finality_proof_data( + &mut self, + protocol: &mut Context, + who: NodeIndex, + response: message::FinalityProofResponse, + ) { + if let Some(ref mut peer) = self.peers.get_mut(&who) { + if let PeerSyncState::DownloadingFinalityProof(hash) = peer.state { + peer.state = PeerSyncState::Available; + + if hash != response.block { + let msg = format!( + "Invalid block finality data provided: requested: {:?} got: {:?}", + hash, + response.block, + ); + + protocol.report_peer(who, Severity::Bad(&msg)); + return; + } + + self.extra_requests.on_finality_proof( + who, + response.proof, + protocol, + &*self.import_queue, + ); + } + } + + self.maintain_sync(protocol); + } + /// Maintain the sync process (download new blocks, fetch justifications). pub fn maintain_sync(&mut self, protocol: &mut Context) { let peers: Vec = self.peers.keys().map(|p| *p).collect(); @@ -379,8 +413,6 @@ impl ChainSync { /// Queues a new justification request and tries to dispatch all pending requests. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { self.extra_requests.request_justification(&(*hash, number), &mut self.peers, protocol); -/* self.justifications.queue_request(&(*hash, number)); - self.justifications.dispatch(&mut self.peers, protocol);*/ } /// Request a finality_proof for the given block. @@ -388,8 +420,6 @@ impl ChainSync { /// Queues a new finality proof request and tries to dispatch all pending requests. pub fn request_finality_proof(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { self.extra_requests.request_finality_proof(&(*hash, number), &mut self.peers, protocol); - /*self.finality_proofs.queue_request(&(*hash, number)); - self.finality_proofs.dispatch(&mut self.peers, protocol);*/ } /// Notify about successful import of the given block. diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index b00e24cbc37c4..e9680b1cd7c0b 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -579,6 +579,7 @@ pub trait TestNetFactory: Sized { let sync = Protocol::new( config.clone(), client.clone(), + None, import_queue.clone(), None, tx_pool, diff --git a/core/service/src/components.rs b/core/service/src/components.rs index c1a9c63fe9e08..ad741924eeb4f 100644 --- a/core/service/src/components.rs +++ b/core/service/src/components.rs @@ -24,7 +24,7 @@ use client_db; use client::{self, Client, runtime_api::{Metadata, TaggedTransactionQueue}}; use {error, Service, maybe_start_server}; use consensus_common::import_queue::ImportQueue; -use network::{self, OnDemand}; +use network::{self, OnDemand, FinalityProofProvider}; use substrate_executor::{NativeExecutor, NativeExecutionDispatch}; use transaction_pool::txpool::{self, Options as TransactionPoolOptions, Pool as TransactionPool}; use runtime_primitives::{ @@ -294,6 +294,11 @@ pub trait ServiceFactory: 'static + Sized { fn build_network_protocol(config: &FactoryFullConfiguration) -> Result; + /// Build finality proof provider for serving network requests on full node. + fn build_finality_proof_provider( + client: Arc> + ) -> Result>>, error::Error>; + /// Build full service. fn new_full(config: FactoryFullConfiguration, executor: TaskExecutor) -> Result; @@ -377,6 +382,11 @@ pub trait Components: Sized + 'static { config: &mut FactoryFullConfiguration, client: Arc> ) -> Result; + + /// Finality proof provider for serving network requests. + fn build_finality_proof_provider( + client: Arc> + ) -> Result::Block>>>, error::Error>; } /// A struct that implement `Components` for the full client. @@ -458,6 +468,12 @@ impl Components for FullComponents { ) -> Result { Factory::build_full_import_queue(config, client) } + + fn build_finality_proof_provider( + client: Arc> + ) -> Result::Block>>>, error::Error> { + Factory::build_finality_proof_provider(client) + } } /// A struct that implement `Components` for the light client. @@ -534,6 +550,12 @@ impl Components for LightComponents { ) -> Result { Factory::build_light_import_queue(config, client) } + + fn build_finality_proof_provider( + _client: Arc> + ) -> Result::Block>>>, error::Error> { + Ok(None) + } } #[cfg(test)] diff --git a/core/service/src/lib.rs b/core/service/src/lib.rs index abffc2b74c1f3..421ea588d081c 100644 --- a/core/service/src/lib.rs +++ b/core/service/src/lib.rs @@ -91,7 +91,7 @@ pub use components::{ServiceFactory, FullBackend, FullExecutor, LightBackend, }; use components::{StartRPC, MaintainTransactionPool}; #[doc(hidden)] -pub use network::OnDemand; +pub use network::{FinalityProofProvider, OnDemand}; const DEFAULT_PROTOCOL_ID: &'static str = "sup"; @@ -155,6 +155,7 @@ impl Service { let (client, on_demand) = Components::build_client(&config, executor)?; let import_queue = Arc::new(Components::build_import_queue(&mut config, client.clone())?); + let finality_proof_provider = Components::build_finality_proof_provider(client.clone())?; let best_header = client.best_block_header()?; let version = config.full_version(); @@ -175,6 +176,7 @@ impl Service { config: network::config::ProtocolConfig { roles: config.roles }, network_config: config.network.clone(), chain: client.clone(), + finality_proof_provider, on_demand: on_demand.as_ref().map(|d| d.clone() as _), transaction_pool: transaction_pool_adapter.clone() as _, specialization: network_protocol, @@ -538,6 +540,7 @@ macro_rules! construct_service_factory { { $( $full_import_queue_init:tt )* }, LightImportQueue = $light_import_queue:ty { $( $light_import_queue_init:tt )* }, + FinalityProofProvider = { $( $finality_proof_provider_init:tt )* }, } ) => { $( #[$attr] )* @@ -594,6 +597,12 @@ macro_rules! construct_service_factory { ( $( $light_import_queue_init )* ) (config, client) } + fn build_finality_proof_provider( + client: Arc<$crate::FullClient> + ) -> Result>>, $crate::Error> { + ( $( $finality_proof_provider_init )* ) (client) + } + fn new_light( config: $crate::FactoryFullConfiguration, executor: $crate::TaskExecutor diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 6391fa3ce7472..a276ebda64ef7 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -23,7 +23,7 @@ use std::time::Duration; use client; use consensus::{import_queue, start_aura, AuraImportQueue, SlotDuration, NothingExtra}; -use grandpa; +use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider}; use node_executor; use primitives::ed25519::Pair; use node_primitives::Block; @@ -170,6 +170,9 @@ construct_service_factory! { config.custom.inherent_data_providers.clone(), ).map_err(Into::into) }}, + FinalityProofProvider = { |client: Arc>| { + Ok(Some(Arc::new(GrandpaFinalityProofProvider::new(client)) as _)) + }}, } } From 41a1dba302176cb0d0bba9230a1ed0058f24952b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Feb 2019 12:22:55 +0300 Subject: [PATCH 13/42] fixed authorities cache test --- core/client/db/src/light.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 64085975edcdf..1a3f759f67e80 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -770,7 +770,7 @@ pub(crate) mod tests { let (hash2, hash6) = { // first few blocks are instantly finalized - // B0(None) -> B1(None) -> B2(1) -> B3(1) -> B4(1, 2) -> B5(1, 2) -> B6(None) + // B0(None) -> B1(None) -> B2(1) -> B3(1) -> B4(1, 2) -> B5(1, 2) -> B6(1, 2) let checks = vec![ (0, None), (1, None), @@ -778,8 +778,7 @@ pub(crate) mod tests { (3, Some(vec![[1u8; 32].into()])), (4, Some(vec![[1u8; 32].into(), [2u8; 32].into()])), (5, Some(vec![[1u8; 32].into(), [2u8; 32].into()])), - (6, None), - (7, None), // block will work for 'future' block too + (6, Some(vec![[1u8; 32].into(), [2u8; 32].into()])), ]; let hash0 = insert_final_block(&db, None, || default_header(&Default::default(), 0)); @@ -795,7 +794,7 @@ pub(crate) mod tests { let hash5 = insert_final_block(&db, Some(vec![[1u8; 32].into(), [2u8; 32].into()]), || default_header(&hash4, 5)); run_checks(&db, 5, &checks); let hash6 = insert_final_block(&db, None, || default_header(&hash5, 6)); - run_checks(&db, 7, &checks); + run_checks(&db, 6, &checks); (hash2, hash6) }; @@ -818,32 +817,32 @@ pub(crate) mod tests { // \> B6_1_2(6) -> B6_1_3(7) let hash7 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash6, 7)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); let hash8 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash7, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); let hash6_1 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6, 7)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); let hash6_1_1 = insert_non_best_block(&db, Some(vec![[5u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); let hash6_1_2 = insert_non_best_block(&db, Some(vec![[6u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_2)), Some(vec![[6u8; 32].into()])); let hash6_2 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); @@ -857,7 +856,7 @@ pub(crate) mod tests { { // finalize block hash6_1 db.finalize_header(BlockId::Hash(hash6_1)).unwrap(); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); @@ -866,7 +865,7 @@ pub(crate) mod tests { assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_2)), Some(vec![[4u8; 32].into()])); // finalize block hash6_2 db.finalize_header(BlockId::Hash(hash6_2)).unwrap(); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); From 2c02eff95556972c68ee4df67f3fde3371141fbd Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Feb 2019 15:57:37 +0300 Subject: [PATCH 14/42] restored finality proof tests --- core/finality-grandpa/src/finality_proof.rs | 357 +++++++++++++------- 1 file changed, 239 insertions(+), 118 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 23f9ab3306b6e..e93d7b9b16903 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -77,6 +77,7 @@ impl, RA> network::FinalityProofProvider f } /// The effects of block finality. +#[derive(Debug, PartialEq)] pub struct FinalityEffects { /// The (ordered) set of headers that could be imported. pub headers_to_import: Vec
, @@ -295,8 +296,8 @@ fn do_check_finality_proof, B, J, CheckAuthoritiesProof for (proof_fragment_index, proof_fragment) in proof.into_iter().enumerate() { // check that proof is non-redundant. The proof still can be valid, but // we do not want peer to spam us with redundant data - if proof_fragment_index == last_fragment_index { - let has_unknown_headers = proof_fragment.unknown_headers.is_empty(); + if proof_fragment_index != last_fragment_index { + let has_unknown_headers = !proof_fragment.unknown_headers.is_empty(); let has_new_authorities = proof_fragment.authorities_proof.is_some(); if has_unknown_headers || !has_new_authorities { return Err(ClientErrorKind::BadJustification("redundant proof of finality".into()).into()); @@ -341,7 +342,9 @@ fn check_finality_proof_fragment, B, J, CheckAuthoritie if let Some(new_authorities_proof) = proof_fragment.authorities_proof { // it is safe to query header here, because its non-finality proves that it can't be pruned let header = blockchain.expect_header(BlockId::Hash(proof_fragment.block))?; - current_authorities = check_authorities_proof(proof_fragment.block, header, new_authorities_proof)?; + let parent_hash = *header.parent_hash(); + let parent_header = blockchain.expect_header(BlockId::Hash(parent_hash))?; + current_authorities = check_authorities_proof(parent_hash, parent_header, new_authorities_proof)?; current_set_id = current_set_id + 1; } @@ -378,9 +381,6 @@ impl AuthoritiesOrEffects { /// Justification used to prove block finality. trait ProvableJustification: Encode + Decode { - /// Get target block of this justification. - fn target_block(&self) -> (Header::Number, Header::Hash); - /// Verify justification with respect to authorities set and authorities set id. fn verify(&self, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()>; } @@ -389,37 +389,25 @@ impl> ProvableJustification for GrandpaJ where NumberFor: BlockNumberOps, { - fn target_block(&self) -> (NumberFor, Block::Hash) { - (self.commit.target_number, self.commit.target_hash) - } - fn verify(&self, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()> { GrandpaJustification::verify(self, set_id, &authorities.iter().cloned().collect()) } } -/*#[cfg(test)] +#[cfg(test)] mod tests { use test_client::runtime::{Block, Header}; - use test_client::client::backend::NewBlockState; + use test_client::client::{backend::NewBlockState}; use test_client::client::in_mem::Blockchain as InMemoryBlockchain; use super::*; type FinalityProof = super::FinalityProof>; - #[derive(Encode, Decode)] - struct ValidFinalityProof(Vec); + #[derive(Debug, PartialEq, Encode, Decode)] + struct ValidJustification(Vec); - impl ProvableJustification
for ValidFinalityProof { - fn target_block(&self) -> (u64, H256) { (3, header(3).hash()) } - - fn verify(&self, set_id: u64, authorities: &VoterSet) -> ClientResult<()> { - assert_eq!(set_id, 1); - assert_eq!(authorities, &vec![ - (Ed25519AuthorityId([1u8; 32]), 1), - (Ed25519AuthorityId([2u8; 32]), 2), - (Ed25519AuthorityId([3u8; 32]), 3), - ].into_iter().collect()); + impl ProvableJustification
for ValidJustification { + fn verify(&self, _set_id: u64, _authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()> { Ok(()) } } @@ -436,6 +424,10 @@ mod tests { Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(1), header(number - 1).hash(), Default::default()) } + fn second_side_header(number: u64) -> Header { + Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(1), side_header(number - 1).hash(), Default::default()) + } + fn test_blockchain() -> InMemoryBlockchain { let blockchain = InMemoryBlockchain::::new(); blockchain.insert(header(0).hash(), header(0), Some(vec![0]), None, NewBlockState::Final).unwrap(); @@ -446,13 +438,36 @@ mod tests { } #[test] - fn finality_proof_is_not_generated_for_non_final_block() { + fn finality_prove_fails_with_invalid_range() { + let blockchain = test_blockchain(); + + // their last finalized is: 2 + // they request for proof-of-finality of: 2 + // => range is invalid + prove_finality( + &blockchain, + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + header(2).hash(), + header(2).hash(), + ).unwrap_err(); + } + + #[test] + fn finality_proof_is_none_if_no_more_last_finalized_blocks() { let blockchain = test_blockchain(); blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap(); - // when asking for finality of block 4, None is returned - let proof_of_4 = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(4).hash()) - .unwrap(); + // our last finalized is: 3 + // their last finalized is: 3 + // => we can't provide any additional justifications + let proof_of_4 = prove_finality( + &blockchain, + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + header(3).hash(), + header(4).hash(), + ).unwrap(); assert_eq!(proof_of_4, None); } @@ -461,129 +476,235 @@ mod tests { let blockchain = test_blockchain(); blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap(); blockchain.insert(side_header(4).hash(), side_header(4), None, None, NewBlockState::Best).unwrap(); + blockchain.insert(second_side_header(5).hash(), second_side_header(5), None, None, NewBlockState::Best).unwrap(); blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); - // when asking for finality of side-block 42, None is returned - let proof_of_side_4_fails = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), H256::from_low_u64_be(42)).is_err(); - assert_eq!(proof_of_side_4_fails, true); + // chain is 1 -> 2 -> 3 -> 4 -> 5 + // \> 4' -> 5' + // and the best finalized is 5 + // => when requesting for (4'; 5'], error is returned + prove_finality( + &blockchain, + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + side_header(4).hash(), + second_side_header(5).hash(), + ).unwrap_err(); } #[test] - fn finality_proof_fails_if_no_justification_known() { + fn finality_proof_is_none_if_no_justification_known() { let blockchain = test_blockchain(); blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Final).unwrap(); - // when asking for finality of block 4, search for justification failing - let proof_of_4_fails = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), H256::from_low_u64_be(42)).is_err(); - assert_eq!(proof_of_4_fails, true); + // block 4 is finalized without justification + // => we can't prove finality + let proof_of_4 = prove_finality( + &blockchain, + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), + |_| unreachable!("authorities didn't change => ProveAuthorities won't be called"), + header(3).hash(), + header(4).hash(), + ).unwrap(); + assert_eq!(proof_of_4, None); } #[test] - fn prove_finality_is_generated() { + fn finality_proof_works_without_authorities_change() { let blockchain = test_blockchain(); + blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); - // when asking for finality of block 2, justification of 3 is returned - let proof_of_2: FinalityProof = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(2).hash()) - .unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap(); - assert_eq!(proof_of_2, FinalityProof { - finalization_path: vec![header(2), header(3)], - justification: vec![3], - authorities_proof: vec![vec![42]], - }); + // blocks 4 && 5 are finalized with justification + // => since authorities are the same, we only need justification for 5 + let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( + &blockchain, + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), + |_| unreachable!("should return before calling ProveAuthorities"), + header(3).hash(), + header(5).hash(), + ).unwrap().unwrap()[..]).unwrap(); + assert_eq!(proof_of_5, vec![FinalityProofFragment { + block: header(5).hash(), + justification: vec![5], + unknown_headers: Vec::new(), + authorities_proof: None, + }]); + } - // when asking for finality of block 3, justification of 3 is returned - let proof_of_3: FinalityProof = prove_finality(&blockchain, |_, _, _| Ok(vec![vec![42]]), header(3).hash()) - .unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap(); - assert_eq!(proof_of_3, FinalityProof { - finalization_path: vec![header(3)], - justification: vec![3], - authorities_proof: vec![vec![42]], - }); + #[test] + fn finality_proof_finalized_earlier_block_if_no_justification_for_target_is_known() { + let blockchain = test_blockchain(); + blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(5).hash(), header(5), None, None, NewBlockState::Final).unwrap(); + + // block 4 is finalized with justification + we request for finality of 5 + // => we can't prove finality of 5, but providing finality for 4 is still useful for requester + let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( + &blockchain, + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), + |_| unreachable!("should return before calling ProveAuthorities"), + header(3).hash(), + header(5).hash(), + ).unwrap().unwrap()[..]).unwrap(); + assert_eq!(proof_of_5, vec![FinalityProofFragment { + block: header(4).hash(), + justification: vec![4], + unknown_headers: Vec::new(), + authorities_proof: None, + }]); } #[test] - fn finality_proof_check_fails_when_block_is_not_included() { - let mut proof_of_2: FinalityProof = prove_finality( - &test_blockchain(), - |_, _, _| Ok(vec![vec![42]]), - header(2).hash(), - ).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap(); - proof_of_2.finalization_path.remove(0); - - // block for which we're trying to request finality proof is missing from finalization_path - assert_eq!(do_check_finality_proof::( - |_| Ok(Vec::::new().encode()), - header(1), - (2, header(2).hash()), + fn finality_proof_works_with_authorities_change() { + let blockchain = test_blockchain(); + blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(6).hash(), header(6), None, None, NewBlockState::Final).unwrap(); + blockchain.insert(header(7).hash(), header(7), Some(vec![7]), None, NewBlockState::Final).unwrap(); + + // when querying for finality of 6, we assume that the #6 is the last block known to the requester + // => since we only have justification for #7, we provide #7 + let proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality( + &blockchain, + |block_id| match *block_id { + BlockId::Hash(h) if h == header(3).hash() => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)].encode()), + BlockId::Number(3) => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)].encode()), + BlockId::Number(4) => Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)].encode()), + BlockId::Number(6) => Ok(vec![(Ed25519AuthorityId([6u8; 32]), 1u64)].encode()), + _ => unreachable!("no other authorities should be fetched: {:?}", block_id), + }, + |block_id| match *block_id { + BlockId::Number(4) => Ok(vec![vec![40]]), + BlockId::Number(6) => Ok(vec![vec![60]]), + _ => unreachable!("no other authorities should be proved: {:?}", block_id), + }, + header(3).hash(), + header(6).hash(), + ).unwrap().unwrap()[..]).unwrap(); + // initial authorities set (which start acting from #4) is [3; 32] + assert_eq!(proof_of_6, vec![ + // new authorities set starts acting from #5 => we do not provide fragment for #4 + // first fragment provides justification for #5 && authorities set that starts acting from #5 + FinalityProofFragment { + block: header(5).hash(), + justification: vec![5], + unknown_headers: Vec::new(), + authorities_proof: Some(vec![vec![40]]), + }, + // last fragment provides justification for #7 && unknown#7 + FinalityProofFragment { + block: header(7).hash(), + justification: vec![7], + unknown_headers: vec![header(7)], + authorities_proof: Some(vec![vec![60]]), + }, + ]); + } + + #[test] + fn finality_proof_check_fails_when_proof_decode_fails() { + let blockchain = test_blockchain(); + + // when we can't decode proof from Vec + do_check_finality_proof::<_, _, ValidJustification, _>( + &blockchain, 1, - proof_of_2.encode(), - ).is_err(), true); + vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], + |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + vec![42], + ).unwrap_err(); } #[test] - fn finality_proof_check_fails_when_justified_block_is_not_included() { - let mut proof_of_2: FinalityProof = prove_finality( - &test_blockchain(), - |_, _, _| Ok(vec![vec![42]]), - header(2).hash(), - ).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap(); - proof_of_2.finalization_path.remove(1); - - // justified block is missing from finalization_path - assert_eq!(do_check_finality_proof::( - |_| Ok(Vec::::new().encode()), - header(1), - (2, header(2).hash()), + fn finality_proof_check_fails_when_proof_is_empty() { + let blockchain = test_blockchain(); + + // when decoded proof has zero length + do_check_finality_proof::<_, _, ValidJustification, _>( + &blockchain, 1, - proof_of_2.encode(), - ).is_err(), true); + vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], + |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + Vec::::new().encode(), + ).unwrap_err(); } #[test] - fn finality_proof_check_fails_when_justification_verification_fails() { - #[derive(Encode, Decode)] - struct InvalidFinalityProof(Vec); + fn finality_proof_check_fails_when_intemediate_fragment_has_unknown_headers() { + let blockchain = test_blockchain(); - impl ProvableJustification
for InvalidFinalityProof { - fn target_block(&self) -> (u64, H256) { (3, header(3).hash()) } + // when intermediate (#0) fragment has non-empty unknown headers + do_check_finality_proof::<_, _, ValidJustification, _>( + &blockchain, + 1, + vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], + |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + vec![FinalityProofFragment { + block: header(4).hash(), + justification: ValidJustification(vec![7]), + unknown_headers: vec![header(4)], + authorities_proof: Some(vec![vec![42]]), + }, FinalityProofFragment { + block: header(5).hash(), + justification: ValidJustification(vec![8]), + unknown_headers: vec![header(5)], + authorities_proof: None, + }].encode(), + ).unwrap_err(); + } - fn verify(&self, _set_id: u64, _authorities: &VoterSet) -> ClientResult<()> { - Err(ClientErrorKind::Backend("test error".into()).into()) - } - } + #[test] + fn finality_proof_check_fails_when_intemediate_fragment_has_no_authorities_proof() { + let blockchain = test_blockchain(); - let mut proof_of_2: FinalityProof = prove_finality( - &test_blockchain(), - |_, _, _| Ok(vec![vec![42]]), - header(2).hash(), - ).unwrap().and_then(|p| Decode::decode(&mut &p[..])).unwrap(); - proof_of_2.finalization_path.remove(1); - - // justification is not valid - assert_eq!(do_check_finality_proof::( - |_| Ok(Vec::::new().encode()), - header(1), - (2, header(2).hash()), + // when intermediate (#0) fragment has empty authorities proof + do_check_finality_proof::<_, _, ValidJustification, _>( + &blockchain, 1, - proof_of_2.encode(), - ).is_err(), true); + vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], + |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + vec![FinalityProofFragment { + block: header(4).hash(), + justification: ValidJustification(vec![7]), + unknown_headers: Vec::new(), + authorities_proof: None, + }, FinalityProofFragment { + block: header(5).hash(), + justification: ValidJustification(vec![8]), + unknown_headers: vec![header(5)], + authorities_proof: None, + }].encode(), + ).unwrap_err(); } #[test] fn finality_proof_check_works() { - let proof_of_2 = prove_finality(&test_blockchain(), |_, _, _| Ok(vec![vec![42]]), header(2).hash()) - .unwrap().unwrap(); - assert_eq!(do_check_finality_proof::( - |_| Ok(vec![ - (Ed25519AuthorityId([1u8; 32]), 1u64), - (Ed25519AuthorityId([2u8; 32]), 2u64), - (Ed25519AuthorityId([3u8; 32]), 3u64), - ].encode()), - header(1), - (2, header(2).hash()), + let blockchain = test_blockchain(); + + let effects = do_check_finality_proof::<_, _, ValidJustification, _>( + &blockchain, 1, - proof_of_2, - ).unwrap(), vec![header(2), header(3)]); + vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], + |_, _, _| Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)]), + vec![FinalityProofFragment { + block: header(2).hash(), + justification: ValidJustification(vec![7]), + unknown_headers: Vec::new(), + authorities_proof: Some(vec![vec![42]]), + }, FinalityProofFragment { + block: header(4).hash(), + justification: ValidJustification(vec![8]), + unknown_headers: vec![header(4)], + authorities_proof: None, + }].encode(), + ).unwrap(); + assert_eq!(effects, FinalityEffects { + headers_to_import: vec![header(4)], + block: header(4).hash(), + justification: ValidJustification(vec![8]), + new_set_id: 2, + new_authorities: vec![(Ed25519AuthorityId([4u8; 32]), 1u64)], + }); } } -*/ \ No newline at end of file From 527596d66f70c767b9fe490921ebb5b294ec6398 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Feb 2019 16:04:23 +0300 Subject: [PATCH 15/42] finality_proof docs --- core/finality-grandpa/src/finality_proof.rs | 38 +++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index e93d7b9b16903..eab63e76ea44f 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -17,17 +17,17 @@ //! GRANDPA block finality proof generation and check. //! //! Finality of block B is proved by providing: -//! 1) valid headers sub-chain from the block B to the block F; -//! 2) valid (with respect to proved authorities) GRANDPA justification of the block F; -//! 3) proof-of-execution of the `grandpa_authorities` call at the block F. +//! 1) the justification for the descendant block F; +//! 2) headers sub-chain (U; F], where U is the last block known to the caller; +//! 3) proof of GRANDPA::authorities() if the set changes at block F. //! //! Since earliest possible justification is returned, the GRANDPA authorities set //! at the block F is guaranteed to be the same as in the block B (this is because block //! that enacts new GRANDPA authorities set always comes with justification). It also //! means that the `set_id` is the same at blocks B and F. //! -//! The caller should track the `set_id`. The most straightforward way is to fetch finality -//! proofs ONLY for blocks on the tip of the chain and track the latest known `set_id`. +//! If authorities set changes several times in the (U; F] interval, multiple finality +//! proof fragments are returned && each should be verified separately. use std::sync::Arc; @@ -59,13 +59,18 @@ impl, RA> FinalityProofProvider } } -impl, RA> network::FinalityProofProvider for FinalityProofProvider +impl network::FinalityProofProvider for FinalityProofProvider where + Block: BlockT, B: Backend + Send + Sync + 'static, E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, { - fn prove_finality(&self, last_finalized: Block::Hash, for_block: Block::Hash) -> Result>, ClientError> { + fn prove_finality( + &self, + last_finalized: Block::Hash, + for_block: Block::Hash, + ) -> Result>, ClientError> { prove_finality( &*self.0.backend().blockchain(), |block| self.0.executor().call(block, "GrandpaApi_grandpa_authorities", &[]), @@ -421,11 +426,23 @@ mod tests { } fn side_header(number: u64) -> Header { - Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(1), header(number - 1).hash(), Default::default()) + Header::new( + number, + H256::from_low_u64_be(0), + H256::from_low_u64_be(1), + header(number - 1).hash(), + Default::default(), + ) } fn second_side_header(number: u64) -> Header { - Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(1), side_header(number - 1).hash(), Default::default()) + Header::new( + number, + H256::from_low_u64_be(0), + H256::from_low_u64_be(1), + side_header(number - 1).hash(), + Default::default(), + ) } fn test_blockchain() -> InMemoryBlockchain { @@ -476,7 +493,8 @@ mod tests { let blockchain = test_blockchain(); blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap(); blockchain.insert(side_header(4).hash(), side_header(4), None, None, NewBlockState::Best).unwrap(); - blockchain.insert(second_side_header(5).hash(), second_side_header(5), None, None, NewBlockState::Best).unwrap(); + blockchain.insert(second_side_header(5).hash(), second_side_header(5), None, None, NewBlockState::Best) + .unwrap(); blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); // chain is 1 -> 2 -> 3 -> 4 -> 5 From f94d5bebffd6ee2018b4cd1c209d24734f2346d1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 13 Feb 2019 13:23:30 +0300 Subject: [PATCH 16/42] use DB backend in test client --- Cargo.lock | 2 + core/client/db/Cargo.toml | 5 ++ core/client/db/src/lib.rs | 65 ++++++++++++-- core/client/db/src/light.rs | 4 +- core/client/src/client.rs | 17 ++-- core/network/src/test/sync.rs | 30 ++++--- .../src/changes_trie/changes_iterator.rs | 9 ++ core/test-client/Cargo.toml | 2 + core/test-client/src/lib.rs | 90 +++++++++++++++++-- 9 files changed, 188 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34309759af206..9aa31fa9a3e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4134,9 +4134,11 @@ dependencies = [ name = "substrate-test-client" version = "0.1.0" dependencies = [ + "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "parity-codec 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sr-primitives 0.1.0", "substrate-client 0.1.0", + "substrate-client-db 0.1.0", "substrate-consensus-common 0.1.0", "substrate-executor 0.1.0", "substrate-keyring 0.1.0", diff --git a/core/client/db/Cargo.toml b/core/client/db/Cargo.toml index 9c4b3dfffac56..6408fa0dc80b3 100644 --- a/core/client/db/Cargo.toml +++ b/core/client/db/Cargo.toml @@ -10,6 +10,7 @@ log = "0.4" kvdb = { git = "https://github.com/paritytech/parity-common", rev="b0317f649ab2c665b7987b8475878fc4d2e1f81d" } # FIXME replace with release as soon as our rocksdb changes are released upstream https://github.com/paritytech/parity-common/issues/88 kvdb-rocksdb = { git = "https://github.com/paritytech/parity-common", rev="b0317f649ab2c665b7987b8475878fc4d2e1f81d" } +kvdb-memorydb = { git = "https://github.com/paritytech/parity-common", rev="b0317f649ab2c665b7987b8475878fc4d2e1f81d", optional = true } lru-cache = "0.1.1" hash-db = { version = "0.11" } primitives = { package = "substrate-primitives", path = "../../primitives" } @@ -27,3 +28,7 @@ kvdb-memorydb = { git = "https://github.com/paritytech/parity-common", rev="b031 substrate-keyring = { path = "../../keyring" } test-client = { package = "substrate-test-client", path = "../../test-client" } env_logger = { version = "0.6" } + +[features] +default = [] +test-helpers = ["kvdb-memorydb"] diff --git a/core/client/db/src/lib.rs b/core/client/db/src/lib.rs index d64708a86b3ba..ec081ceec2870 100644 --- a/core/client/db/src/lib.rs +++ b/core/client/db/src/lib.rs @@ -57,6 +57,9 @@ use crate::storage_cache::{CachingState, SharedCache, new_shared_cache}; use log::{trace, debug, warn}; pub use state_db::PruningMode; +#[cfg(feature = "test-helpers")] +use client::in_mem::Backend as InMemoryBackend; + const CANONICALIZATION_DELAY: u64 = 4096; const MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR: u64 = 32768; const STATE_CACHE_SIZE_BYTES: usize = 16 * 1024 * 1024; @@ -463,8 +466,10 @@ impl client::backend::PrunableStateChangesTrieStorage state_machine::ChangesTrieRootsStorage for DbChangesTrieStorage { fn root(&self, anchor: &state_machine::ChangesTrieAnchorBlockId, block: u64) -> Result, String> { - // check API requirement - assert!(block <= anchor.number, "API requirement"); + // check API requirement: we can't get NEXT block(s) based on anchor + if block > anchor.number { + return Err(format!("Can't get CT root at {} using anchor at {}", block, anchor.number)); + } // we need to get hash of the block to resolve changes trie root let block_id = if block <= self.meta.read().finalized_number.as_() { @@ -531,8 +536,8 @@ impl> Backend { Backend::from_kvdb(db as Arc<_>, config.pruning, canonicalization_delay) } - #[cfg(test)] - fn new_test(keep_blocks: u32, canonicalization_delay: u64) -> Self { + #[cfg(any(test, feature = "test-helpers"))] + pub fn new_test(keep_blocks: u32, canonicalization_delay: u64) -> Self { use utils::NUM_COLUMNS; let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS)); @@ -570,6 +575,54 @@ impl> Backend { }) } + /// Returns in-memory blockchain that contains the same set of blocks that the self. + #[cfg(feature = "test-helpers")] + pub fn as_in_memory(&self) -> InMemoryBackend { + use client::backend::{Backend as ClientBackend, BlockImportOperation}; + use client::blockchain::Backend as BlockchainBackend; + + let inmem = InMemoryBackend::::new(); + + // get all headers hashes && sort them by number (could be duplicate) + let mut headers: Vec<(NumberFor, Block::Hash, Block::Header)> = Vec::new(); + for (_, header) in self.blockchain.db.iter(columns::HEADER) { + let header = Block::Header::decode(&mut &header[..]).unwrap(); + let hash = header.hash(); + let number = *header.number(); + let pos = headers.binary_search_by(|item| item.0.cmp(&number)); + match pos { + Ok(pos) => headers.insert(pos, (number, hash, header)), + Err(pos) => headers.insert(pos, (number, hash, header)), + } + } + + // insert all other headers + bodies + justifications + let info = self.blockchain.info().unwrap(); + for (number, hash, header) in headers { + let id = BlockId::Hash(hash); + let justification = self.blockchain.justification(id).unwrap(); + let body = self.blockchain.body(id).unwrap(); + let state = self.state_at(id).unwrap().pairs(); + + let new_block_state = if number.is_zero() { + NewBlockState::Final + } else if hash == info.best_hash { + NewBlockState::Best + } else { + NewBlockState::Normal + }; + let mut op = inmem.begin_operation().unwrap(); + op.set_block_data(header, body, justification, new_block_state).unwrap(); + op.update_db_storage(state.into_iter().map(|(k, v)| (None, k, Some(v))).collect()).unwrap(); + inmem.commit_operation(op).unwrap(); + } + + // and now finalize the best block we have + inmem.finalize_block(BlockId::Hash(info.finalized_hash), None).unwrap(); + + inmem + } + /// Handle setting head within a transaction. `route_to` should be the last /// block that existed in the database. `best_to` should be the best block /// to be set. @@ -712,8 +765,8 @@ impl> Backend { operation.apply_aux(&mut transaction); let mut meta_updates = Vec::new(); + let mut last_finalized_hash = self.blockchain.meta.read().finalized_hash; if !operation.finalized_blocks.is_empty() { - let mut last_finalized_hash = self.blockchain.meta.read().finalized_hash; for (block, justification) in operation.finalized_blocks { let block_hash = self.blockchain.expect_block_hash_from_id(&block)?; let block_header = self.blockchain.expect_header(BlockId::Hash(block_hash))?; @@ -787,7 +840,7 @@ impl> Backend { if finalized { // TODO: ensure best chain contains this block. - self.ensure_sequential_finalization(header, None)?; + self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; self.note_finalized(&mut transaction, header, hash)?; } else { // canonicalize blocks which are old enough, regardless of finality. diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index f47f29180ae5f..d03970d4aa277 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -72,8 +72,8 @@ impl LightStorage Self::from_kvdb(db as Arc<_>) } - #[cfg(test)] - pub(crate) fn new_test() -> Self { + #[cfg(any(test, feature = "test-helpers"))] + pub fn new_test() -> Self { use utils::NUM_COLUMNS; let db = Arc::new(::kvdb_memorydb::create(NUM_COLUMNS)); diff --git a/core/client/src/client.rs b/core/client/src/client.rs index bb3095bc6472e..e9a7af2107df4 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -1644,7 +1644,7 @@ pub(crate) mod tests { client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap(); assert_eq!(client.info().unwrap().chain.best_number, 1); - assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap()); + assert!(client.state_at(&BlockId::Number(1)).unwrap().pairs() != client.state_at(&BlockId::Number(0)).unwrap().pairs()); assert_eq!( client.runtime_api().balance_of( &BlockId::Number(client.info().unwrap().chain.best_number), @@ -1663,14 +1663,11 @@ pub(crate) mod tests { #[test] fn client_uses_authorities_from_blockchain_cache() { - let client = test_client::new(); - test_client::client::in_mem::cache_authorities_at( - client.backend().blockchain(), - Default::default(), - Some(vec![[1u8; 32].into()])); - assert_eq!(client.authorities_at( - &BlockId::Hash(Default::default())).unwrap(), - vec![[1u8; 32].into()]); + let client = test_client::new_light(); + let genesis_hash = client.header(&BlockId::Number(0)).unwrap().unwrap().hash(); + // authorities cache is first filled in genesis block + // => should be read from cache here (remote request will fail in this test) + assert!(!client.authorities_at(&BlockId::Hash(genesis_hash)).unwrap().is_empty()); } #[test] @@ -1696,7 +1693,7 @@ pub(crate) mod tests { client.import(BlockOrigin::Own, builder.bake().unwrap()).unwrap(); assert_eq!(client.info().unwrap().chain.best_number, 1); - assert!(client.state_at(&BlockId::Number(1)).unwrap() != client.state_at(&BlockId::Number(0)).unwrap()); + assert!(client.state_at(&BlockId::Number(1)).unwrap().pairs() != client.state_at(&BlockId::Number(0)).unwrap().pairs()); assert_eq!(client.body(&BlockId::Number(1)).unwrap().unwrap().len(), 1) } diff --git a/core/network/src/test/sync.rs b/core/network/src/test/sync.rs index b4b2b2078f437..edf670e82646d 100644 --- a/core/network/src/test/sync.rs +++ b/core/network/src/test/sync.rs @@ -31,7 +31,8 @@ fn sync_from_two_peers_works() { net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); net.sync(); - assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain())); + assert!(net.peer(0).client.backend().as_in_memory().blockchain() + .equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); let status = net.peer(0).status(); assert_eq!(status.sync.state, SyncState::Idle); } @@ -45,7 +46,8 @@ fn sync_from_two_peers_with_ancestry_search_works() { net.peer(2).push_blocks(100, false); net.restart_peer(0); net.sync(); - assert!(net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain())); + assert!(net.peer(0).client.backend().as_in_memory().blockchain() + .canon_equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); } #[test] @@ -58,7 +60,8 @@ fn sync_long_chain_works() { net.sync(); // Wait for peers to get up to speed. thread::sleep(time::Duration::from_millis(1000)); - assert!(net.peer(0).client.backend().blockchain().equals_to(net.peer(1).client.backend().blockchain())); + assert!(net.peer(0).client.backend().as_in_memory().blockchain() + .equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); } #[test] @@ -68,7 +71,8 @@ fn sync_no_common_longer_chain_fails() { net.peer(0).push_blocks(20, true); net.peer(1).push_blocks(20, false); net.sync(); - assert!(!net.peer(0).client.backend().blockchain().canon_equals_to(net.peer(1).client.backend().blockchain())); + assert!(!net.peer(0).client.backend().as_in_memory().blockchain() + .canon_equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); } #[test] @@ -111,11 +115,11 @@ fn sync_after_fork_works() { net.peer(2).push_blocks(1, false); // peer 1 has the best chain - let peer1_chain = net.peer(1).client.backend().blockchain().clone(); + let peer1_chain = net.peer(1).client.backend().as_in_memory().blockchain().clone(); net.sync(); - assert!(net.peer(0).client.backend().blockchain().canon_equals_to(&peer1_chain)); - assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer1_chain)); - assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(0).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(1).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(2).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); } #[test] @@ -131,8 +135,8 @@ fn syncs_all_forks() { net.sync(); // Check that all peers have all of the blocks. - assert_eq!(9, net.peer(0).client.backend().blockchain().blocks_count()); - assert_eq!(9, net.peer(1).client.backend().blockchain().blocks_count()); + assert_eq!(9, net.peer(0).client.backend().as_in_memory().blockchain().blocks_count()); + assert_eq!(9, net.peer(1).client.backend().as_in_memory().blockchain().blocks_count()); } #[test] @@ -147,9 +151,9 @@ fn own_blocks_are_announced() { net.sync(); assert_eq!(net.peer(0).client.backend().blockchain().info().unwrap().best_number, 1); assert_eq!(net.peer(1).client.backend().blockchain().info().unwrap().best_number, 1); - let peer0_chain = net.peer(0).client.backend().blockchain().clone(); - assert!(net.peer(1).client.backend().blockchain().canon_equals_to(&peer0_chain)); - assert!(net.peer(2).client.backend().blockchain().canon_equals_to(&peer0_chain)); + let peer0_chain = net.peer(0).client.backend().as_in_memory().blockchain().clone(); + assert!(net.peer(1).client.backend().as_in_memory().blockchain().canon_equals_to(&peer0_chain)); + assert!(net.peer(2).client.backend().as_in_memory().blockchain().canon_equals_to(&peer0_chain)); } #[test] diff --git a/core/state-machine/src/changes_trie/changes_iterator.rs b/core/state-machine/src/changes_trie/changes_iterator.rs index 4245c62e51fe0..5805427870c86 100644 --- a/core/state-machine/src/changes_trie/changes_iterator.rs +++ b/core/state-machine/src/changes_trie/changes_iterator.rs @@ -40,6 +40,9 @@ pub fn key_changes<'a, S: Storage, H: Hasher>( max: u64, key: &'a [u8], ) -> Result, String> where H::Out: HeapSizeOf { + // we can't query any roots before root + let max = ::std::cmp::min(max, end.number); + Ok(DrilldownIterator { essence: DrilldownIteratorEssence { key, @@ -67,6 +70,9 @@ pub fn key_changes_proof, H: Hasher>( max: u64, key: &[u8], ) -> Result>, String> where H::Out: HeapSizeOf { + // we can't query any roots before root + let max = ::std::cmp::min(max, end.number); + let mut iter = ProvingDrilldownIterator { essence: DrilldownIteratorEssence { key, @@ -104,6 +110,9 @@ pub fn key_changes_proof_check, H: Hasher>( max: u64, key: &[u8] ) -> Result, String> where H::Out: HeapSizeOf { + // we can't query any roots before root + let max = ::std::cmp::min(max, end.number); + let mut proof_db = MemoryDB::::default(); for item in proof { proof_db.insert(&item); diff --git a/core/test-client/Cargo.toml b/core/test-client/Cargo.toml index 786d61cd2d695..467a715de1033 100644 --- a/core/test-client/Cargo.toml +++ b/core/test-client/Cargo.toml @@ -6,6 +6,8 @@ edition = "2018" [dependencies] client = { package = "substrate-client", path = "../client" } +client-db = { package = "substrate-client-db", path = "../client/db", features = ["test-helpers"] } +futures = { version = "0.1.17" } parity-codec = "3.0" executor = { package = "substrate-executor", path = "../executor" } consensus = { package = "substrate-consensus-common", path = "../consensus/common" } diff --git a/core/test-client/src/lib.rs b/core/test-client/src/lib.rs index de730dce30911..bd2d3a298a044 100644 --- a/core/test-client/src/lib.rs +++ b/core/test-client/src/lib.rs @@ -34,9 +34,10 @@ pub use runtime; pub use consensus; use std::sync::Arc; +use futures::future::FutureResult; use primitives::Blake2Hasher; use runtime_primitives::StorageOverlay; -use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash as HashT}; +use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Hash as HashT, NumberFor}; use runtime::genesismap::{GenesisConfig, additional_storage_with_genesis}; use keyring::Keyring; use state_machine::ExecutionStrategy; @@ -59,7 +60,7 @@ mod local_executor { pub use local_executor::LocalExecutor; /// Test client database backend. -pub type Backend = client::in_mem::Backend; +pub type Backend = client_db::Backend; /// Test client executor. pub type Executor = client::LocalCallExecutor< @@ -67,16 +68,60 @@ pub type Executor = client::LocalCallExecutor< executor::NativeExecutor, >; +/// Test client light database backend. +pub type LightBackend = client::light::backend::Backend< + client_db::light::LightStorage, + LightFetcher, + Blake2Hasher, +>; + +/// Test client light fetcher. +pub struct LightFetcher; + +/// Test client light executor. +pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecutor< + runtime::Block, + LightBackend, + client::light::call_executor::RemoteCallExecutor< + client::light::blockchain::Blockchain< + client_db::light::LightStorage, + LightFetcher + >, + LightFetcher + >, + client::LocalCallExecutor< + client::light::backend::Backend< + client_db::light::LightStorage, + LightFetcher, + Blake2Hasher + >, + executor::NativeExecutor + > +>; + /// Creates new client instance used for tests. pub fn new() -> client::Client { - new_with_backend(Arc::new(Backend::new()), false) + new_with_backend(Arc::new(Backend::new_test(::std::u32::MAX, ::std::u64::MAX)), false) +} + +/// Creates new light client instance used for tests. +pub fn new_light() -> client::Client { + let storage = client_db::light::LightStorage::new_test(); + let blockchain = Arc::new(client::light::blockchain::Blockchain::new(storage)); + let backend = Arc::new(LightBackend::new(blockchain.clone())); + let executor = NativeExecutor::new(None); + let fetcher = Arc::new(LightFetcher); + let remote_call_executor = client::light::call_executor::RemoteCallExecutor::new(blockchain.clone(), fetcher); + let local_call_executor = client::LocalCallExecutor::new(backend.clone(), executor); + let call_executor = LightExecutor::new(backend.clone(), remote_call_executor, local_call_executor); + client::Client::new(backend, call_executor, genesis_storage(false), Default::default()).unwrap() } /// Creates new client instance used for tests with the given api execution strategy. pub fn new_with_execution_strategy( execution_strategy: ExecutionStrategy ) -> client::Client { - let backend = Arc::new(Backend::new()); + let backend = Arc::new(Backend::new_test(::std::u32::MAX, ::std::u64::MAX)); let executor = NativeExecutor::new(None); let executor = LocalCallExecutor::new(backend.clone(), executor); @@ -99,7 +144,7 @@ pub fn new_with_execution_strategy( pub fn new_with_changes_trie() -> client::Client { - new_with_backend(Arc::new(Backend::new()), true) + new_with_backend(Arc::new(Backend::new_test(::std::u32::MAX, ::std::u64::MAX)), true) } /// Creates new client instance used for tests with an explicitly provided backend. @@ -133,3 +178,38 @@ fn genesis_storage(support_changes_trie: bool) -> StorageOverlay { storage.extend(additional_storage_with_genesis(&block)); storage } + +impl client::light::fetcher::Fetcher for LightFetcher { + type RemoteHeaderResult = FutureResult; + type RemoteReadResult = FutureResult>, client::error::Error>; + type RemoteCallResult = FutureResult, client::error::Error>; + type RemoteChangesResult = FutureResult, u32)>, client::error::Error>; + + fn remote_header( + &self, + _request: client::light::fetcher::RemoteHeaderRequest, + ) -> Self::RemoteHeaderResult { + unimplemented!("not (yet) used in tests") + } + + fn remote_read( + &self, + _request: client::light::fetcher::RemoteReadRequest, + ) -> Self::RemoteReadResult { + unimplemented!("not (yet) used in tests") + } + + fn remote_call( + &self, + _request: client::light::fetcher::RemoteCallRequest, + ) -> Self::RemoteCallResult { + unimplemented!("not (yet) used in tests") + } + + fn remote_changes( + &self, + _request: client::light::fetcher::RemoteChangesRequest, + ) -> Self::RemoteChangesResult { + unimplemented!("not (yet) used in tests") + } +} From ac6388054a4e8f56ea2bd1f3cfc483c22d3fce2a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Feb 2019 17:21:16 +0300 Subject: [PATCH 17/42] justification_is_fetched_by_light_client_when_consensus_data_changes --- core/consensus/aura/src/lib.rs | 43 +-- core/consensus/common/src/import_queue.rs | 8 +- core/finality-grandpa/src/finality_proof.rs | 347 +++++++++++++------- core/finality-grandpa/src/lib.rs | 2 +- core/finality-grandpa/src/light_import.rs | 86 +++-- core/finality-grandpa/src/tests.rs | 121 +++++-- core/network/src/extra_requests.rs | 26 +- core/network/src/sync.rs | 9 + core/network/src/test/mod.rs | 178 ++++++++-- core/network/src/test/sync.rs | 51 ++- core/service/src/components.rs | 2 +- core/test-client/src/lib.rs | 2 +- node/cli/src/service.rs | 4 +- 13 files changed, 628 insertions(+), 251 deletions(-) diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index 03add2ab8983f..822daf21a70e4 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -663,7 +663,7 @@ mod tests { use super::*; use consensus_common::NoNetwork as DummyOracle; use network::test::*; - use network::test::{Block as TestBlock, PeersClient}; + use network::test::{Block as TestBlock, PeersClient, PeersFullClient}; use runtime_primitives::traits::Block as BlockT; use network::config::ProtocolConfig; use parking_lot::Mutex; @@ -704,14 +704,14 @@ mod tests { pub struct AuraTestNet { peers: Vec, ()>>>, started: bool, } impl TestNetFactory for AuraTestNet { - type Verifier = AuraVerifier; + type Verifier = AuraVerifier; type PeerData = (); /// Create new test network with peers and given config. @@ -722,23 +722,28 @@ mod tests { } } - fn make_verifier(&self, client: Arc, _cfg: &ProtocolConfig) + fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig) -> Arc { - let slot_duration = SlotDuration::get_or_compute(&*client) - .expect("slot duration available"); - let inherent_data_providers = InherentDataProviders::new(); - register_aura_inherent_data_provider( - &inherent_data_providers, - slot_duration.get() - ).expect("Registers aura inherent data provider"); - - assert_eq!(slot_duration.get(), SLOT_DURATION); - Arc::new(AuraVerifier { - client, - extra: NothingExtra, - inherent_data_providers, - }) + match client { + PeersClient::Full(client) => { + let slot_duration = SlotDuration::get_or_compute(&*client) + .expect("slot duration available"); + let inherent_data_providers = InherentDataProviders::new(); + register_aura_inherent_data_provider( + &inherent_data_providers, + slot_duration.get() + ).expect("Registers aura inherent data provider"); + + assert_eq!(slot_duration.get(), SLOT_DURATION); + Arc::new(AuraVerifier { + client, + extra: NothingExtra, + inherent_data_providers, + }) + }, + PeersClient::Light(_) => unreachable!("No (yet) tests for light client + Aura"), + } } fn peer(&self, i: usize) -> &Peer { @@ -780,7 +785,7 @@ mod tests { let mut runtime = current_thread::Runtime::new().unwrap(); for (peer_id, key) in peers { - let client = net.lock().peer(*peer_id).client().clone(); + let client = net.lock().peer(*peer_id).client().as_full().expect("full clients are created").clone(); let environ = Arc::new(DummyFactory(client.clone())); import_notifications.push( client.import_notification_stream() diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 84b35a0b4e8e9..30c8b5849aee1 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -245,13 +245,17 @@ impl> ImportQueue for BasicQueue { fn import_justification(&self, hash: B::Hash, number: NumberFor, justification: Justification) -> bool { self.justification_import.as_ref().map(|justification_import| { - justification_import.import_justification(hash, number, justification).is_ok() + justification_import.import_justification(hash, number, justification) + .map_err(|e| { trace!(target:"sync", "Justification import failed with: {}", e); e }) + .is_ok() }).unwrap_or(false) } fn import_finality_proof(&self, hash: B::Hash, number: NumberFor, finality_proof: Vec) -> bool { self.finality_proof_import.as_ref().map(|finality_proof_import| { - finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier).is_ok() + finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier) + .map_err(|e| { trace!(target:"sync", "Finality proof import failed with: {}", e); e }) + .is_ok() }).unwrap_or(false) } } diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 178199d2093d3..078a352017043 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -34,6 +34,7 @@ use std::sync::Arc; use client::{ backend::Backend, blockchain::Backend as BlockchainBackend, CallExecutor, Client, error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}, + light::fetcher::{FetchChecker, RemoteCallRequest}, ExecutionStrategy, }; use codec::{Encode, Decode}; @@ -46,8 +47,81 @@ use substrate_primitives::{Ed25519AuthorityId, H256, Blake2Hasher}; use justification::GrandpaJustification; +/// GRANDPA authority set related methods for the finality proof provider. +pub trait AuthoritySetForFinalityProver: Send + Sync { + /// Call GrandpaApi::grandpa_authorities at given block. + fn authorities(&self, block: &BlockId) -> ClientResult>; + /// Prove call of GrandpaApi::grandpa_authorities at given block. + fn prove_authorities(&self, block: &BlockId) -> ClientResult>>; +} + +/// Client-based implementation of AuthoritySetForFinalityProver. +impl, RA> AuthoritySetForFinalityProver for Client + where + B: Backend + Send + Sync + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, +{ + fn authorities(&self, block: &BlockId) -> ClientResult> { + self.executor().call( + block, + "GrandpaApi_grandpa_authorities", + &[], + ExecutionStrategy::NativeElseWasm, + ).and_then(|call_result| Decode::decode(&mut &call_result[..]) + .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( + "failed to decode GRANDPA authorities set proof".into(), + )))) + } + + fn prove_authorities(&self, block: &BlockId) -> ClientResult>> { + self.execution_proof(block, "GrandpaApi_grandpa_authorities",&[]).map(|(_, proof)| proof) + } +} + +/// GRANDPA authority set related methods for the finality proof checker. +pub trait AuthoritySetForFinalityChecker: Send + Sync { + /// Check execution proof of Grandpa::grandpa_authorities at given block. + fn check_authorities_proof( + &self, + hash: Block::Hash, + header: Block::Header, + proof: Vec>, + ) -> ClientResult>; +} + +/// FetchChecker-based implementation of AuthoritySetForFinalityChecker. +impl AuthoritySetForFinalityChecker for Arc> { + fn check_authorities_proof( + &self, + hash: Block::Hash, + header: Block::Header, + proof: Vec>, + ) -> ClientResult> { + let request = RemoteCallRequest { + block: hash, + header, + method: "GrandpaApi_grandpa_authorities".into(), + call_data: vec![], + retry_count: None, + }; + + self.check_execution_proof(&request, proof) + .and_then(|authorities| { + let authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &authorities[..]) + .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( + "failed to decode GRANDPA authorities set proof".into(), + )))?; + Ok(authorities.into_iter().collect()) + }) + } +} + /// Finality proof provider for serving network requests. -pub struct FinalityProofProvider, RA>(Arc>); +pub struct FinalityProofProvider, RA> { + client: Arc>, + authority_provider: Arc>, +} impl, RA> FinalityProofProvider where @@ -55,8 +129,15 @@ impl, RA> FinalityProofProvider E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, { - pub fn new(client: Arc>) -> Self { - FinalityProofProvider(client) + /// Create new finality proof provider using: + /// + /// - client for accessing blockchain data; + /// - authority_provider for calling and proving runtime methods. + pub fn new( + client: Arc>, + authority_provider: Arc>, + ) -> Self { + FinalityProofProvider { client, authority_provider } } } @@ -73,14 +154,8 @@ impl network::FinalityProofProvider for FinalityProofPro for_block: Block::Hash, ) -> Result>, ClientError> { prove_finality( - &*self.0.backend().blockchain(), - |block| self.0.executor().call( - block, - "GrandpaApi_grandpa_authorities", - &[], - ExecutionStrategy::NativeElseWasm, - ), - |block| self.0.execution_proof(block, "GrandpaApi_grandpa_authorities",&[]).map(|(_, proof)| proof), + &*self.client.backend().blockchain(), + &*self.authority_provider, last_finalized, for_block, ) @@ -89,13 +164,13 @@ impl network::FinalityProofProvider for FinalityProofPro /// The effects of block finality. #[derive(Debug, PartialEq)] -pub struct FinalityEffects { +pub struct FinalityEffects { /// The (ordered) set of headers that could be imported. pub headers_to_import: Vec
, /// The hash of the block that could be finalized. pub block: Header::Hash, /// The justification for the block. - pub justification: J, + pub justification: Vec, /// New authorities set id that should be applied starting from block. pub new_set_id: u64, /// New authorities set that should be applied starting from block. @@ -109,11 +184,11 @@ pub struct FinalityEffects { /// 2) headers sub-chain (U; F], where U is the last block known to the caller; /// 3) proof of GRANDPA::authorities() if the set changes at block F. #[derive(Debug, PartialEq, Encode, Decode)] -struct FinalityProofFragment { +struct FinalityProofFragment { /// The hash of block F for which justification is provided. pub block: Header::Hash, /// Justification of the block F. - pub justification: Justification, + pub justification: Vec, /// The set of headers in the range (U; F] that we believe are unknown to the caller. Ordered. pub unknown_headers: Vec
, /// Optional proof of execution of GRANDPA::authorities(). @@ -123,7 +198,7 @@ struct FinalityProofFragment { /// Proof of finality is the ordered set of finality fragments, where: /// - last fragment provides justification for the best possible block from the requested range; /// - all other fragments provide justifications for GRANDPA authorities set changes within requested range. -type FinalityProof = Vec>; +type FinalityProof
= Vec>; /// Prepare proof-of-finality for the best possible block in the range: (begin; end]. /// @@ -131,24 +206,25 @@ type FinalityProof = Vec, B, GetAuthorities, ProveAuthorities>( +pub fn prove_finality, B: BlockchainBackend>( blockchain: &B, - get_authorities: GetAuthorities, - prove_authorities: ProveAuthorities, + authorities_provider: &AuthoritySetForFinalityProver, begin: Block::Hash, end: Block::Hash, -) -> ::client::error::Result>> - where - B: BlockchainBackend, - GetAuthorities: Fn(&BlockId) -> ClientResult>, - ProveAuthorities: Fn(&BlockId) -> ClientResult>>, -{ +) -> ::client::error::Result>> { let begin_id = BlockId::Hash(begin); let begin_number = blockchain.expect_block_number_from_id(&begin_id)?; // early-return if we sure that there are no blocks finalized AFTER begin block let info = blockchain.info()?; if info.finalized_number <= begin_number { + trace!( + target: "finality", + "Requested finality proof for descendant of #{} while we only have finalized #{}. Returning empty proof.", + begin_number, + info.finalized_number, + ); + return Ok(None); } @@ -170,7 +246,7 @@ pub fn prove_finality, B, GetAuthorities, ProveAuthorit } // iterate justifications && try to prove finality - let mut current_authorities = get_authorities(&begin_id)?; + let mut current_authorities = authorities_provider.authorities(&begin_id)?; let mut current_number = begin_number + One::one(); let mut finality_proof = Vec::new(); let mut unknown_headers = Vec::new(); @@ -187,10 +263,10 @@ pub fn prove_finality, B, GetAuthorities, ProveAuthorit if let Some(justification) = blockchain.justification(current_id)? { // check if the current block enacts new GRANDPA authorities set let parent_id = BlockId::Number(current_number - One::one()); - let new_authorities = get_authorities(&parent_id)?; + let new_authorities = authorities_provider.authorities(&parent_id)?; let new_authorities_proof = if current_authorities != new_authorities { current_authorities = new_authorities; - Some(prove_authorities(&parent_id)?) + Some(authorities_provider.prove_authorities(&parent_id)?) } else { None }; @@ -235,38 +311,47 @@ pub fn prove_finality, B, GetAuthorities, ProveAuthorit } if finality_proof.is_empty() { + trace!( + target: "finality", + "No justifications found when making finality proof for {}. Returning empty proof.", + end, + ); + Ok(None) } else { + trace!( + target: "finality", + "Built finality proof for {} of {} fragments. Last fragment for {}.", + end, + finality_proof.len(), + finality_proof.last().expect("checked that !finality_proof.is_empty(); qed").block, + ); + Ok(Some(finality_proof.encode())) } } -/// Check proof-of-finality for the given block. +/// Check GRANDPA proof-of-finality for the given block. /// /// Returns the vector of headers that MUST be validated + imported /// AND. If at least one of those headers /// is invalid, all other MUST be considered invalid. -pub(crate) fn check_finality_proof, B, CheckAuthoritiesProof>( +pub(crate) fn check_finality_proof, B>( blockchain: &B, current_set_id: u64, current_authorities: Vec<(Ed25519AuthorityId, u64)>, - check_authorities_proof: CheckAuthoritiesProof, + authorities_provider: &AuthoritySetForFinalityChecker, remote_proof: Vec, -) -> ClientResult>> +) -> ClientResult> where NumberFor: BlockNumberOps, B: BlockchainBackend, - CheckAuthoritiesProof: Fn( - Block::Hash, - Block::Header, - Vec>, - ) -> ClientResult>, { - do_check_finality_proof( + do_check_finality_proof::<_, _, GrandpaJustification>( blockchain, current_set_id, current_authorities, - check_authorities_proof, + authorities_provider, remote_proof) } @@ -275,25 +360,20 @@ pub(crate) fn check_finality_proof, B, CheckAuthorities /// Returns the vector of headers that MUST be validated + imported /// AND. If at least one of those headers /// is invalid, all other MUST be considered invalid. -fn do_check_finality_proof, B, J, CheckAuthoritiesProof>( +fn do_check_finality_proof, B, J>( blockchain: &B, current_set_id: u64, current_authorities: Vec<(Ed25519AuthorityId, u64)>, - check_authorities_proof: CheckAuthoritiesProof, + authorities_provider: &AuthoritySetForFinalityChecker, remote_proof: Vec, -) -> ClientResult> +) -> ClientResult> where NumberFor: BlockNumberOps, B: BlockchainBackend, J: ProvableJustification, - CheckAuthoritiesProof: Fn( - Block::Hash, - Block::Header, - Vec>, - ) -> ClientResult>, { // decode finality proof - let proof = FinalityProof::::decode(&mut &remote_proof[..]) + let proof = FinalityProof::::decode(&mut &remote_proof[..]) .ok_or_else(|| ClientErrorKind::BadJustification("failed to decode finality proof".into()))?; // empty proof can't prove anything @@ -315,10 +395,10 @@ fn do_check_finality_proof, B, J, CheckAuthoritiesProof } } - authorities = check_finality_proof_fragment( + authorities = check_finality_proof_fragment::<_, _, J>( blockchain, authorities, - &check_authorities_proof, + authorities_provider, proof_fragment)?; } @@ -329,33 +409,36 @@ fn do_check_finality_proof, B, J, CheckAuthoritiesProof } /// Check finality proof for the single block. -fn check_finality_proof_fragment, B, J, CheckAuthoritiesProof>( +fn check_finality_proof_fragment, B, J>( blockchain: &B, - authority_set: AuthoritiesOrEffects, - check_authorities_proof: &CheckAuthoritiesProof, - proof_fragment: FinalityProofFragment, -) -> ClientResult> + authority_set: AuthoritiesOrEffects, + authorities_provider: &AuthoritySetForFinalityChecker, + proof_fragment: FinalityProofFragment, +) -> ClientResult> where NumberFor: BlockNumberOps, B: BlockchainBackend, - J: ProvableJustification, - CheckAuthoritiesProof: Fn( - Block::Hash, - Block::Header, - Vec>, - ) -> ClientResult>, + J: Decode + ProvableJustification, { // verify justification using previous authorities set let (mut current_set_id, mut current_authorities) = authority_set.extract_authorities(); - proof_fragment.justification.verify(current_set_id, ¤t_authorities)?; + let justification: J = Decode::decode(&mut &proof_fragment.justification[..]) + .ok_or_else(|| ClientError::from(ClientErrorKind::JustificationDecode))?; + justification.verify(current_set_id, ¤t_authorities)?; // and now verify new authorities proof (if provided) if let Some(new_authorities_proof) = proof_fragment.authorities_proof { + // it is safe to query header here, because its non-finality proves that it can't be pruned let header = blockchain.expect_header(BlockId::Hash(proof_fragment.block))?; let parent_hash = *header.parent_hash(); let parent_header = blockchain.expect_header(BlockId::Hash(parent_hash))?; - current_authorities = check_authorities_proof(parent_hash, parent_header, new_authorities_proof)?; + current_authorities = authorities_provider.check_authorities_proof( + parent_hash, + parent_header, + new_authorities_proof, + )?; + current_set_id = current_set_id + 1; } @@ -369,12 +452,12 @@ fn check_finality_proof_fragment, B, J, CheckAuthoritie } /// Authorities set from initial authorities set or finality effects. -enum AuthoritiesOrEffects { +enum AuthoritiesOrEffects { Authorities(u64, Vec<(Ed25519AuthorityId, u64)>), - Effects(FinalityEffects), + Effects(FinalityEffects
), } -impl AuthoritiesOrEffects { +impl AuthoritiesOrEffects
{ pub fn extract_authorities(self) -> (u64, Vec<(Ed25519AuthorityId, u64)>) { match self { AuthoritiesOrEffects::Authorities(set_id, authorities) => (set_id, authorities), @@ -382,7 +465,7 @@ impl AuthoritiesOrEffects { } } - pub fn extract_effects(self) -> Option> { + pub fn extract_effects(self) -> Option> { match self { AuthoritiesOrEffects::Authorities(_, _) => None, AuthoritiesOrEffects::Effects(effects) => Some(effects), @@ -407,12 +490,42 @@ impl> ProvableJustification for GrandpaJ #[cfg(test)] mod tests { - use test_client::runtime::{Block, Header}; + use test_client::runtime::{Block, Header, H256}; use test_client::client::{backend::NewBlockState}; use test_client::client::in_mem::Blockchain as InMemoryBlockchain; use super::*; - type FinalityProof = super::FinalityProof>; + type FinalityProof = super::FinalityProof
; + + impl AuthoritySetForFinalityProver for (GetAuthorities, ProveAuthorities) + where + GetAuthorities: Send + Sync + Fn(BlockId) -> ClientResult>, + ProveAuthorities: Send + Sync + Fn(BlockId) -> ClientResult>>, + { + fn authorities(&self, block: &BlockId) -> ClientResult> { + self.0(*block) + } + + fn prove_authorities(&self, block: &BlockId) -> ClientResult>> { + self.1(*block) + } + } + + struct ClosureAuthoritySetForFinalityChecker(pub Closure); + + impl AuthoritySetForFinalityChecker for ClosureAuthoritySetForFinalityChecker + where + Closure: Send + Sync + Fn(H256, Header, Vec>) -> ClientResult>, + { + fn check_authorities_proof( + &self, + hash: H256, + header: Header, + proof: Vec>, + ) -> ClientResult> { + self.0(hash, header, proof) + } + } #[derive(Debug, PartialEq, Encode, Decode)] struct ValidJustification(Vec); @@ -469,8 +582,10 @@ mod tests { // => range is invalid prove_finality( &blockchain, - |_| unreachable!("should return before calling GetAuthorities"), - |_| unreachable!("should return before calling ProveAuthorities"), + &( + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + ), header(2).hash(), header(2).hash(), ).unwrap_err(); @@ -486,8 +601,10 @@ mod tests { // => we can't provide any additional justifications let proof_of_4 = prove_finality( &blockchain, - |_| unreachable!("should return before calling GetAuthorities"), - |_| unreachable!("should return before calling ProveAuthorities"), + &( + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + ), header(3).hash(), header(4).hash(), ).unwrap(); @@ -509,8 +626,10 @@ mod tests { // => when requesting for (4'; 5'], error is returned prove_finality( &blockchain, - |_| unreachable!("should return before calling GetAuthorities"), - |_| unreachable!("should return before calling ProveAuthorities"), + &( + |_| unreachable!("should return before calling GetAuthorities"), + |_| unreachable!("should return before calling ProveAuthorities"), + ), side_header(4).hash(), second_side_header(5).hash(), ).unwrap_err(); @@ -525,8 +644,10 @@ mod tests { // => we can't prove finality let proof_of_4 = prove_finality( &blockchain, - |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), - |_| unreachable!("authorities didn't change => ProveAuthorities won't be called"), + &( + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), + |_| unreachable!("authorities didn't change => ProveAuthorities won't be called"), + ), header(3).hash(), header(4).hash(), ).unwrap(); @@ -543,8 +664,10 @@ mod tests { // => since authorities are the same, we only need justification for 5 let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( &blockchain, - |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), - |_| unreachable!("should return before calling ProveAuthorities"), + &( + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), + |_| unreachable!("should return before calling ProveAuthorities"), + ), header(3).hash(), header(5).hash(), ).unwrap().unwrap()[..]).unwrap(); @@ -566,8 +689,10 @@ mod tests { // => we can't prove finality of 5, but providing finality for 4 is still useful for requester let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( &blockchain, - |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)].encode()), - |_| unreachable!("should return before calling ProveAuthorities"), + &( + |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), + |_| unreachable!("should return before calling ProveAuthorities"), + ), header(3).hash(), header(5).hash(), ).unwrap().unwrap()[..]).unwrap(); @@ -591,18 +716,20 @@ mod tests { // => since we only have justification for #7, we provide #7 let proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality( &blockchain, - |block_id| match *block_id { - BlockId::Hash(h) if h == header(3).hash() => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)].encode()), - BlockId::Number(3) => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)].encode()), - BlockId::Number(4) => Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)].encode()), - BlockId::Number(6) => Ok(vec![(Ed25519AuthorityId([6u8; 32]), 1u64)].encode()), - _ => unreachable!("no other authorities should be fetched: {:?}", block_id), - }, - |block_id| match *block_id { - BlockId::Number(4) => Ok(vec![vec![40]]), - BlockId::Number(6) => Ok(vec![vec![60]]), - _ => unreachable!("no other authorities should be proved: {:?}", block_id), - }, + &( + |block_id| match block_id { + BlockId::Hash(h) if h == header(3).hash() => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)]), + BlockId::Number(3) => Ok(vec![(Ed25519AuthorityId([3u8; 32]), 1u64)]), + BlockId::Number(4) => Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)]), + BlockId::Number(6) => Ok(vec![(Ed25519AuthorityId([6u8; 32]), 1u64)]), + _ => unreachable!("no other authorities should be fetched: {:?}", block_id), + }, + |block_id| match block_id { + BlockId::Number(4) => Ok(vec![vec![40]]), + BlockId::Number(6) => Ok(vec![vec![60]]), + _ => unreachable!("no other authorities should be proved: {:?}", block_id), + }, + ), header(3).hash(), header(6).hash(), ).unwrap().unwrap()[..]).unwrap(); @@ -631,11 +758,11 @@ mod tests { let blockchain = test_blockchain(); // when we can't decode proof from Vec - do_check_finality_proof::<_, _, ValidJustification, _>( + do_check_finality_proof::<_, _, ValidJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], - |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), vec![42], ).unwrap_err(); } @@ -645,11 +772,11 @@ mod tests { let blockchain = test_blockchain(); // when decoded proof has zero length - do_check_finality_proof::<_, _, ValidJustification, _>( + do_check_finality_proof::<_, _, ValidJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], - |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), Vec::::new().encode(), ).unwrap_err(); } @@ -659,19 +786,19 @@ mod tests { let blockchain = test_blockchain(); // when intermediate (#0) fragment has non-empty unknown headers - do_check_finality_proof::<_, _, ValidJustification, _>( + do_check_finality_proof::<_, _, ValidJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], - |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), vec![FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![7]), + justification: ValidJustification(vec![7]).encode(), unknown_headers: vec![header(4)], authorities_proof: Some(vec![vec![42]]), }, FinalityProofFragment { block: header(5).hash(), - justification: ValidJustification(vec![8]), + justification: ValidJustification(vec![8]).encode(), unknown_headers: vec![header(5)], authorities_proof: None, }].encode(), @@ -683,19 +810,19 @@ mod tests { let blockchain = test_blockchain(); // when intermediate (#0) fragment has empty authorities proof - do_check_finality_proof::<_, _, ValidJustification, _>( + do_check_finality_proof::<_, _, ValidJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], - |_, _, _| unreachable!("returns before CheckAuthoritiesProof"), + &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), vec![FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![7]), + justification: ValidJustification(vec![7]).encode(), unknown_headers: Vec::new(), authorities_proof: None, }, FinalityProofFragment { block: header(5).hash(), - justification: ValidJustification(vec![8]), + justification: ValidJustification(vec![8]).encode(), unknown_headers: vec![header(5)], authorities_proof: None, }].encode(), @@ -706,19 +833,19 @@ mod tests { fn finality_proof_check_works() { let blockchain = test_blockchain(); - let effects = do_check_finality_proof::<_, _, ValidJustification, _>( + let effects = do_check_finality_proof::<_, _, ValidJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], - |_, _, _| Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)]), + &ClosureAuthoritySetForFinalityChecker(|_, _, _| Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)])), vec![FinalityProofFragment { block: header(2).hash(), - justification: ValidJustification(vec![7]), + justification: ValidJustification(vec![7]).encode(), unknown_headers: Vec::new(), authorities_proof: Some(vec![vec![42]]), }, FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![8]), + justification: ValidJustification(vec![8]).encode(), unknown_headers: vec![header(4)], authorities_proof: None, }].encode(), @@ -726,7 +853,7 @@ mod tests { assert_eq!(effects, FinalityEffects { headers_to_import: vec![header(4)], block: header(4).hash(), - justification: ValidJustification(vec![8]), + justification: ValidJustification(vec![8]).encode(), new_set_id: 2, new_authorities: vec![(Ed25519AuthorityId([4u8; 32]), 1u64)], }); diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 8df906c80f5b3..cc7d5900e705d 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -123,7 +123,7 @@ mod service_integration; #[cfg(feature="service-integration")] pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService}; -pub use finality_proof::FinalityProofProvider; +pub use finality_proof::{FinalityProofProvider, AuthoritySetForFinalityChecker, AuthoritySetForFinalityProver}; pub use light_import::light_block_import; use authorities::SharedAuthoritySet; diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 9f3bf1a1fda9c..3635427c1f29e 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -22,7 +22,6 @@ use client::{ backend::Backend, blockchain::HeaderBackend, error::Error as ClientError, error::ErrorKind as ClientErrorKind, - light::fetcher::{FetchChecker, RemoteCallRequest}, }; use codec::{Encode, Decode}; use consensus_common::{ @@ -41,6 +40,7 @@ use substrate_primitives::{H256, Ed25519AuthorityId, Blake2Hasher}; use crate::consensus_changes::ConsensusChanges; use crate::environment::canonical_at_height; +use crate::finality_proof::AuthoritySetForFinalityChecker; use crate::justification::GrandpaJustification; use crate::load_consensus_changes; @@ -52,7 +52,7 @@ const LIGHT_CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes"; /// Create light block importer. pub fn light_block_import, RA, PRA>( client: Arc>, - fetch_checker: Arc>, + authority_set_provider: Arc>, api: Arc, ) -> Result, ClientError> where @@ -88,7 +88,7 @@ pub fn light_block_import, RA, PRA>( Ok(GrandpaLightBlockImport { client, - fetch_checker, + authority_set_provider, data: Arc::new(RwLock::new(LightImportData { authority_set, consensus_changes, @@ -103,7 +103,7 @@ pub fn light_block_import, RA, PRA>( /// - fetching finality proofs for blocks that are enacting consensus changes. pub struct GrandpaLightBlockImport, RA> { client: Arc>, - fetch_checker: Arc>, + authority_set_provider: Arc>, data: Arc>>, } @@ -180,7 +180,15 @@ impl, RA> FinalityProofImport finality_proof: Vec, verifier: &Verifier, ) -> Result<(), Self::Error> { - do_import_finality_proof(&*self.client, &*self.fetch_checker, &mut *self.data.write(), hash, number, finality_proof, verifier) + do_import_finality_proof( + &*self.client, + &*self.authority_set_provider, + &mut *self.data.write(), + hash, + number, + finality_proof, + verifier, + ) } } @@ -244,13 +252,26 @@ fn do_import_block, RA>( match justification { Some(justification) => { + trace!( + target: "finality", + "Imported block {}{}. Importing justification.", + if enacts_consensus_change { " which enacts consensus changes" } else { "" }, + hash, + ); + do_import_justification(client, data, hash, number, justification)?; Ok(import_result) }, None if enacts_consensus_change => { - // remember that we need justification for this block + trace!( + target: "finality", + "Imported block {} which enacts consensus changes. Requesting finality proof.", + hash, + ); + + // remember that we need finality proof for this block data.consensus_changes.note_change((number, hash)); - Ok(ImportResult::NeedsJustification) + Ok(ImportResult::NeedsFinalityProof) }, None => Ok(import_result), } @@ -259,7 +280,7 @@ fn do_import_block, RA>( /// Try to import finality proof. fn do_import_finality_proof, RA>( client: &Client, - fetch_checker: &FetchChecker, + authority_set_provider: &AuthoritySetForFinalityChecker, data: &mut LightImportData, _hash: Block::Hash, _number: NumberFor, @@ -281,24 +302,7 @@ fn do_import_finality_proof, RA>( &*client.backend().blockchain(), authority_set_id, authorities, - |hash, header, authorities_proof| { - let request = RemoteCallRequest { - block: hash, - header, - method: "GrandpaApi_grandpa_authorities".into(), - call_data: vec![], - retry_count: None, - }; - - fetch_checker.check_execution_proof(&request, authorities_proof) - .and_then(|authorities| { - let authorities: Vec<(Ed25519AuthorityId, u64)> = Decode::decode(&mut &authorities[..]) - .ok_or_else(|| ClientError::from(ClientErrorKind::CallResultDecode( - "failed to decode GRANDPA authorities set proof".into(), - )))?; - Ok(authorities.into_iter().collect()) - }) - }, + authority_set_provider, finality_proof, ).map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; @@ -359,9 +363,33 @@ fn do_import_justification, RA>( // BadJustification error means that justification has been successfully decoded, but // it isn't valid within current authority set let justification = match justification { - Err(ClientError(ClientErrorKind::BadJustification(_), _)) => return Ok(ImportResult::NeedsFinalityProof), - Err(e) => return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()), - Ok(justification) => justification, + Err(ClientError(ClientErrorKind::BadJustification(_), _)) => { + trace!( + target: "finality", + "Justification for {} is not valid within current authorities set. Requesting finality proof.", + hash, + ); + + return Ok(ImportResult::NeedsFinalityProof); + }, + Err(e) => { + trace!( + target: "finality", + "Justification for {} is not valid. Bailing.", + hash, + ); + + return Err(ConsensusErrorKind::ClientImport(e.to_string()).into()); + }, + Ok(justification) => { + trace!( + target: "finality", + "Justification for {} is valid. Finalizing the block.", + hash, + ); + + justification + }, }; // finalize the block diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index 119bd11d1cd75..047ea6f8bc0dd 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -24,14 +24,13 @@ use parking_lot::Mutex; use tokio::runtime::current_thread; use keyring::Keyring; use client::{ - BlockchainEvents, error::Result, - blockchain::Backend as BlockchainBackend, + error::Result, runtime_api::{Core, RuntimeVersion, ApiExt}, }; use test_client::{self, runtime::BlockNumber}; use codec::Decode; use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportBlock, ImportResult}; -use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport}; +use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport}; use std::collections::{HashMap, HashSet}; use std::result; use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi}; @@ -40,6 +39,7 @@ use runtime_primitives::ExecutionContext; use substrate_primitives::NativeOrEncoded; use authorities::AuthoritySet; +use finality_proof::{FinalityProofProvider, AuthoritySetForFinalityProver, AuthoritySetForFinalityChecker}; type PeerData = Mutex< @@ -70,7 +70,7 @@ impl GrandpaTestNet { let config = Self::default_config(); for _ in 0..n_peers { - net.add_peer(&config); + net.add_full_peer(&config); } net @@ -97,21 +97,45 @@ impl TestNetFactory for GrandpaTestNet { } } - fn make_verifier(&self, _client: Arc, _cfg: &ProtocolConfig) + fn make_verifier(&self, _client: PeersClient, _cfg: &ProtocolConfig) -> Arc { Arc::new(PassThroughVerifier(false)) // use non-instant finality. } - fn make_block_import(&self, client: Arc) - -> (SharedBlockImport, Option>, PeerData) + fn make_block_import(&self, client: PeersClient) + -> (SharedBlockImport, Option>, Option>, PeerData) { - let (import, link) = block_import( - client, - Arc::new(self.test_config.clone()) - ).expect("Could not create block import for fresh peer."); - let shared_import = Arc::new(import); - (shared_import.clone(), Some(shared_import), Mutex::new(Some(link))) + match client { + PeersClient::Full(ref client) => { + let (import, link) = block_import( + client.clone(), + Arc::new(self.test_config.clone()) + ).expect("Could not create block import for fresh peer."); + let shared_import = Arc::new(import); + (shared_import.clone(), Some(shared_import), None, Mutex::new(Some(link))) + }, + PeersClient::Light(ref client) => { + let authorities_provider = Arc::new(self.test_config.clone()); + let import = light_block_import( + client.clone(), + authorities_provider, + Arc::new(self.test_config.clone()) + ).expect("Could not create block import for fresh peer."); + let shared_import = Arc::new(import); + (shared_import.clone(), None, Some(shared_import), Mutex::new(None)) + }, + } + } + + fn make_finality_proof_provider(&self, client: PeersClient) -> Option>> { + match client { + PeersClient::Full(ref client) => { + let authorities_provider = Arc::new(self.test_config.clone()); + Some(Arc::new(FinalityProofProvider::new(client.clone(), authorities_provider))) + }, + PeersClient::Light(_) => None, + } } fn peer(&self, i: usize) -> &GrandpaPeer { @@ -321,16 +345,16 @@ impl ApiExt for RuntimeApi { impl GrandpaApi for RuntimeApi { fn grandpa_authorities_runtime_api_impl( &self, - at: &BlockId, + _: &BlockId, _: ExecutionContext, _: Option<()>, _: Vec, ) -> Result>> { - if at == &BlockId::Number(0) { +// if at == &BlockId::Number(0) { Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native) - } else { +/* } else { panic!("should generally only request genesis authorities") - } + }*/ } fn grandpa_pending_change_runtime_api_impl( @@ -351,6 +375,33 @@ impl GrandpaApi for RuntimeApi { } } +impl AuthoritySetForFinalityProver for TestApi { + fn authorities(&self, block: &BlockId) -> Result> { + let runtime_api = RuntimeApi { inner: self.clone() }; + runtime_api.grandpa_authorities_runtime_api_impl(block, ExecutionContext::Syncing, None, Vec::new()) + .map(|v| match v { + NativeOrEncoded::Native(value) => value, + _ => unreachable!("only providing native values"), + }) + } + + fn prove_authorities(&self, block: &BlockId) -> Result>> { + self.authorities(block).map(|auth| vec![auth.encode()]) + } +} + +impl AuthoritySetForFinalityChecker for TestApi { + fn check_authorities_proof( + &self, + _hash: ::Hash, + _header: ::Header, + proof: Vec>, + ) -> Result> { + Decode::decode(&mut &proof[0][..]) + .ok_or_else(|| unreachable!("incorrect value is passed as GRANDPA authorities proof")) + } +} + const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500); const TEST_ROUTING_INTERVAL: Duration = Duration::from_millis(50); @@ -445,7 +496,7 @@ fn finalize_3_voters_no_observers() { run_to_completion(20, net.clone(), peers); // normally there's no justification for finalized blocks - assert!(net.lock().peer(0).client().backend().blockchain().justification(BlockId::Number(20)).unwrap().is_none(), + assert!(net.lock().peer(0).client().justification(&BlockId::Number(20)).unwrap().is_none(), "Extra justification for block#1"); } @@ -546,7 +597,7 @@ fn transition_3_voters_twice_1_observer() { assert_eq!(peer.client().info().unwrap().chain.best_number, 1, "Peer #{} failed to sync", i); - let set_raw = peer.client().backend().get_aux(::AUTHORITY_SET_KEY).unwrap().unwrap(); + let set_raw = peer.client().get_aux(::AUTHORITY_SET_KEY).unwrap().unwrap(); let set = AuthoritySet::::decode(&mut &set_raw[..]).unwrap(); assert_eq!(set.current(), (0, make_ids(peers_a).as_slice())); @@ -632,7 +683,7 @@ fn transition_3_voters_twice_1_observer() { .take_while(|n| Ok(n.header.number() < &30)) .for_each(move |_| Ok(())) .map(move |()| { - let set_raw = client.backend().get_aux(::AUTHORITY_SET_KEY).unwrap().unwrap(); + let set_raw = client.get_aux(::AUTHORITY_SET_KEY).unwrap().unwrap(); let set = AuthoritySet::::decode(&mut &set_raw[..]).unwrap(); assert_eq!(set.current(), (2, make_ids(peers_c).as_slice())); @@ -684,8 +735,8 @@ fn justification_is_emitted_when_consensus_data_changes() { let net = Arc::new(Mutex::new(net)); run_to_completion(1, net.clone(), peers); - // ... and check that there's no justification for block#1 - assert!(net.lock().peer(0).client().backend().blockchain().justification(BlockId::Number(1)).unwrap().is_some(), + // ... and check that there's justification for block#1 + assert!(net.lock().peer(0).client().justification(&BlockId::Number(1)).unwrap().is_some(), "Missing justification for block#1"); } @@ -704,8 +755,7 @@ fn justification_is_generated_periodically() { // when block#32 (justification_period) is finalized, justification // is required => generated for i in 0..3 { - assert!(net.lock().peer(i).client().backend().blockchain() - .justification(BlockId::Number(32)).unwrap().is_some()); + assert!(net.lock().peer(i).client().justification(&BlockId::Number(32)).unwrap().is_some()); } } @@ -811,7 +861,8 @@ fn allows_reimporting_change_blocks() { let client = net.peer(0).client().clone(); let (block_import, ..) = net.make_block_import(client.clone()); - let builder = client.new_block_at(&BlockId::Number(0)).unwrap(); + let full_client = client.as_full().expect("only full clients are used in test"); + let builder = full_client.new_block_at(&BlockId::Number(0)).unwrap(); let block = builder.bake().unwrap(); api.scheduled_changes.lock().insert(*block.header.parent_hash(), ScheduledChange { next_authorities: make_ids(peers_b), @@ -842,3 +893,23 @@ fn allows_reimporting_change_blocks() { ImportResult::AlreadyInChain ); } + +#[test] +fn justification_is_fetched_by_light_client_when_consensus_data_changes() { + let _ = ::env_logger::try_init(); + + let peers = &[Keyring::Alice]; + let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); + net.add_light_peer(&GrandpaTestNet::default_config()); + + // import block#1 WITH consensus data change + let new_authorities = vec![Ed25519AuthorityId::from([42; 32])]; + net.peer(0).push_authorities_change_block(new_authorities); + net.sync(); + let net = Arc::new(Mutex::new(net)); + run_to_completion(1, net.clone(), peers); + net.lock().sync(); + + // ... and check that the block#1 is finalized on light client + assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); +} diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index 49511b00c3d55..ce6e633aad052 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -35,6 +35,8 @@ type ExtraRequest = (::Hash, NumberFor); trait ExtraRequestsEssence { type Response; + /// Name of request type to display in logs. + fn type_name(&self) -> &'static str; /// Prepare network message corresponding to the request. fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message; /// Accept response. @@ -145,7 +147,7 @@ impl> ExtraRequests { let last_finalzied_hash = match protocol.client().info() { Ok(info) => info.chain.finalized_hash, Err(e) => { - debug!(target:"sync", "Cannot dispatch extra requests: error {:?} when reading blockchain", e); + debug!(target:"sync", "Cannot dispatch {} requests: error {:?} when reading blockchain", self.essence.type_name(), e); return; }, }; @@ -157,7 +159,7 @@ impl> ExtraRequests { }; // only ask peers that have synced past the block number that we're - // asking the justification for and to whom we haven't already made + // asking the extra data for and to whom we haven't already made // the same request recently let peer_eligible = { let request = match self.pending_requests.front() { @@ -199,7 +201,7 @@ impl> ExtraRequests { .expect("peer was is taken from available_peers; available_peers is a subset of peers; qed") .state = self.essence.peer_downloading_state(request.0); - trace!(target: "sync", "Requesting extra for block #{} from {}", request.0, peer); + trace!(target: "sync", "Requesting {} for block #{} from {}", self.essence.type_name(), request.0, peer); let request = self.essence.into_network_request(request, last_finalzied_hash); protocol.send_message(peer, request); @@ -207,8 +209,9 @@ impl> ExtraRequests { self.pending_requests.append(&mut unhandled_requests); - trace!(target: "sync", "Dispatched {} justification requests ({} pending)", + trace!(target: "sync", "Dispatched {} {} requests ({} pending)", initial_pending_requests - self.pending_requests.len(), + self.essence.type_name(), self.pending_requests.len(), ); } @@ -244,17 +247,20 @@ impl> ExtraRequests { if let Some(request) = self.peer_requests.remove(&who) { match self.essence.accept_response(request, import_queue, response) { ExtraResponseKind::Accepted => { + trace!(target: "sync", "Accepted {} response for {} from {}.", self.essence.type_name(), request.0, who); self.requests.remove(&request); self.previous_requests.remove(&request); return; }, ExtraResponseKind::Invalid => { + trace!(target: "sync", "Invalid {} provided for {} by {}", self.essence.type_name(), request.0, who); protocol.report_peer( who, - Severity::Bad(format!("Invalid extra data provided for #{}", request.0)), + Severity::Bad(format!("Invalid {} provided for {} by {}", self.essence.type_name(), request.0, who)), ); }, ExtraResponseKind::Missing => { + trace!(target: "sync", "Empty {} response for {} has provided by {}", self.essence.type_name(), request.0, who); self.previous_requests .entry(request) .or_insert(Vec::new()) @@ -263,6 +269,8 @@ impl> ExtraRequests { } self.pending_requests.push_front(request); + } else { + trace!(target: "sync", "Ignoring {} response from {}. No pending request.", self.essence.type_name(), who); } } @@ -287,6 +295,10 @@ struct JustificationsRequestsEssence; impl ExtraRequestsEssence for JustificationsRequestsEssence { type Response = Option; + fn type_name(&self) -> &'static str { + "justification" + } + fn into_network_request(&self, request: ExtraRequest, _last_finalzied_hash: B::Hash) -> Message { GenericMessage::BlockRequest(message::generic::BlockRequest { id: 0, @@ -320,6 +332,10 @@ struct FinalityProofRequestsEssence; impl ExtraRequestsEssence for FinalityProofRequestsEssence { type Response = Option>; + fn type_name(&self) -> &'static str { + "finality proof" + } + fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message { GenericMessage::FinalityProofRequest(message::generic::FinalityProofRequest { block: request.0, diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index 6e6d5cb974317..d36b5bc710f24 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -391,6 +391,10 @@ impl ChainSync { response.block, ); + trace!(target: "sync", "Invalid block finality data provided: requested: {:?} got: {:?}", + hash, + response.block, + ); protocol.report_peer(who, Severity::Bad(msg)); return; } @@ -401,6 +405,11 @@ impl ChainSync { protocol, &*self.import_queue, ); + } else { + trace!(target: "sync", "Ignoring finality proof response from {}. Peer state: {:?}", + who, + peer.state, + ); } } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index 3fd37e77335dd..d5fa6e8fddfdd 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -26,8 +26,10 @@ use std::sync::Arc; use std::time::Duration; use log::trace; -use client; +use crate::chain::FinalityProofProvider; +use client::{self, ClientInfo, BlockchainEvents, FinalityNotifications, in_mem::Backend as InMemoryBackend, error::Result as ClientResult}; use client::block_builder::BlockBuilder; +use client::backend::AuxStore; use crate::config::ProtocolConfig; use consensus::import_queue::{import_many_blocks, ImportQueue, ImportQueueStatus, IncomingBlock}; use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, Verifier}; @@ -42,7 +44,7 @@ use crate::message::Message; use network_libp2p::{NodeIndex, ProtocolId}; use parity_codec::Encode; use parking_lot::Mutex; -use primitives::{H256, Ed25519AuthorityId}; +use primitives::{H256, Ed25519AuthorityId, Blake2Hasher}; use crate::protocol::{Context, Protocol, ProtocolMsg, ProtocolStatus}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{AuthorityIdFor, Block as BlockT, Digest, DigestItem, Header, Zero, NumberFor}; @@ -211,7 +213,9 @@ impl> ImportQueue for SyncImpor justification: Justification, ) -> bool { self.justification_import.as_ref().map(|justification_import| { - justification_import.import_justification(hash, number, justification).is_ok() + justification_import.import_justification(hash, number, justification) + .map_err(|e| { trace!("Justification import failed with: {}", e); e }) + .is_ok() }).unwrap_or(false) } @@ -222,9 +226,10 @@ impl> ImportQueue for SyncImpor finality_proof: Vec, ) -> bool { self.finality_proof_import.as_ref().map(|finality_proof_import| { - finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier).is_ok() + finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier) + .map_err(|e| { trace!("Finality proof import failed with: {}", e); e }) + .is_ok() }).unwrap_or(false) - } } @@ -251,10 +256,82 @@ impl NetworkSpecialization for DummySpecialization { } } -pub type PeersClient = client::Client; +pub type PeersFullClient = client::Client; +pub type PeersLightClient = client::Client; + +#[derive(Clone)] +pub enum PeersClient { + Full(Arc), + Light(Arc), +} + +impl PeersClient { + pub fn as_full(&self) -> Option> { + match *self { + PeersClient::Full(ref client) => Some(client.clone()), + _ => None, + } + } + + pub fn as_block_import(&self) -> SharedBlockImport { + match *self { + PeersClient::Full(ref client) => client.clone() as _, + PeersClient::Light(ref client) => client.clone() as _, + } + } + + pub fn as_in_memory_backend(&self) -> InMemoryBackend { + match *self { + PeersClient::Full(ref client) => client.backend().as_in_memory(), + PeersClient::Light(_) => unimplemented!("TODO"), + } + } + + pub fn get_aux(&self, key: &[u8]) -> ClientResult>> { + match *self { + PeersClient::Full(ref client) => client.backend().get_aux(key), + PeersClient::Light(ref client) => client.backend().get_aux(key), + } + } + + pub fn info(&self) -> ClientResult> { + match *self { + PeersClient::Full(ref client) => client.info(), + PeersClient::Light(ref client) => client.info(), + } + } + + pub fn header(&self, block: &BlockId) -> ClientResult::Header>> { + match *self { + PeersClient::Full(ref client) => client.header(block), + PeersClient::Light(ref client) => client.header(block), + } + } + + pub fn justification(&self, block: &BlockId) -> ClientResult> { + match *self { + PeersClient::Full(ref client) => client.justification(block), + PeersClient::Light(ref client) => client.justification(block), + } + } + + pub fn finality_notification_stream(&self) -> FinalityNotifications { + match *self { + PeersClient::Full(ref client) => client.finality_notification_stream(), + PeersClient::Light(ref client) => client.finality_notification_stream(), + } + } + + pub fn finalize_block(&self, id: BlockId, justification: Option, notify: bool) -> ClientResult<()> { + match *self { + PeersClient::Full(ref client) => client.finalize_block(id, justification, notify), + PeersClient::Light(ref client) => client.finalize_block(id, justification, notify), + } + } +} pub struct Peer, D> { - client: Arc, + client: PeersClient, pub protocol_sender: Sender>, network_port: Mutex>, import_queue: Arc>, @@ -264,7 +341,7 @@ pub struct Peer, D> { impl, D> Peer { fn new( - client: Arc, + client: PeersClient, import_queue: Arc>, protocol_sender: Sender>, network_sender: NetworkChan, @@ -432,7 +509,7 @@ impl, D> Peer { /// Add blocks to the peer -- edit the block before adding pub fn generate_blocks(&self, count: usize, origin: BlockOrigin, edit_block: F) - where F: FnMut(BlockBuilder) -> Block + where F: FnMut(BlockBuilder) -> Block { let best_hash = self.client.info().unwrap().chain.best_hash; self.generate_blocks_at(BlockId::Hash(best_hash), count, origin, edit_block) @@ -441,10 +518,11 @@ impl, D> Peer { /// Add blocks to the peer -- edit the block before adding. The chain will /// start at the given block iD. pub fn generate_blocks_at(&self, mut at: BlockId, count: usize, origin: BlockOrigin, mut edit_block: F) - where F: FnMut(BlockBuilder) -> Block + where F: FnMut(BlockBuilder) -> Block { - for _ in 0..count { - let builder = self.client.new_block_at(&at).unwrap(); + let full_client = self.client.as_full().expect("blocks could only be generated by full clients"); + for _ in 0..count { + let builder = full_client.new_block_at(&at).unwrap(); let block = edit_block(builder); let hash = block.header.hash(); trace!( @@ -507,7 +585,7 @@ impl, D> Peer { } /// Get a reference to the client. - pub fn client(&self) -> &Arc { + pub fn client(&self) -> &PeersClient { &self.client } } @@ -532,7 +610,7 @@ pub trait TestNetFactory: Sized { /// These two need to be implemented! fn from_config(config: &ProtocolConfig) -> Self; - fn make_verifier(&self, client: Arc, config: &ProtocolConfig) -> Arc; + fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig) -> Arc; /// Get reference to peer. fn peer(&self, i: usize) -> &Peer; @@ -543,10 +621,15 @@ pub trait TestNetFactory: Sized { fn set_started(&mut self, now: bool); /// Get custom block import handle for fresh client, along with peer data. - fn make_block_import(&self, client: Arc) - -> (SharedBlockImport, Option>, Self::PeerData) + fn make_block_import(&self, client: PeersClient) + -> (SharedBlockImport, Option>, Option>, Self::PeerData) { - (client, None, Default::default()) + (client.as_block_import(), None, None, Default::default()) + } + + /// Get finality proof provider (if supported). + fn make_finality_proof_provider(&self, _client: PeersClient) -> Option>> { + None } fn default_config() -> ProtocolConfig { @@ -559,26 +642,61 @@ pub trait TestNetFactory: Sized { let mut net = Self::from_config(&config); for _ in 0..n { - net.add_peer(&config); + net.add_full_peer(&config); } net } - /// Add a peer. - fn add_peer(&mut self, config: &ProtocolConfig) { + /// Add a full peer. + fn add_full_peer(&mut self, config: &ProtocolConfig) { let client = Arc::new(test_client::new()); let tx_pool = Arc::new(EmptyTransactionPool); - let verifier = self.make_verifier(client.clone(), config); - let (block_import, justification_import, data) = self.make_block_import(client.clone()); + let verifier = self.make_verifier(PeersClient::Full(client.clone()), config); + let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Full(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); - let import_queue = Arc::new(SyncImportQueue::new(verifier, block_import, justification_import, None)); + let import_queue = Arc::new(SyncImportQueue::new(verifier, block_import, justification_import, finality_proof_import)); let specialization = DummySpecialization { }; let protocol_sender = Protocol::new( network_sender.clone(), config.clone(), client.clone(), + self.make_finality_proof_provider(PeersClient::Full(client.clone())), + import_queue.clone(), None, + tx_pool, + specialization, + ).unwrap(); + + let peer = Arc::new(Peer::new( + PeersClient::Full(client), + import_queue, + protocol_sender, + network_sender, + network_port, + data, + )); + + self.mut_peers(|peers| { + peers.push(peer.clone()) + }); + } + + /// Add a light peer. + fn add_light_peer(&mut self, config: &ProtocolConfig) { + let client = Arc::new(test_client::new_light()); + let tx_pool = Arc::new(EmptyTransactionPool); + let verifier = self.make_verifier(PeersClient::Light(client.clone()), config); + let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Light(client.clone())); + let (network_sender, network_port) = network_channel(ProtocolId::default()); + + let import_queue = Arc::new(SyncImportQueue::new(verifier, block_import, justification_import, finality_proof_import)); + let specialization = DummySpecialization { }; + let protocol_sender = Protocol::new( + network_sender.clone(), + config.clone(), + client.clone(), + self.make_finality_proof_provider(PeersClient::Light(client.clone())), import_queue.clone(), None, tx_pool, @@ -586,7 +704,7 @@ pub trait TestNetFactory: Sized { ).unwrap(); let peer = Arc::new(Peer::new( - client, + PeersClient::Light(client), import_queue, protocol_sender, network_sender, @@ -763,7 +881,7 @@ impl TestNetFactory for TestNet { } } - fn make_verifier(&self, _client: Arc, _config: &ProtocolConfig) + fn make_verifier(&self, _client: PeersClient, _config: &ProtocolConfig) -> Arc { Arc::new(PassThroughVerifier(false)) @@ -790,7 +908,7 @@ impl TestNetFactory for TestNet { } } -pub struct ForceFinalized(Arc); +pub struct ForceFinalized(PeersClient); impl JustificationImport for ForceFinalized { type Error = ConsensusError; @@ -816,7 +934,7 @@ impl TestNetFactory for JustificationTestNet { JustificationTestNet(TestNet::from_config(config)) } - fn make_verifier(&self, client: Arc, config: &ProtocolConfig) + fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig) -> Arc { self.0.make_verifier(client, config) @@ -842,9 +960,9 @@ impl TestNetFactory for JustificationTestNet { self.0.set_started(new) } - fn make_block_import(&self, client: Arc) - -> (SharedBlockImport, Option>, Self::PeerData) + fn make_block_import(&self, client: PeersClient) + -> (SharedBlockImport, Option>, Option>, Self::PeerData) { - (client.clone(), Some(Arc::new(ForceFinalized(client))), Default::default()) + (client.as_block_import(), Some(Arc::new(ForceFinalized(client))), None, Default::default()) } } diff --git a/core/network/src/test/sync.rs b/core/network/src/test/sync.rs index edf670e82646d..23aedf34b8e5f 100644 --- a/core/network/src/test/sync.rs +++ b/core/network/src/test/sync.rs @@ -15,7 +15,6 @@ // along with Substrate. If not, see . use client::backend::Backend; -use client::blockchain::HeaderBackend as BlockchainHeaderBackend; use crate::config::Roles; use consensus::BlockOrigin; use network_libp2p::NodeIndex; @@ -31,8 +30,8 @@ fn sync_from_two_peers_works() { net.peer(1).push_blocks(100, false); net.peer(2).push_blocks(100, false); net.sync(); - assert!(net.peer(0).client.backend().as_in_memory().blockchain() - .equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); + assert!(net.peer(0).client.as_in_memory_backend().blockchain() + .equals_to(net.peer(1).client.as_in_memory_backend().blockchain())); let status = net.peer(0).status(); assert_eq!(status.sync.state, SyncState::Idle); } @@ -46,8 +45,8 @@ fn sync_from_two_peers_with_ancestry_search_works() { net.peer(2).push_blocks(100, false); net.restart_peer(0); net.sync(); - assert!(net.peer(0).client.backend().as_in_memory().blockchain() - .canon_equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); + assert!(net.peer(0).client.as_in_memory_backend().blockchain() + .canon_equals_to(net.peer(1).client.as_in_memory_backend().blockchain())); } #[test] @@ -60,8 +59,8 @@ fn sync_long_chain_works() { net.sync(); // Wait for peers to get up to speed. thread::sleep(time::Duration::from_millis(1000)); - assert!(net.peer(0).client.backend().as_in_memory().blockchain() - .equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); + assert!(net.peer(0).client.as_in_memory_backend().blockchain() + .equals_to(net.peer(1).client.as_in_memory_backend().blockchain())); } #[test] @@ -71,8 +70,8 @@ fn sync_no_common_longer_chain_fails() { net.peer(0).push_blocks(20, true); net.peer(1).push_blocks(20, false); net.sync(); - assert!(!net.peer(0).client.backend().as_in_memory().blockchain() - .canon_equals_to(net.peer(1).client.backend().as_in_memory().blockchain())); + assert!(!net.peer(0).client.as_in_memory_backend().blockchain() + .canon_equals_to(net.peer(1).client.as_in_memory_backend().blockchain())); } #[test] @@ -115,11 +114,11 @@ fn sync_after_fork_works() { net.peer(2).push_blocks(1, false); // peer 1 has the best chain - let peer1_chain = net.peer(1).client.backend().as_in_memory().blockchain().clone(); + let peer1_chain = net.peer(1).client.as_in_memory_backend().blockchain().clone(); net.sync(); - assert!(net.peer(0).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); - assert!(net.peer(1).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); - assert!(net.peer(2).client.backend().as_in_memory().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(0).client.as_in_memory_backend().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(1).client.as_in_memory_backend().blockchain().canon_equals_to(&peer1_chain)); + assert!(net.peer(2).client.as_in_memory_backend().blockchain().canon_equals_to(&peer1_chain)); } #[test] @@ -135,8 +134,8 @@ fn syncs_all_forks() { net.sync(); // Check that all peers have all of the blocks. - assert_eq!(9, net.peer(0).client.backend().as_in_memory().blockchain().blocks_count()); - assert_eq!(9, net.peer(1).client.backend().as_in_memory().blockchain().blocks_count()); + assert_eq!(9, net.peer(0).client.as_in_memory_backend().blockchain().blocks_count()); + assert_eq!(9, net.peer(1).client.as_in_memory_backend().blockchain().blocks_count()); } #[test] @@ -149,11 +148,11 @@ fn own_blocks_are_announced() { let header = net.peer(0).client().header(&BlockId::Number(1)).unwrap().unwrap(); net.peer(0).on_block_imported(header.hash(), &header); net.sync(); - assert_eq!(net.peer(0).client.backend().blockchain().info().unwrap().best_number, 1); - assert_eq!(net.peer(1).client.backend().blockchain().info().unwrap().best_number, 1); - let peer0_chain = net.peer(0).client.backend().as_in_memory().blockchain().clone(); - assert!(net.peer(1).client.backend().as_in_memory().blockchain().canon_equals_to(&peer0_chain)); - assert!(net.peer(2).client.backend().as_in_memory().blockchain().canon_equals_to(&peer0_chain)); + assert_eq!(net.peer(0).client.info().unwrap().chain.best_number, 1); + assert_eq!(net.peer(1).client.info().unwrap().chain.best_number, 1); + let peer0_chain = net.peer(0).client.as_in_memory_backend().blockchain().clone(); + assert!(net.peer(1).client.as_in_memory_backend().blockchain().canon_equals_to(&peer0_chain)); + assert!(net.peer(2).client.as_in_memory_backend().blockchain().canon_equals_to(&peer0_chain)); } #[test] @@ -165,9 +164,9 @@ fn blocks_are_not_announced_by_light_nodes() { // light peer1 is connected to full peer2 let mut light_config = ProtocolConfig::default(); light_config.roles = Roles::LIGHT; - net.add_peer(&ProtocolConfig::default()); - net.add_peer(&light_config); - net.add_peer(&ProtocolConfig::default()); + net.add_full_peer(&ProtocolConfig::default()); + net.add_full_peer(&light_config); + net.add_full_peer(&ProtocolConfig::default()); net.peer(0).push_blocks(1, false); net.peer(0).start(); @@ -185,9 +184,9 @@ fn blocks_are_not_announced_by_light_nodes() { // peer 0 has the best chain // peer 1 has the best chain // peer 2 has genesis-chain only - assert_eq!(net.peer(0).client.backend().blockchain().info().unwrap().best_number, 1); - assert_eq!(net.peer(1).client.backend().blockchain().info().unwrap().best_number, 1); - assert_eq!(net.peer(2).client.backend().blockchain().info().unwrap().best_number, 0); + assert_eq!(net.peer(0).client.info().unwrap().chain.best_number, 1); + assert_eq!(net.peer(1).client.info().unwrap().chain.best_number, 1); + assert_eq!(net.peer(2).client.info().unwrap().chain.best_number, 0); } #[test] diff --git a/core/service/src/components.rs b/core/service/src/components.rs index 7ba9480f4101e..be2d73ba83fc1 100644 --- a/core/service/src/components.rs +++ b/core/service/src/components.rs @@ -72,7 +72,7 @@ pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecu client_db::light::LightStorage<::Block>, network::OnDemand<::Block> >, - network::OnDemand<::Block> + network::OnDemand<::Block>, >, client::LocalCallExecutor< client::light::backend::Backend< diff --git a/core/test-client/src/lib.rs b/core/test-client/src/lib.rs index bd2d3a298a044..99f1cc944924f 100644 --- a/core/test-client/src/lib.rs +++ b/core/test-client/src/lib.rs @@ -87,7 +87,7 @@ pub type LightExecutor = client::light::call_executor::RemoteOrLocalCallExecutor client_db::light::LightStorage, LightFetcher >, - LightFetcher + LightFetcher, >, client::LocalCallExecutor< client::light::backend::Backend< diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index a276ebda64ef7..bed7e8fb0c49f 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -157,7 +157,7 @@ construct_service_factory! { .map(|fetcher| fetcher.checker().clone()) .ok_or_else(|| "Trying to start light import queue without active fetch checker")?; let block_import = Arc::new(grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>( - client.clone(), fetch_checker, client.clone() + client.clone(), Arc::new(fetch_checker), client.clone() )?); import_queue( @@ -171,7 +171,7 @@ construct_service_factory! { ).map_err(Into::into) }}, FinalityProofProvider = { |client: Arc>| { - Ok(Some(Arc::new(GrandpaFinalityProofProvider::new(client)) as _)) + Ok(Some(Arc::new(GrandpaFinalityProofProvider::new(client.clone(), client)) as _)) }}, } } From 8577c87f46754ff0aa563338b5b41c9cc021b6aa Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 7 Mar 2019 16:50:44 +0300 Subject: [PATCH 18/42] restore justification_is_fetched_by_light_client_when_consensus_data_changes --- core/finality-grandpa/src/tests.rs | 11 +++++------ core/network/src/test/mod.rs | 12 ++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index bbd9159362878..d10636df3604b 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -1057,22 +1057,21 @@ fn allows_reimporting_change_blocks() { #[test] fn justification_is_fetched_by_light_client_when_consensus_data_changes() { - /* TODO: fix me - let _ = ::env_logger::try_init(); let peers = &[Keyring::Alice]; let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); - net.add_light_peer(&GrandpaTestNet::default_config()); - // import block#1 WITH consensus data change + // import block#1 WITH consensus data change + ensure if is finalized (with justification) let new_authorities = vec![Ed25519AuthorityId::from([42; 32])]; net.peer(0).push_authorities_change_block(new_authorities); - net.sync(); let net = Arc::new(Mutex::new(net)); run_to_completion(1, net.clone(), peers); + + // add && sync light peer + net.lock().add_light_peer(&GrandpaTestNet::default_config()); net.lock().sync(); // ... and check that the block#1 is finalized on light client - assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1);*/ + assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index 364b2cedbda18..dd0bcc5c63747 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -32,7 +32,7 @@ use crate::chain::FinalityProofProvider; use client::{self, ClientInfo, BlockchainEvents, FinalityNotifications, in_mem::Backend as InMemoryBackend, error::Result as ClientResult}; use client::block_builder::BlockBuilder; use client::backend::AuxStore; -use crate::config::ProtocolConfig; +use crate::config::{ProtocolConfig, Roles}; use consensus::import_queue::{BasicQueue, ImportQueue, IncomingBlock}; use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, Verifier}; use consensus::{Error as ConsensusError, ErrorKind as ConsensusErrorKind}; @@ -695,9 +695,12 @@ pub trait TestNetFactory: Sized { /// Add a light peer. fn add_light_peer(&mut self, config: &ProtocolConfig) { + let mut config = config.clone(); + config.roles = Roles::LIGHT; + let client = Arc::new(test_client::new_light()); let tx_pool = Arc::new(EmptyTransactionPool); - let verifier = self.make_verifier(PeersClient::Light(client.clone()), config); + let verifier = self.make_verifier(PeersClient::Light(client.clone()), &config); let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Light(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); @@ -714,7 +717,7 @@ pub trait TestNetFactory: Sized { is_major_syncing.clone(), peers.clone(), network_sender.clone(), - config.clone(), + config, client.clone(), self.make_finality_proof_provider(PeersClient::Light(client.clone())), import_queue.clone(), @@ -780,7 +783,8 @@ pub trait TestNetFactory: Sized { } peers[recipient].receive_message(peer as NodeIndex, packet) } - Some(NetworkMsg::ReportPeer(who, _)) => { + Some(NetworkMsg::ReportPeer(who, reason)) => { + trace!("Disconnecting test peer {} from {}: {}", who, peer, reason); to_disconnect.insert(who); } Some(_msg) => continue, From 8a3ba3dfdb993f56d0898bdc3ce38d66b726204d Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Mar 2019 12:54:33 +0300 Subject: [PATCH 19/42] some more tests --- core/finality-grandpa/src/light_import.rs | 131 +++++++++++++++++----- core/finality-grandpa/src/tests.rs | 14 +-- 2 files changed, 106 insertions(+), 39 deletions(-) diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 7529298f41322..1875abdc7beba 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -20,7 +20,7 @@ use parking_lot::RwLock; use client::{ CallExecutor, Client, - backend::Backend, + backend::{AuxStore, Backend}, blockchain::HeaderBackend, error::Error as ClientError, error::ErrorKind as ClientErrorKind, }; @@ -63,38 +63,11 @@ pub fn light_block_import, RA, PRA>( PRA: ProvideRuntimeApi, PRA::Api: GrandpaApi, { - use runtime_primitives::traits::Zero; - let authority_set = match Backend::get_aux(&**client.backend(), LIGHT_AUTHORITY_SET_KEY)? { - None => { - info!(target: "afg", "Loading GRANDPA authorities \ - from genesis on what appears to be first startup."); - - // no authority set on disk: fetch authorities from genesis state - let genesis_authorities = api.runtime_api().grandpa_authorities(&BlockId::number(Zero::zero()))?; - - let authority_set = LightAuthoritySet::genesis(genesis_authorities); - let encoded = authority_set.encode(); - Backend::insert_aux(&**client.backend(), &[(LIGHT_AUTHORITY_SET_KEY, &encoded[..])], &[])?; - - authority_set - }, - Some(raw) => LightAuthoritySet::decode(&mut &raw[..]) - .ok_or_else(|| ::client::error::ErrorKind::Backend( - format!("GRANDPA authority set kept in invalid format") - ))? - .into(), - }; - - let consensus_changes = load_decode(&**client.backend(), LIGHT_CONSENSUS_CHANGES_KEY)? - .unwrap_or_else(ConsensusChanges::>::empty); - + let import_data = load_aux_import_data(&**client.backend(), api)?; Ok(GrandpaLightBlockImport { client, authority_set_provider, - data: Arc::new(RwLock::new(LightImportData { - authority_set, - consensus_changes, - })), + data: Arc::new(RwLock::new(import_data)), }) } @@ -436,6 +409,52 @@ fn do_finalize_block, RA>( Ok(ImportResult::imported()) } +/// Load light impoty aux data from the store. +fn load_aux_import_data, PRA>( + aux_store: &B, + api: Arc, +) -> Result, ClientError> + where + B: AuxStore, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi, +{ + use runtime_primitives::traits::Zero; + let authority_set = match load_decode(aux_store, LIGHT_AUTHORITY_SET_KEY)? { + Some(authority_set) => authority_set, + None => { + info!(target: "afg", "Loading GRANDPA authorities \ + from genesis on what appears to be first startup."); + + // no authority set on disk: fetch authorities from genesis state + let genesis_authorities = api.runtime_api().grandpa_authorities(&BlockId::number(Zero::zero()))?; + + let authority_set = LightAuthoritySet::genesis(genesis_authorities); + let encoded = authority_set.encode(); + aux_store.insert_aux(&[(LIGHT_AUTHORITY_SET_KEY, &encoded[..])], &[])?; + + authority_set + }, + }; + + let consensus_changes = match load_decode(aux_store, LIGHT_CONSENSUS_CHANGES_KEY)? { + Some(consensus_changes) => consensus_changes, + None => { + let consensus_changes = ConsensusChanges::>::empty(); + + let encoded = authority_set.encode(); + aux_store.insert_aux(&[(LIGHT_CONSENSUS_CHANGES_KEY, &encoded[..])], &[])?; + + consensus_changes + }, + }; + + Ok(LightImportData { + authority_set, + consensus_changes, + }) +} + /// Insert into aux store. If failed, return error && show inconsistency warning. fn require_insert_aux, RA>( client: &Client, @@ -463,3 +482,55 @@ fn on_post_finalization_error(error: ClientError, value_type: &str) -> Consensus warn!(target: "finality", "Node is in a potentially inconsistent state."); ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string())) } + +#[cfg(test)] +mod tests { + use super::*; + use substrate_primitives::H256; + use test_client::client::in_mem::Blockchain as InMemoryAuxStore; + use test_client::runtime::Block; + use crate::tests::TestApi; + + #[test] + fn aux_data_updated_on_start() { + let aux_store = InMemoryAuxStore::::new(); + let api = Arc::new(TestApi::new(vec![(Ed25519AuthorityId([1; 32]), 1)])); + + // when aux store is empty initially + assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_none()); + assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_none()); + + // it is updated on importer start + load_aux_import_data(&aux_store, api).unwrap(); + assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_some()); + assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_some()); + } + + #[test] + fn aux_data_loaded_on_restart() { + let aux_store = InMemoryAuxStore::::new(); + let api = Arc::new(TestApi::new(vec![(Ed25519AuthorityId([1; 32]), 1)])); + + // when aux store is non-empty initially + let mut consensus_changes = ConsensusChanges::::empty(); + consensus_changes.note_change((42, Default::default())); + aux_store.insert_aux( + &[ + ( + LIGHT_AUTHORITY_SET_KEY, + LightAuthoritySet::genesis(vec![(Ed25519AuthorityId([42; 32]), 2)]).encode().as_slice(), + ), + ( + LIGHT_CONSENSUS_CHANGES_KEY, + consensus_changes.encode().as_slice(), + ), + ], + &[], + ).unwrap(); + + // importer uses it on start + let data = load_aux_import_data(&aux_store, api).unwrap(); + assert_eq!(data.authority_set.authorities(), vec![(Ed25519AuthorityId([42; 32]), 2)]); + assert_eq!(data.consensus_changes.pending_changes(), &[(42, Default::default())]); + } +} diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index d10636df3604b..fc00f9432bd04 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -261,14 +261,14 @@ impl Network for MessageRouting { } #[derive(Default, Clone)] -struct TestApi { +pub(crate) struct TestApi { genesis_authorities: Vec<(Ed25519AuthorityId, u64)>, scheduled_changes: Arc>>>, forced_changes: Arc)>>>, } impl TestApi { - fn new(genesis_authorities: Vec<(Ed25519AuthorityId, u64)>) -> Self { + pub fn new(genesis_authorities: Vec<(Ed25519AuthorityId, u64)>) -> Self { TestApi { genesis_authorities, scheduled_changes: Arc::new(Mutex::new(HashMap::new())), @@ -277,7 +277,7 @@ impl TestApi { } } -struct RuntimeApi { +pub(crate) struct RuntimeApi { inner: TestApi, } @@ -352,11 +352,7 @@ impl GrandpaApi for RuntimeApi { _: Option<()>, _: Vec, ) -> Result>> { -// if at == &BlockId::Number(0) { - Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native) -/* } else { - panic!("should generally only request genesis authorities") - }*/ + Ok(self.inner.genesis_authorities.clone()).map(NativeOrEncoded::Native) } fn grandpa_pending_change_runtime_api_impl( @@ -1060,7 +1056,7 @@ fn justification_is_fetched_by_light_client_when_consensus_data_changes() { let _ = ::env_logger::try_init(); let peers = &[Keyring::Alice]; - let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); + let net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); // import block#1 WITH consensus data change + ensure if is finalized (with justification) let new_authorities = vec![Ed25519AuthorityId::from([42; 32])]; From b36251850d7a51407196952e5abcaeeb6843b5dc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Mar 2019 13:27:38 +0300 Subject: [PATCH 20/42] added authorities-related TODO --- core/client/src/client.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/core/client/src/client.rs b/core/client/src/client.rs index 2322e236100c7..4df53e26af984 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -276,6 +276,7 @@ impl Client where ) -> error::Result { if backend.blockchain().header(BlockId::Number(Zero::zero()))?.is_none() { let (genesis_storage, children_genesis_storage) = build_genesis_storage.build_storage()?; + // TODO: this should be moved in #1412 let genesis_authorities_len: Option = genesis_storage .get(well_known_keys::AUTHORITY_COUNT) .and_then(|v| Decode::decode(&mut &v[..])); From 4c69880f9e65c7b149ebf1dd37afb290813bc912 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Mar 2019 13:34:36 +0300 Subject: [PATCH 21/42] removed unneeded clear_finality_proof_requests field --- core/consensus/common/src/block_import.rs | 3 --- core/consensus/common/src/import_queue.rs | 2 -- core/network/src/protocol.rs | 3 --- core/network/src/service.rs | 4 ---- core/network/src/sync.rs | 5 ----- 5 files changed, 17 deletions(-) diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index ff169efb8f987..7fa48d0209fba 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -42,8 +42,6 @@ pub struct ImportedAux { pub clear_justification_requests: bool, /// Request a justification for the given block. pub needs_justification: bool, - /// Clear all pending finality proof requests. TODO: do we need this??????????????????????!!!!!!!!!!!!!!!!!!!!!!!!! - pub clear_finality_proof_requests: bool, /// Request a finality proof for the given block. pub needs_finality_proof: bool, } @@ -53,7 +51,6 @@ impl Default for ImportedAux { ImportedAux { clear_justification_requests: false, needs_justification: false, - clear_finality_proof_requests: false, needs_finality_proof: false, } } diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index e224743d30aea..1a612ab33c26a 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -512,8 +512,6 @@ pub trait Link: Send { fn request_justification(&self, _hash: &B::Hash, _number: NumberFor) {} /// Finality proof import result. fn finality_proof_imported(&self, _who: Origin, _hash: &B::Hash, _number: NumberFor, _success: bool) {} - /// Clear all pending finality proof requests. - fn clear_finality_proof_requests(&self) {} /// Request a finality proof for the given block. fn request_finality_proof(&self, _hash: &B::Hash, _number: NumberFor) {} /// Disconnect from peer. diff --git a/core/network/src/protocol.rs b/core/network/src/protocol.rs index 5a9cab4a8dd15..c9714bd304a32 100644 --- a/core/network/src/protocol.rs +++ b/core/network/src/protocol.rs @@ -230,8 +230,6 @@ pub enum ProtocolMsg> { RequestJustification(B::Hash, NumberFor), /// Inform protocol whether a justification was successfully imported. JustificationImportResult(B::Hash, NumberFor, bool), - /// Tell protocol to clear all pending finality proof requests. - ClearFinalityProofRequests, /// Tell protocol to request finality proof for a block. RequestFinalityProof(B::Hash, NumberFor), /// Inform protocol whether a finality proof was successfully imported. @@ -412,7 +410,6 @@ impl, H: ExHashT> Protocol { self.sync.request_justification(&hash, number, &mut context); }, ProtocolMsg::JustificationImportResult(hash, number, success) => self.sync.justification_import_result(hash, number, success), - ProtocolMsg::ClearFinalityProofRequests => self.sync.clear_finality_proof_requests(), ProtocolMsg::RequestFinalityProof(hash, number) => { let mut context = ProtocolContext::new(&mut self.context_data, &self.network_chan); diff --git a/core/network/src/service.rs b/core/network/src/service.rs index 1505e0a145463..a74e556fdcfc7 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -109,10 +109,6 @@ impl> Link for NetworkLink { let _ = self.protocol_sender.send(ProtocolMsg::RequestJustification(hash.clone(), number)); } - fn clear_finality_proof_requests(&self) { - let _ = self.protocol_sender.send(ProtocolMsg::ClearFinalityProofRequests); - } - fn request_finality_proof(&self, hash: &B::Hash, number: NumberFor) { let _ = self.protocol_sender.send(ProtocolMsg::RequestFinalityProof(hash.clone(), number)); } diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index bf0cd62bb540a..8e043198ed366 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -515,11 +515,6 @@ impl ChainSync { self.extra_requests.finality_proofs().dispatch(&mut self.peers, protocol); } - /// Clears all pending finality proof requests. - pub fn clear_finality_proof_requests(&mut self) { - self.extra_requests.finality_proofs().clear(); - } - pub fn finality_proof_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) { self.extra_requests.justifications().on_import_result(hash, number, success); } From 0db08a06cb4c95992794d9be4fbca22ad72df3ca Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 11 Mar 2019 13:43:15 +0300 Subject: [PATCH 22/42] truncated some long lines --- core/client/db/src/light.rs | 40 ++++++++++++++++++----- core/consensus/aura/src/lib.rs | 4 ++- core/consensus/common/src/import_queue.rs | 8 ++++- core/finality-grandpa/src/light_import.rs | 8 ++++- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 63bc9aab5866c..f67288c1894eb 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -874,32 +874,50 @@ pub(crate) mod tests { // \> B6_1_2(6) -> B6_1_3(7) let hash7 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash6, 7)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); let hash8 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash7, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); let hash6_1 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6, 7)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); let hash6_1_1 = insert_non_best_block(&db, Some(vec![[5u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); let hash6_1_2 = insert_non_best_block(&db, Some(vec![[6u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_2)), Some(vec![[6u8; 32].into()])); let hash6_2 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6_1, 8)); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); @@ -913,7 +931,10 @@ pub(crate) mod tests { { // finalize block hash6_1 db.finalize_header(BlockId::Hash(hash6_1)).unwrap(); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); @@ -922,7 +943,10 @@ pub(crate) mod tests { assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_2)), Some(vec![[4u8; 32].into()])); // finalize block hash6_2 db.finalize_header(BlockId::Hash(hash6_2)).unwrap(); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + assert_eq!( + db.cache().authorities_at(BlockId::Hash(hash6)), + Some(vec![[1u8; 32].into(), [2u8; 32].into()]), + ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index f519d60d9711c..ab33a8e1afd71 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -32,7 +32,9 @@ use parity_codec::Encode; use consensus_common::{ Authorities, BlockImport, Environment, Proposer, ForkChoiceStrategy }; -use consensus_common::import_queue::{Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport}; +use consensus_common::import_queue::{ + Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, +}; use client::ChainHead; use client::block_builder::api::{BlockBuilder as BlockBuilderApi, self as block_builder_api}; use client::runtime_api::ApiExt; diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 1a612ab33c26a..f1e1035299c2b 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -148,7 +148,13 @@ impl BasicQueue { ) -> Self { let (result_sender, result_port) = channel::unbounded(); let worker_sender = BlockImportWorker::new(result_sender, verifier.clone(), block_import); - let importer_sender = BlockImporter::new(result_port, worker_sender, verifier, justification_import, finality_proof_import); + let importer_sender = BlockImporter::new( + result_port, + worker_sender, + verifier, + justification_import, + finality_proof_import, + ); Self { sender: importer_sender, diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 1875abdc7beba..e28f39513a171 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -293,7 +293,13 @@ fn do_import_finality_proof, RA>( let finalized_block_number = client.backend().blockchain() .expect_block_number_from_id(&BlockId::Hash(finality_effects.block)) .map_err(|e| ConsensusError::from(ConsensusErrorKind::ClientImport(e.to_string())))?; - do_finalize_block(client, data, finalized_block_hash, finalized_block_number, finality_effects.justification.encode())?; + do_finalize_block( + client, + data, + finalized_block_hash, + finalized_block_number, + finality_effects.justification.encode(), + )?; // apply new authorities set data.authority_set.update( From 131513012e55673c9ed841b455edd3c119915727 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 12 Mar 2019 10:36:15 +0300 Subject: [PATCH 23/42] more granular light import tests --- core/finality-grandpa/src/finality_proof.rs | 55 ++++++---- core/finality-grandpa/src/light_import.rs | 110 +++++++++++++++++--- 2 files changed, 130 insertions(+), 35 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 515fd98ceef99..80d66e1863220 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -40,7 +40,7 @@ use client::{ }; use parity_codec::{Encode, Decode}; use grandpa::BlockNumberOps; -use runtime_primitives::generic::BlockId; +use runtime_primitives::{Justification, generic::BlockId}; use runtime_primitives::traits::{ NumberFor, Block as BlockT, Header as HeaderT, One, }; @@ -475,9 +475,22 @@ impl AuthoritiesOrEffects
{ } /// Justification used to prove block finality. -trait ProvableJustification: Encode + Decode { +pub(crate) trait ProvableJustification: Encode + Decode { /// Verify justification with respect to authorities set and authorities set id. fn verify(&self, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()>; + + /// Decode and verify justification. + fn decode_and_verify( + justification: Justification, + set_id: u64, + authorities: &[(Ed25519AuthorityId, u64)], + ) -> ClientResult { + let justification = Self::decode(&mut &*justification).ok_or_else(|| { + ClientError::from(ClientErrorKind::JustificationDecode) + })?; + justification.verify(set_id, authorities)?; + Ok(justification) + } } impl> ProvableJustification for GrandpaJustification @@ -490,7 +503,7 @@ impl> ProvableJustification for GrandpaJ } #[cfg(test)] -mod tests { +pub(crate) mod tests { use test_client::runtime::{Block, Header, H256}; use test_client::client::{backend::NewBlockState}; use test_client::client::in_mem::Blockchain as InMemoryBlockchain; @@ -529,11 +542,15 @@ mod tests { } #[derive(Debug, PartialEq, Encode, Decode)] - struct ValidJustification(Vec); + pub struct TestJustification(pub bool, pub Vec); - impl ProvableJustification
for ValidJustification { + impl ProvableJustification
for TestJustification { fn verify(&self, _set_id: u64, _authorities: &[(Ed25519AuthorityId, u64)]) -> ClientResult<()> { - Ok(()) + if self.0 { + Ok(()) + } else { + Err(ClientErrorKind::BadJustification("test".into()).into()) + } } } @@ -759,7 +776,7 @@ mod tests { let blockchain = test_blockchain(); // when we can't decode proof from Vec - do_check_finality_proof::<_, _, ValidJustification>( + do_check_finality_proof::<_, _, TestJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], @@ -773,12 +790,12 @@ mod tests { let blockchain = test_blockchain(); // when decoded proof has zero length - do_check_finality_proof::<_, _, ValidJustification>( + do_check_finality_proof::<_, _, TestJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), - Vec::::new().encode(), + Vec::::new().encode(), ).unwrap_err(); } @@ -787,19 +804,19 @@ mod tests { let blockchain = test_blockchain(); // when intermediate (#0) fragment has non-empty unknown headers - do_check_finality_proof::<_, _, ValidJustification>( + do_check_finality_proof::<_, _, TestJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), vec![FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![7]).encode(), + justification: TestJustification(true, vec![7]).encode(), unknown_headers: vec![header(4)], authorities_proof: Some(vec![vec![42]]), }, FinalityProofFragment { block: header(5).hash(), - justification: ValidJustification(vec![8]).encode(), + justification: TestJustification(true, vec![8]).encode(), unknown_headers: vec![header(5)], authorities_proof: None, }].encode(), @@ -811,19 +828,19 @@ mod tests { let blockchain = test_blockchain(); // when intermediate (#0) fragment has empty authorities proof - do_check_finality_proof::<_, _, ValidJustification>( + do_check_finality_proof::<_, _, TestJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], &ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")), vec![FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![7]).encode(), + justification: TestJustification(true, vec![7]).encode(), unknown_headers: Vec::new(), authorities_proof: None, }, FinalityProofFragment { block: header(5).hash(), - justification: ValidJustification(vec![8]).encode(), + justification: TestJustification(true, vec![8]).encode(), unknown_headers: vec![header(5)], authorities_proof: None, }].encode(), @@ -834,19 +851,19 @@ mod tests { fn finality_proof_check_works() { let blockchain = test_blockchain(); - let effects = do_check_finality_proof::<_, _, ValidJustification>( + let effects = do_check_finality_proof::<_, _, TestJustification>( &blockchain, 1, vec![(Ed25519AuthorityId([3u8; 32]), 1u64)], &ClosureAuthoritySetForFinalityChecker(|_, _, _| Ok(vec![(Ed25519AuthorityId([4u8; 32]), 1u64)])), vec![FinalityProofFragment { block: header(2).hash(), - justification: ValidJustification(vec![7]).encode(), + justification: TestJustification(true, vec![7]).encode(), unknown_headers: Vec::new(), authorities_proof: Some(vec![vec![42]]), }, FinalityProofFragment { block: header(4).hash(), - justification: ValidJustification(vec![8]).encode(), + justification: TestJustification(true, vec![8]).encode(), unknown_headers: vec![header(4)], authorities_proof: None, }].encode(), @@ -854,7 +871,7 @@ mod tests { assert_eq!(effects, FinalityEffects { headers_to_import: vec![header(4)], block: header(4).hash(), - justification: ValidJustification(vec![8]).encode(), + justification: TestJustification(true, vec![8]).encode(), new_set_id: 2, new_authorities: vec![(Ed25519AuthorityId([4u8; 32]), 1u64)], }); diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index e28f39513a171..d4f704fb37ca2 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -42,7 +42,7 @@ use substrate_primitives::{H256, Ed25519AuthorityId, Blake2Hasher}; use crate::aux_schema::load_decode; use crate::consensus_changes::ConsensusChanges; use crate::environment::canonical_at_height; -use crate::finality_proof::AuthoritySetForFinalityChecker; +use crate::finality_proof::{AuthoritySetForFinalityChecker, ProvableJustification}; use crate::justification::GrandpaJustification; /// LightAuthoritySet is saved under this key in aux storage. @@ -111,7 +111,7 @@ impl, RA> BlockImport block: ImportBlock, new_authorities: Option>, ) -> Result { - do_import_block(&*self.client, &mut *self.data.write(), block, new_authorities) + do_import_block::<_, _, _, _, GrandpaJustification>(&*self.client, &mut *self.data.write(), block, new_authorities) } fn check_block( @@ -155,7 +155,7 @@ impl, RA> FinalityProofImport finality_proof: Vec, verifier: &Verifier, ) -> Result<(), Self::Error> { - do_import_finality_proof( + do_import_finality_proof::<_, _, _, _, GrandpaJustification>( &*self.client, &*self.authority_set_provider, &mut *self.data.write(), @@ -195,7 +195,7 @@ impl LightAuthoritySet { } /// Try to import new block. -fn do_import_block, RA>( +fn do_import_block, RA, J>( client: &Client, data: &mut LightImportData, mut block: ImportBlock, @@ -208,6 +208,7 @@ fn do_import_block, RA>( NumberFor: grandpa::BlockNumberOps, DigestFor: Encode, DigestItemFor: DigestItem, + J: ProvableJustification, { let hash = block.post_header().hash(); let number = block.header.number().clone(); @@ -232,7 +233,7 @@ fn do_import_block, RA>( hash, ); - do_import_justification(client, data, hash, number, justification)?; + do_import_justification::<_, _, _, _, J>(client, data, hash, number, justification) }, None if enacts_consensus_change => { trace!( @@ -244,15 +245,14 @@ fn do_import_block, RA>( // remember that we need finality proof for this block imported_aux.needs_finality_proof = true; data.consensus_changes.note_change((number, hash)); + Ok(ImportResult::Imported(imported_aux)) }, - None => (), + None => Ok(ImportResult::Imported(imported_aux)), } - - Ok(ImportResult::Imported(imported_aux)) } /// Try to import finality proof. -fn do_import_finality_proof, RA>( +fn do_import_finality_proof, RA, J>( client: &Client, authority_set_provider: &AuthoritySetForFinalityChecker, data: &mut LightImportData, @@ -268,8 +268,8 @@ fn do_import_finality_proof, RA>( DigestFor: Encode, DigestItemFor: DigestItem, NumberFor: grandpa::BlockNumberOps, + J: ProvableJustification, { - // TODO: ensure that the proof is for non-finalized block let authority_set_id = data.authority_set.set_id(); let authorities = data.authority_set.authorities(); let finality_effects = crate::finality_proof::check_finality_proof( @@ -285,7 +285,7 @@ fn do_import_finality_proof, RA>( for header_to_import in finality_effects.headers_to_import { let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)?; assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify"); - do_import_block(client, data, block_to_import, new_authorities)?; + do_import_block::<_, _, _, _, J>(client, data, block_to_import, new_authorities)?; } // try to import latest justification @@ -311,7 +311,7 @@ fn do_import_finality_proof, RA>( } /// Try to import justification. -fn do_import_justification, RA>( +fn do_import_justification, RA, J>( client: &Client, data: &mut LightImportData, hash: Block::Hash, @@ -323,6 +323,7 @@ fn do_import_justification, RA>( E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, NumberFor: grandpa::BlockNumberOps, + J: ProvableJustification, { // with justification, we have two cases // @@ -334,10 +335,10 @@ fn do_import_justification, RA>( // first, try to behave optimistically let authority_set_id = data.authority_set.set_id(); - let justification = GrandpaJustification::::decode_and_verify( - &justification, + let justification = J::decode_and_verify( + justification, authority_set_id, - &data.authority_set.authorities().into_iter().collect(), + &data.authority_set.authorities(), ); // BadJustification error means that justification has been successfully decoded, but @@ -492,10 +493,87 @@ fn on_post_finalization_error(error: ClientError, value_type: &str) -> Consensus #[cfg(test)] mod tests { use super::*; + use consensus_common::ForkChoiceStrategy; use substrate_primitives::H256; use test_client::client::in_mem::Blockchain as InMemoryAuxStore; - use test_client::runtime::Block; + use test_client::runtime::{Block, Header}; use crate::tests::TestApi; + use crate::finality_proof::tests::TestJustification; + + fn import_block( + new_authorities: Option>, + justification: Option, + ) -> ImportResult { + let client = test_client::new_light(); + let mut import_data = LightImportData { + authority_set: LightAuthoritySet::genesis(vec![(Ed25519AuthorityId([1; 32]), 1)]), + consensus_changes: ConsensusChanges::empty(), + }; + let block = ImportBlock { + origin: BlockOrigin::Own, + header: Header { + number: 1, + parent_hash: client.info().unwrap().chain.best_hash, + state_root: Default::default(), + digest: Default::default(), + extrinsics_root: Default::default(), + }, + justification, + post_digests: Vec::new(), + body: None, + finalized: false, + auxiliary: Vec::new(), + fork_choice: ForkChoiceStrategy::LongestChain, + }; + do_import_block::<_, _, _, _, TestJustification>( + &client, + &mut import_data, + block, + new_authorities, + ).unwrap() + } + + #[test] + fn finality_proof_not_required_when_consensus_data_does_not_changes_and_no_justification_provided() { + assert_eq!(import_block(None, None), ImportResult::Imported(ImportedAux { + clear_justification_requests: false, + needs_justification: false, + needs_finality_proof: false, + })); + } + + #[test] + fn finality_proof_not_required_when_consensus_data_does_not_changes_and_correct_justification_provided() { + let justification = TestJustification(true, Vec::new()).encode(); + assert_eq!(import_block(None, Some(justification)), ImportResult::Imported(ImportedAux { + clear_justification_requests: false, + needs_justification: false, + needs_finality_proof: false, + })); + } + + #[test] + fn finality_proof_required_when_consensus_data_changes_and_no_justification_provided() { + assert_eq!(import_block(Some(vec![Ed25519AuthorityId([2; 32])]), None), ImportResult::Imported(ImportedAux { + clear_justification_requests: false, + needs_justification: false, + needs_finality_proof: true, + })); + } + + #[test] + fn finality_proof_required_when_consensus_data_changes_and_incorrect_justification_provided() { + let justification = TestJustification(false, Vec::new()).encode(); + assert_eq!( + import_block(Some(vec![Ed25519AuthorityId([2; 32])]), Some(justification)), + ImportResult::Imported(ImportedAux { + clear_justification_requests: false, + needs_justification: false, + needs_finality_proof: true, + }, + )); + } + #[test] fn aux_data_updated_on_start() { From 9e5aabe595dc340dce5efbef2a133af5e88be628 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 14 Mar 2019 15:04:55 +0300 Subject: [PATCH 24/42] only provide finality proof if it is generated by the requested set --- core/consensus/aura/src/lib.rs | 10 +- core/consensus/common/src/block_import.rs | 6 + core/consensus/common/src/import_queue.rs | 18 ++- core/consensus/common/src/lib.rs | 2 +- core/finality-grandpa/src/finality_proof.rs | 124 +++++++++++++++----- core/finality-grandpa/src/light_import.rs | 42 +++++-- core/network/src/chain.rs | 6 +- core/network/src/extra_requests.rs | 51 ++++---- core/network/src/message.rs | 4 +- core/network/src/protocol.rs | 23 ++-- core/network/src/service.rs | 11 +- core/network/src/sync.rs | 12 +- core/network/src/test/block_import.rs | 2 +- core/network/src/test/mod.rs | 26 ++-- node-template/src/service.rs | 2 + node/cli/src/service.rs | 13 +- 16 files changed, 254 insertions(+), 98 deletions(-) diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index ab33a8e1afd71..9b6dbb505f43c 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -34,6 +34,7 @@ use consensus_common::{ }; use consensus_common::import_queue::{ Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, + SharedFinalityProofRequestBuilder, }; use client::ChainHead; use client::block_builder::api::{BlockBuilder as BlockBuilderApi, self as block_builder_api}; @@ -674,6 +675,7 @@ pub fn import_queue( block_import: SharedBlockImport, justification_import: Option>, finality_proof_import: Option>, + finality_proof_request_builder: Option>, client: Arc, extra: E, inherent_data_providers: InherentDataProviders, @@ -689,7 +691,13 @@ pub fn import_queue( let verifier = Arc::new( AuraVerifier { client: client.clone(), extra, inherent_data_providers } ); - Ok(BasicQueue::new(verifier, block_import, justification_import, finality_proof_import)) + Ok(BasicQueue::new( + verifier, + block_import, + justification_import, + finality_proof_import, + finality_proof_request_builder, + )) } #[cfg(test)] diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index 7fa48d0209fba..4949be3075246 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -217,3 +217,9 @@ pub trait FinalityProofImport { verifier: &Verifier, ) -> Result<(), Self::Error>; } + +/// Finality proof request builder. +pub trait FinalityProofRequestBuilder: Send { + /// Build data blob, associated with the request. + fn build_request_data(&self, hash: &B::Hash) -> Vec; +} diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index f1e1035299c2b..246bfece2f31f 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -25,7 +25,8 @@ //! instantiated simply. use crate::block_import::{ - BlockImport, BlockOrigin, ImportBlock, ImportedAux, ImportResult, JustificationImport, FinalityProofImport, + BlockImport, BlockOrigin, ImportBlock, ImportedAux, ImportResult, JustificationImport, + FinalityProofImport, FinalityProofRequestBuilder, }; use crossbeam_channel::{self as channel, Receiver, Sender}; @@ -48,6 +49,9 @@ pub type SharedJustificationImport = Arc = Arc + Send + Sync>; +/// Shared finality proof request builder struct used by the queue. +pub type SharedFinalityProofRequestBuilder = Arc + Send + Sync>; + /// Maps to the Origin used by the network. pub type Origin = usize; @@ -145,6 +149,7 @@ impl BasicQueue { block_import: SharedBlockImport, justification_import: Option>, finality_proof_import: Option>, + finality_proof_request_builder: Option>, ) -> Self { let (result_sender, result_port) = channel::unbounded(); let worker_sender = BlockImportWorker::new(result_sender, verifier.clone(), block_import); @@ -154,6 +159,7 @@ impl BasicQueue { verifier, justification_import, finality_proof_import, + finality_proof_request_builder, ); Self { @@ -235,6 +241,7 @@ struct BlockImporter { verifier: Arc>, justification_import: Option>, finality_proof_import: Option>, + finality_proof_request_builder: Option>, } impl BlockImporter { @@ -244,6 +251,7 @@ impl BlockImporter { verifier: Arc>, justification_import: Option>, finality_proof_import: Option>, + finality_proof_request_builder: Option>, ) -> Sender> { let (sender, port) = channel::bounded(4); let _ = thread::Builder::new() @@ -257,6 +265,7 @@ impl BlockImporter { verifier, justification_import, finality_proof_import, + finality_proof_request_builder, }; while importer.run() { // Importing until all senders have been dropped... @@ -300,6 +309,9 @@ impl BlockImporter { self.handle_import_finality_proof(who, hash, number, finality_proof) }, BlockImportMsg::Start(link, sender) => { + if let Some(finality_proof_request_builder) = self.finality_proof_request_builder.take() { + link.set_finality_proof_request_builder(finality_proof_request_builder); + } if let Some(justification_import) = self.justification_import.as_ref() { justification_import.on_start(&*link); } @@ -526,6 +538,8 @@ pub trait Link: Send { fn note_useless_and_restart_sync(&self, _who: Origin, _reason: &str) {} /// Restart sync. fn restart(&self) {} + /// Remember finality proof request builder on start. + fn set_finality_proof_request_builder(&self, _request_builder: SharedFinalityProofRequestBuilder) {} } /// Block import successful result. @@ -685,7 +699,7 @@ mod tests { let (result_sender, result_port) = channel::unbounded(); let (worker_sender, _) = channel::unbounded(); let (link_sender, link_port) = channel::unbounded(); - let importer_sender = BlockImporter::::new(result_port, worker_sender, Arc::new(()), None, None); + let importer_sender = BlockImporter::::new(result_port, worker_sender, Arc::new(()), None, None, None); let link = TestLink::new(link_sender); let (ack_sender, start_ack_port) = channel::bounded(4); let _ = importer_sender.send(BlockImportMsg::Start(Box::new(link.clone()), ack_sender)); diff --git a/core/consensus/common/src/lib.rs b/core/consensus/common/src/lib.rs index d2d19859b061f..38909bf8b25f5 100644 --- a/core/consensus/common/src/lib.rs +++ b/core/consensus/common/src/lib.rs @@ -49,7 +49,7 @@ const MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024 + 512; pub use self::error::{Error, ErrorKind}; pub use block_import::{ BlockImport, BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult, - JustificationImport, FinalityProofImport, + JustificationImport, FinalityProofImport, FinalityProofRequestBuilder, }; /// Trait for getting the authorities at a given block. diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 80d66e1863220..7b27b0ba5cdfb 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -30,7 +30,7 @@ //! proof fragments are returned && each should be verified separately. use std::sync::Arc; -use log::trace; +use log::{trace, warn}; use client::{ backend::Backend, blockchain::Backend as BlockchainBackend, CallExecutor, Client, @@ -145,21 +145,30 @@ impl, RA> FinalityProofProvider impl network::FinalityProofProvider for FinalityProofProvider where Block: BlockT, + NumberFor: BlockNumberOps, B: Backend + Send + Sync + 'static, E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, { fn prove_finality( &self, - last_finalized: Block::Hash, for_block: Block::Hash, + request: &[u8], ) -> Result>, ClientError> { - prove_finality( - &*self.client.backend().blockchain(), - &*self.authority_provider, - last_finalized, - for_block, - ) + let request: FinalityProofRequest = Decode::decode(&mut &request[..]) + .ok_or_else(|| { + warn!(target: "finality", "Unable to decode finality proof request."); + ClientError::from(ClientErrorKind::Backend(format!("Invalid finality proof request"))) + })?; + match request { + FinalityProofRequest::Original(request) => prove_finality::<_, _, GrandpaJustification>( + &*self.client.backend().blockchain(), + &*self.authority_provider, + request.authorities_set_id, + request.last_finalized, + for_block, + ), + } } } @@ -201,18 +210,48 @@ struct FinalityProofFragment { /// - all other fragments provide justifications for GRANDPA authorities set changes within requested range. type FinalityProof
= Vec>; +/// Finality proof request data. +#[derive(Debug, Encode, Decode)] +enum FinalityProofRequest { + /// Original version of the request. + Original(OriginalFinalityProofRequest), +} + +/// Original version of finality proof request. +#[derive(Debug, Encode, Decode)] +struct OriginalFinalityProofRequest { + /// The authorities set id we are waiting proof from. + /// + /// The first justification in the proof must be signed by this authority set. + pub authorities_set_id: u64, + /// Hash of the last known finalized block. + pub last_finalized: H, +} + +/// Prepare data blob associated with finality proof request. +pub(crate) fn make_finality_proof_request(last_finalized: H, authorities_set_id: u64) -> Vec { + FinalityProofRequest::Original(OriginalFinalityProofRequest { + authorities_set_id, + last_finalized, + }).encode() +} + /// Prepare proof-of-finality for the best possible block in the range: (begin; end]. /// /// It is assumed that the caller already have a proof-of-finality for the block 'begin'. /// It is assumed that the caller already knows all blocks in the range (begin; end]. /// /// Returns None if there are no finalized blocks unknown to the caller. -pub fn prove_finality, B: BlockchainBackend>( +pub(crate) fn prove_finality, B: BlockchainBackend, J>( blockchain: &B, authorities_provider: &AuthoritySetForFinalityProver, + authorities_set_id: u64, begin: Block::Hash, end: Block::Hash, -) -> ::client::error::Result>> { +) -> ::client::error::Result>> + where + J: ProvableJustification, +{ let begin_id = BlockId::Hash(begin); let begin_number = blockchain.expect_block_number_from_id(&begin_id)?; @@ -285,6 +324,25 @@ pub fn prove_finality, B: BlockchainBackend>( let justifies_end_block = current_number >= end_number; let justifies_authority_set_change = proof_fragment.authorities_proof.is_some(); if justifies_end_block || justifies_authority_set_change { + // check if the proof is generated by the requested authority set + if finality_proof.is_empty() { + let justification_check_result = J::decode_and_verify( + &proof_fragment.justification, + authorities_set_id, + ¤t_authorities, + ); + if justification_check_result.is_err() { + trace!( + target: "finality", + "Can not provide finality proof with requested set id #{}\ + (possible forced change?). Returning empty proof.", + authorities_set_id, + ); + + return Ok(None); + } + } + finality_proof.push(proof_fragment); latest_proof_fragment = None; } else { @@ -481,11 +539,11 @@ pub(crate) trait ProvableJustification: Encode + Decode { /// Decode and verify justification. fn decode_and_verify( - justification: Justification, + justification: &Justification, set_id: u64, authorities: &[(Ed25519AuthorityId, u64)], ) -> ClientResult { - let justification = Self::decode(&mut &*justification).ok_or_else(|| { + let justification = Self::decode(&mut &**justification).ok_or_else(|| { ClientError::from(ClientErrorKind::JustificationDecode) })?; justification.verify(set_id, authorities)?; @@ -598,12 +656,13 @@ pub(crate) mod tests { // their last finalized is: 2 // they request for proof-of-finality of: 2 // => range is invalid - prove_finality( + prove_finality::<_, _, TestJustification>( &blockchain, &( |_| unreachable!("should return before calling GetAuthorities"), |_| unreachable!("should return before calling ProveAuthorities"), ), + 0, header(2).hash(), header(2).hash(), ).unwrap_err(); @@ -617,12 +676,13 @@ pub(crate) mod tests { // our last finalized is: 3 // their last finalized is: 3 // => we can't provide any additional justifications - let proof_of_4 = prove_finality( + let proof_of_4 = prove_finality::<_, _, TestJustification>( &blockchain, &( |_| unreachable!("should return before calling GetAuthorities"), |_| unreachable!("should return before calling ProveAuthorities"), ), + 0, header(3).hash(), header(4).hash(), ).unwrap(); @@ -642,12 +702,13 @@ pub(crate) mod tests { // \> 4' -> 5' // and the best finalized is 5 // => when requesting for (4'; 5'], error is returned - prove_finality( + prove_finality::<_, _, TestJustification>( &blockchain, &( |_| unreachable!("should return before calling GetAuthorities"), |_| unreachable!("should return before calling ProveAuthorities"), ), + 0, side_header(4).hash(), second_side_header(5).hash(), ).unwrap_err(); @@ -660,12 +721,13 @@ pub(crate) mod tests { // block 4 is finalized without justification // => we can't prove finality - let proof_of_4 = prove_finality( + let proof_of_4 = prove_finality::<_, _, TestJustification>( &blockchain, &( |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), |_| unreachable!("authorities didn't change => ProveAuthorities won't be called"), ), + 0, header(3).hash(), header(4).hash(), ).unwrap(); @@ -675,23 +737,26 @@ pub(crate) mod tests { #[test] fn finality_proof_works_without_authorities_change() { let blockchain = test_blockchain(); - blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap(); - blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); + let just4 = TestJustification(true, vec![4]).encode(); + let just5 = TestJustification(true, vec![5]).encode(); + blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), None, NewBlockState::Final).unwrap(); // blocks 4 && 5 are finalized with justification // => since authorities are the same, we only need justification for 5 - let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( + let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>( &blockchain, &( |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), |_| unreachable!("should return before calling ProveAuthorities"), ), + 0, header(3).hash(), header(5).hash(), ).unwrap().unwrap()[..]).unwrap(); assert_eq!(proof_of_5, vec![FinalityProofFragment { block: header(5).hash(), - justification: vec![5], + justification: just5, unknown_headers: Vec::new(), authorities_proof: None, }]); @@ -705,12 +770,13 @@ pub(crate) mod tests { // block 4 is finalized with justification + we request for finality of 5 // => we can't prove finality of 5, but providing finality for 4 is still useful for requester - let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality( + let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>( &blockchain, &( |_| Ok(vec![(Ed25519AuthorityId([1u8; 32]), 1u64)]), |_| unreachable!("should return before calling ProveAuthorities"), ), + 0, header(3).hash(), header(5).hash(), ).unwrap().unwrap()[..]).unwrap(); @@ -725,14 +791,17 @@ pub(crate) mod tests { #[test] fn finality_proof_works_with_authorities_change() { let blockchain = test_blockchain(); - blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap(); - blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap(); + let just4 = TestJustification(true, vec![4]).encode(); + let just5 = TestJustification(true, vec![5]).encode(); + let just7 = TestJustification(true, vec![7]).encode(); + blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), None, NewBlockState::Final).unwrap(); blockchain.insert(header(6).hash(), header(6), None, None, NewBlockState::Final).unwrap(); - blockchain.insert(header(7).hash(), header(7), Some(vec![7]), None, NewBlockState::Final).unwrap(); + blockchain.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final).unwrap(); // when querying for finality of 6, we assume that the #6 is the last block known to the requester // => since we only have justification for #7, we provide #7 - let proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality( + let proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>( &blockchain, &( |block_id| match block_id { @@ -748,6 +817,7 @@ pub(crate) mod tests { _ => unreachable!("no other authorities should be proved: {:?}", block_id), }, ), + 0, header(3).hash(), header(6).hash(), ).unwrap().unwrap()[..]).unwrap(); @@ -757,14 +827,14 @@ pub(crate) mod tests { // first fragment provides justification for #5 && authorities set that starts acting from #5 FinalityProofFragment { block: header(5).hash(), - justification: vec![5], + justification: just5, unknown_headers: Vec::new(), authorities_proof: Some(vec![vec![40]]), }, // last fragment provides justification for #7 && unknown#7 FinalityProofFragment { block: header(7).hash(), - justification: vec![7], + justification: just7, unknown_headers: vec![header(7)], authorities_proof: Some(vec![vec![60]]), }, diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index d4f704fb37ca2..1583428da394c 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -26,9 +26,9 @@ use client::{ }; use parity_codec::{Encode, Decode}; use consensus_common::{ - import_queue::Verifier, + import_queue::{Verifier, SharedFinalityProofRequestBuilder}, BlockOrigin, BlockImport, FinalityProofImport, ImportBlock, ImportResult, ImportedAux, - Error as ConsensusError, ErrorKind as ConsensusErrorKind + Error as ConsensusError, ErrorKind as ConsensusErrorKind, FinalityProofRequestBuilder, }; use runtime_primitives::Justification; use runtime_primitives::traits::{ @@ -42,7 +42,7 @@ use substrate_primitives::{H256, Ed25519AuthorityId, Blake2Hasher}; use crate::aux_schema::load_decode; use crate::consensus_changes::ConsensusChanges; use crate::environment::canonical_at_height; -use crate::finality_proof::{AuthoritySetForFinalityChecker, ProvableJustification}; +use crate::finality_proof::{AuthoritySetForFinalityChecker, ProvableJustification, make_finality_proof_request}; use crate::justification::GrandpaJustification; /// LightAuthoritySet is saved under this key in aux storage. @@ -63,7 +63,8 @@ pub fn light_block_import, RA, PRA>( PRA: ProvideRuntimeApi, PRA::Api: GrandpaApi, { - let import_data = load_aux_import_data(&**client.backend(), api)?; + let info = client.info()?; + let import_data = load_aux_import_data(info.chain.finalized_hash, &**client.backend(), api)?; Ok(GrandpaLightBlockImport { client, authority_set_provider, @@ -84,6 +85,7 @@ pub struct GrandpaLightBlockImport, RA> { /// Mutable data of light block importer. struct LightImportData> { + last_finalized: Block::Hash, authority_set: LightAuthoritySet, consensus_changes: ConsensusChanges>, } @@ -95,6 +97,13 @@ struct LightAuthoritySet { authorities: Vec<(Ed25519AuthorityId, u64)>, } +impl, RA> GrandpaLightBlockImport { + /// Create finality proof request builder. + pub fn create_finality_proof_request_builder(&self) -> SharedFinalityProofRequestBuilder { + Arc::new(GrandpaFinalityProofRequestBuilder(self.data.clone())) as _ + } +} + impl, RA> BlockImport for GrandpaLightBlockImport where NumberFor: grandpa::BlockNumberOps, @@ -194,6 +203,18 @@ impl LightAuthoritySet { } } +struct GrandpaFinalityProofRequestBuilder>(Arc>>); + +impl> FinalityProofRequestBuilder for GrandpaFinalityProofRequestBuilder { + fn build_request_data(&self, _hash: &B::Hash) -> Vec { + let data = self.0.read(); + make_finality_proof_request( + data.last_finalized, + data.authority_set.set_id(), + ) + } +} + /// Try to import new block. fn do_import_block, RA, J>( client: &Client, @@ -336,7 +357,7 @@ fn do_import_justification, RA, J>( // first, try to behave optimistically let authority_set_id = data.authority_set.set_id(); let justification = J::decode_and_verify( - justification, + &justification, authority_set_id, &data.authority_set.authorities(), ); @@ -413,11 +434,15 @@ fn do_finalize_block, RA>( Err(error) => return Err(on_post_finalization_error(error, "consensus changes")), } + // update last finalized block reference + data.last_finalized = hash; + Ok(ImportResult::imported()) } /// Load light impoty aux data from the store. fn load_aux_import_data, PRA>( + last_finalized: Block::Hash, aux_store: &B, api: Arc, ) -> Result, ClientError> @@ -457,6 +482,7 @@ fn load_aux_import_data, PRA>( }; Ok(LightImportData { + last_finalized, authority_set, consensus_changes, }) @@ -490,6 +516,7 @@ fn on_post_finalization_error(error: ClientError, value_type: &str) -> Consensus ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string())) } + #[cfg(test)] mod tests { use super::*; @@ -506,6 +533,7 @@ mod tests { ) -> ImportResult { let client = test_client::new_light(); let mut import_data = LightImportData { + last_finalized: Default::default(), authority_set: LightAuthoritySet::genesis(vec![(Ed25519AuthorityId([1; 32]), 1)]), consensus_changes: ConsensusChanges::empty(), }; @@ -585,7 +613,7 @@ mod tests { assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_none()); // it is updated on importer start - load_aux_import_data(&aux_store, api).unwrap(); + load_aux_import_data(Default::default(), &aux_store, api).unwrap(); assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_some()); assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_some()); } @@ -613,7 +641,7 @@ mod tests { ).unwrap(); // importer uses it on start - let data = load_aux_import_data(&aux_store, api).unwrap(); + let data = load_aux_import_data(Default::default(), &aux_store, api).unwrap(); assert_eq!(data.authority_set.authorities(), vec![(Ed25519AuthorityId([42; 32]), 2)]); assert_eq!(data.consensus_changes.pending_changes(), &[(42, Default::default())]); } diff --git a/core/network/src/chain.rs b/core/network/src/chain.rs index 653ebf77c2b61..213a7ab8c097f 100644 --- a/core/network/src/chain.rs +++ b/core/network/src/chain.rs @@ -73,10 +73,10 @@ pub trait Client: Send + Sync { fn is_descendent_of(&self, base: &Block::Hash, block: &Block::Hash) -> Result; } -/// +/// Finality proof provider. pub trait FinalityProofProvider: Send + Sync { - /// - fn prove_finality(&self, last_finalized: Block::Hash, for_block: Block::Hash) -> Result>, Error>; + /// Prove finality of the block. + fn prove_finality(&self, for_block: Block::Hash, request: &[u8]) -> Result>, Error>; } impl Client for SubstrateClient where diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index 6e97d99d62aa4..ccc11122c52ca 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -16,9 +16,9 @@ use std::collections::{HashMap, VecDeque}; use std::time::{Duration, Instant}; -use log::{trace, debug, warn}; +use log::{trace, warn}; use client::error::Error as ClientError; -use consensus::import_queue::ImportQueue; +use consensus::import_queue::{ImportQueue, SharedFinalityProofRequestBuilder}; use fork_tree::ForkTree; use network_libp2p::NodeIndex; use runtime_primitives::Justification; @@ -40,10 +40,16 @@ pub(crate) trait ExtraRequestsEssence { /// Name of request type to display in logs. fn type_name(&self) -> &'static str; /// Prepare network message corresponding to the request. - fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message; + fn into_network_request(&self, request: ExtraRequest) -> Message; /// Accept response. - fn import_response(&self, import_queue: &ImportQueue, who: NodeIndex, request: ExtraRequest, response: Self::Response); - /// + fn import_response( + &self, + import_queue: &ImportQueue, + who: NodeIndex, + request: ExtraRequest, + response: Self::Response, + ); + /// Create peer state for peer that is downloading extra data. fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState; } @@ -52,14 +58,14 @@ pub(crate) struct ExtraRequestsAggregator { /// Manages justifications requests. justifications: ExtraRequests, /// Manages finality proof requests. - finality_proofs: ExtraRequests, + finality_proofs: ExtraRequests>, } impl ExtraRequestsAggregator { pub(crate) fn new() -> Self { ExtraRequestsAggregator { justifications: ExtraRequests::new(JustificationsRequestsEssence), - finality_proofs: ExtraRequests::new(FinalityProofRequestsEssence), + finality_proofs: ExtraRequests::new(FinalityProofRequestsEssence(None)), } } @@ -67,7 +73,7 @@ impl ExtraRequestsAggregator { &mut self.justifications } - pub(crate) fn finality_proofs(&mut self) -> &mut ExtraRequests { + pub(crate) fn finality_proofs(&mut self) -> &mut ExtraRequests> { &mut self.finality_proofs } @@ -123,6 +129,11 @@ impl> ExtraRequests { } } + /// Get mutable reference to the requests essence. + pub(crate) fn essence(&mut self) -> &mut Essence { + &mut self.essence + } + /// Dispatches all possible pending requests to the given peers. Peers are /// filtered according to the current known best block (i.e. we won't send a /// extra request for block #10 to a peer at block #2), and we also @@ -152,14 +163,6 @@ impl> ExtraRequests { let mut last_peer = available_peers.back().map(|p| p.0); let mut unhandled_requests = VecDeque::new(); - let last_finalzied_hash = match protocol.client().info() { - Ok(info) => info.chain.finalized_hash, - Err(e) => { - debug!(target:"sync", "Cannot dispatch {} requests: error {:?} when reading blockchain", self.essence.type_name(), e); - return; - }, - }; - loop { let (peer, peer_best_number) = match available_peers.pop_front() { Some(p) => p, @@ -210,7 +213,7 @@ impl> ExtraRequests { .state = self.essence.peer_downloading_state(request.0); trace!(target: "sync", "Requesting {} for block #{} from {}", self.essence.type_name(), request.0, peer); - let request = self.essence.into_network_request(request, last_finalzied_hash); + let request = self.essence.into_network_request(request); protocol.send_message(peer, request); } @@ -225,7 +228,7 @@ impl> ExtraRequests { } /// Queue a extra data request (without dispatching it). - pub(crate) fn queue_request(&mut self, request: &ExtraRequest, is_descendent_of: F) + pub(crate) fn queue_request(&mut self, request: ExtraRequest, is_descendent_of: F) where F: Fn(&B::Hash, &B::Hash) -> Result { match self.tree.import(request.0.clone(), request.1.clone(), (), &is_descendent_of) { @@ -345,7 +348,7 @@ impl ExtraRequestsEssence for JustificationsRequestsEssence { "justification" } - fn into_network_request(&self, request: ExtraRequest, _last_finalzied_hash: B::Hash) -> Message { + fn into_network_request(&self, request: ExtraRequest) -> Message { GenericMessage::BlockRequest(message::generic::BlockRequest { id: 0, fields: message::BlockAttributes::JUSTIFICATION, @@ -365,20 +368,22 @@ impl ExtraRequestsEssence for JustificationsRequestsEssence { } } -pub(crate) struct FinalityProofRequestsEssence; +pub(crate) struct FinalityProofRequestsEssence(pub Option>); -impl ExtraRequestsEssence for FinalityProofRequestsEssence { +impl ExtraRequestsEssence for FinalityProofRequestsEssence { type Response = Vec; fn type_name(&self) -> &'static str { "finality proof" } - fn into_network_request(&self, request: ExtraRequest, last_finalzied_hash: B::Hash) -> Message { + fn into_network_request(&self, request: ExtraRequest) -> Message { GenericMessage::FinalityProofRequest(message::generic::FinalityProofRequest { id: 0, block: request.0, - last_finalized: last_finalzied_hash, + request: self.0.as_ref() + .map(|builder| builder.build_request_data(&request.0)) + .unwrap_or_default(), }) } diff --git a/core/network/src/message.rs b/core/network/src/message.rs index a3af8771b5ac5..b903d5baf93bf 100644 --- a/core/network/src/message.rs +++ b/core/network/src/message.rs @@ -382,8 +382,8 @@ pub mod generic { pub id: RequestId, /// Hash of the block to request proof for. pub block: H, - /// Hash of the last known finalized block. - pub last_finalized: H, + /// Additional data blob (that both requester and provider understood) required for proving finality. + pub request: Vec, } #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] diff --git a/core/network/src/protocol.rs b/core/network/src/protocol.rs index c9714bd304a32..1df7ca3b8a63c 100644 --- a/core/network/src/protocol.rs +++ b/core/network/src/protocol.rs @@ -21,7 +21,7 @@ use network_libp2p::{NodeIndex, PeerId, Severity}; use primitives::storage::StorageKey; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor, Zero}; -use consensus::import_queue::ImportQueue; +use consensus::import_queue::{ImportQueue, SharedFinalityProofRequestBuilder}; use crate::message::{self, Message, ConsensusEngineId}; use crate::message::generic::{Message as GenericMessage, ConsensusMessage}; use crate::consensus_gossip::ConsensusGossip; @@ -192,24 +192,24 @@ struct ContextData { /// A task, consisting of a user-provided closure, to be executed on the Protocol thread. pub trait SpecTask> { - fn call_box(self: Box, spec: &mut S, context: &mut Context); + fn call_box(self: Box, spec: &mut S, context: &mut Context); } impl, F: FnOnce(&mut S, &mut Context)> SpecTask for F { - fn call_box(self: Box, spec: &mut S, context: &mut Context) { - (*self)(spec, context) - } + fn call_box(self: Box, spec: &mut S, context: &mut Context) { + (*self)(spec, context) + } } /// A task, consisting of a user-provided closure, to be executed on the Protocol thread. pub trait GossipTask { - fn call_box(self: Box, gossip: &mut ConsensusGossip, context: &mut Context); + fn call_box(self: Box, gossip: &mut ConsensusGossip, context: &mut Context); } impl, &mut Context)> GossipTask for F { - fn call_box(self: Box, gossip: &mut ConsensusGossip, context: &mut Context) { - (*self)(gossip, context) - } + fn call_box(self: Box, gossip: &mut ConsensusGossip, context: &mut Context) { + (*self)(gossip, context) + } } /// Messages sent to Protocol from elsewhere inside the system. @@ -230,6 +230,8 @@ pub enum ProtocolMsg> { RequestJustification(B::Hash, NumberFor), /// Inform protocol whether a justification was successfully imported. JustificationImportResult(B::Hash, NumberFor, bool), + /// Set finality proof request builder. + SetFinalityProofRequestBuilder(SharedFinalityProofRequestBuilder), /// Tell protocol to request finality proof for a block. RequestFinalityProof(B::Hash, NumberFor), /// Inform protocol whether a finality proof was successfully imported. @@ -410,6 +412,7 @@ impl, H: ExHashT> Protocol { self.sync.request_justification(&hash, number, &mut context); }, ProtocolMsg::JustificationImportResult(hash, number, success) => self.sync.justification_import_result(hash, number, success), + ProtocolMsg::SetFinalityProofRequestBuilder(builder) => self.sync.set_finality_proof_request_builder(builder), ProtocolMsg::RequestFinalityProof(hash, number) => { let mut context = ProtocolContext::new(&mut self.context_data, &self.network_chan); @@ -1110,7 +1113,7 @@ impl, H: ExHashT> Protocol { trace!(target: "sync", "Finality proof request from {} for {}", who, request.block); let finality_proof = self.context_data.finality_proof_provider.as_ref() .ok_or_else(|| String::from("Finality provider is not configured")) - .and_then(|provider| provider.prove_finality(request.last_finalized, request.block) + .and_then(|provider| provider.prove_finality(request.block, &request.request) .map_err(|e| e.to_string())); let finality_proof = match finality_proof { Ok(finality_proof) => finality_proof, diff --git a/core/network/src/service.rs b/core/network/src/service.rs index a74e556fdcfc7..f3ab02d024c59 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -24,7 +24,7 @@ use parking_lot::{Mutex, RwLock}; use network_libp2p::{ProtocolId, NetworkConfiguration, NodeIndex, ErrorKind, Severity}; use network_libp2p::{start_service, parse_str_addr, Service as NetworkService, ServiceEvent as NetworkServiceEvent}; use network_libp2p::{Protocol as Libp2pProtocol, RegisteredProtocol, NetworkState}; -use consensus::import_queue::{ImportQueue, Link}; +use consensus::import_queue::{ImportQueue, Link, SharedFinalityProofRequestBuilder}; use crate::consensus_gossip::ConsensusGossip; use crate::message::{Message, ConsensusEngineId}; use crate::protocol::{self, Context, FromNetworkMsg, Protocol, ConnectedPeer, ProtocolMsg, ProtocolStatus, PeerInfo}; @@ -110,7 +110,10 @@ impl> Link for NetworkLink { } fn request_finality_proof(&self, hash: &B::Hash, number: NumberFor) { - let _ = self.protocol_sender.send(ProtocolMsg::RequestFinalityProof(hash.clone(), number)); + let _ = self.protocol_sender.send(ProtocolMsg::RequestFinalityProof( + hash.clone(), + number, + )); } fn useless_peer(&self, who: NodeIndex, reason: &str) { @@ -128,6 +131,10 @@ impl> Link for NetworkLink { fn restart(&self) { let _ = self.protocol_sender.send(ProtocolMsg::RestartSync); } + + fn set_finality_proof_request_builder(&self, request_builder: SharedFinalityProofRequestBuilder) { + let _ = self.protocol_sender.send(ProtocolMsg::SetFinalityProofRequestBuilder(request_builder)); + } } /// Substrate network service. Handles network IO and manages connectivity. diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index 8e043198ed366..497bceb6767c6 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -21,7 +21,7 @@ use crate::protocol::Context; use network_libp2p::{Severity, NodeIndex}; use client::{BlockStatus, ClientInfo}; use consensus::BlockOrigin; -use consensus::import_queue::{ImportQueue, IncomingBlock}; +use consensus::import_queue::{ImportQueue, IncomingBlock, SharedFinalityProofRequestBuilder}; use client::error::Error as ClientError; use crate::blocks::BlockCollection; use crate::extra_requests::ExtraRequestsAggregator; @@ -487,7 +487,7 @@ impl ChainSync { /// Queues a new justification request and tries to dispatch all pending requests. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { self.extra_requests.justifications().queue_request( - &(*hash, number), + (*hash, number), |base, block| protocol.client().is_descendent_of(base, block), ); @@ -508,7 +508,7 @@ impl ChainSync { /// Queues a new finality proof request and tries to dispatch all pending requests. pub fn request_finality_proof(&mut self, hash: &B::Hash, number: NumberFor, protocol: &mut Context) { self.extra_requests.finality_proofs().queue_request( - &(*hash, number), + (*hash, number), |base, block| protocol.client().is_descendent_of(base, block), ); @@ -516,7 +516,11 @@ impl ChainSync { } pub fn finality_proof_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - self.extra_requests.justifications().on_import_result(hash, number, success); + self.extra_requests.finality_proofs().on_import_result(hash, number, success); + } + + pub fn set_finality_proof_request_builder(&mut self, request_builder: SharedFinalityProofRequestBuilder) { + self.extra_requests.finality_proofs().essence().0 = Some(request_builder); } pub fn stop(&self) { diff --git a/core/network/src/test/block_import.rs b/core/network/src/test/block_import.rs index 5e52b637976c6..5a7a1595050ce 100644 --- a/core/network/src/test/block_import.rs +++ b/core/network/src/test/block_import.rs @@ -76,7 +76,7 @@ fn async_import_queue_drops() { // Perform this test multiple times since it exhibits non-deterministic behavior. for _ in 0..100 { let verifier = Arc::new(PassThroughVerifier(true)); - let queue = BasicQueue::new(verifier, Arc::new(test_client::new()), None, None); + let queue = BasicQueue::new(verifier, Arc::new(test_client::new()), None, None, None); queue.start(Box::new(TestLink{})).unwrap(); drop(queue); } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index dd0bcc5c63747..8bbb194faa065 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -34,7 +34,7 @@ use client::block_builder::BlockBuilder; use client::backend::AuxStore; use crate::config::{ProtocolConfig, Roles}; use consensus::import_queue::{BasicQueue, ImportQueue, IncomingBlock}; -use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, Verifier}; +use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, Verifier, SharedFinalityProofImport}; use consensus::{Error as ConsensusError, ErrorKind as ConsensusErrorKind}; use consensus::{BlockOrigin, ForkChoiceStrategy, ImportBlock, JustificationImport}; use crate::consensus_gossip::ConsensusGossip; @@ -484,14 +484,6 @@ impl + Clone> Peer { .send(ProtocolMsg::RequestJustification(hash.clone(), number)); } - /// Request a finality proof for the given block. - #[cfg(test)] - fn request_finality_proof(&self, hash: &::primitives::H256, number: NumberFor) { - let _ = self - .protocol_sender - .send(ProtocolMsg::RequestFinalityProof(hash.clone(), number)); - } - /// Add blocks to the peer -- edit the block before adding pub fn generate_blocks(&self, count: usize, origin: BlockOrigin, edit_block: F) -> H256 where F: FnMut(BlockBuilder) -> Block @@ -653,7 +645,13 @@ pub trait TestNetFactory: Sized { let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Full(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); - let import_queue = Box::new(BasicQueue::new(verifier, block_import, justification_import, finality_proof_import)); + let import_queue = Box::new(BasicQueue::new( + verifier, + block_import, + justification_import, + finality_proof_import, + None, + )); let status_sinks = Arc::new(Mutex::new(Vec::new())); let is_offline = Arc::new(AtomicBool::new(true)); let is_major_syncing = Arc::new(AtomicBool::new(false)); @@ -704,7 +702,13 @@ pub trait TestNetFactory: Sized { let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Light(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); - let import_queue = Box::new(BasicQueue::new(verifier, block_import, justification_import, finality_proof_import)); + let import_queue = Box::new(BasicQueue::new( + verifier, + block_import, + justification_import, + finality_proof_import, + None, + )); let status_sinks = Arc::new(Mutex::new(Vec::new())); let is_offline = Arc::new(AtomicBool::new(true)); let is_major_syncing = Arc::new(AtomicBool::new(false)); diff --git a/node-template/src/service.rs b/node-template/src/service.rs index aff032fd49806..905b4099c28c7 100644 --- a/node-template/src/service.rs +++ b/node-template/src/service.rs @@ -91,6 +91,7 @@ construct_service_factory! { client.clone(), None, None, + None, client, NothingExtra, config.custom.inherent_data_providers.clone(), @@ -105,6 +106,7 @@ construct_service_factory! { client.clone(), None, None, + None, client, NothingExtra, config.custom.inherent_data_providers.clone(), diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index af459ffedda00..fca4b71887bb6 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -138,6 +138,7 @@ construct_service_factory! { block_import, Some(justification_import), None, + None, client, NothingExtra, config.custom.inherent_data_providers.clone(), @@ -149,15 +150,19 @@ construct_service_factory! { .upgrade() .map(|fetcher| fetcher.checker().clone()) .ok_or_else(|| "Trying to start light import queue without active fetch checker")?; - let block_import = Arc::new(grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>( + let block_import = grandpa::light_block_import::<_, _, _, RuntimeApi, LightClient>( client.clone(), Arc::new(fetch_checker), client.clone() - )?); + )?; + let block_import = Arc::new(block_import); + let finality_proof_import = block_import.clone(); + let finality_proof_request_builder = finality_proof_import.create_finality_proof_request_builder(); import_queue( SlotDuration::get_or_compute(&*client)?, - block_import.clone(), + block_import, None, - Some(block_import), + Some(finality_proof_import), + Some(finality_proof_request_builder), client, NothingExtra, config.custom.inherent_data_providers.clone(), From f22ebf1fa8ff0aca3d610aca935be00f33131c62 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 14 Mar 2019 16:54:54 +0300 Subject: [PATCH 25/42] post-merge fix --- core/client/db/src/light.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 4da5d6743236d..90e2eede3f141 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -843,8 +843,7 @@ pub(crate) mod tests { (3, Some(vec![auth1()])), (4, Some(vec![auth1(), auth2()])), (5, Some(vec![auth1(), auth2()])), - (6, None), - (7, None), // block will work for 'future' block too + (6, Some(vec![auth1(), auth2()])), ]; let hash0 = insert_final_block(&db, None, || default_header(&Default::default(), 0)); @@ -928,7 +927,7 @@ pub(crate) mod tests { Some(vec![auth1(), auth2()]), ); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![auth3()])); - assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![auth4()])); + assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![auth3()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![auth4()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![auth5()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_2)), Some(vec![auth6()])); From c6c128ba9bd5251167e20ba2d7333eeb847b8a71 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 14 Mar 2019 17:11:10 +0300 Subject: [PATCH 26/42] finality_proof_is_none_if_first_justification_is_generated_by_unknown_set --- core/finality-grandpa/src/finality_proof.rs | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index ca3f94383f3bf..3e4665f3ddf82 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -947,4 +947,27 @@ pub(crate) mod tests { new_authorities: vec![(AuthorityId::from_raw([4u8; 32]), 1u64)], }); } + + #[test] + fn finality_proof_is_none_if_first_justification_is_generated_by_unknown_set() { + // this is the case for forced change: set_id has been forcibly increased on full node + // and ligh node missed that + // => justification verification will fail on light node anyways, so we do not return + // finality proof at all + let blockchain = test_blockchain(); + let just4 = TestJustification(false, vec![4]).encode(); // false makes verification fail + blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap(); + + let proof_of_4 = prove_finality::<_, _, TestJustification>( + &blockchain, + &( + |_| Ok(vec![(AuthorityId::from_raw([1u8; 32]), 1u64)]), + |_| unreachable!("should return before calling ProveAuthorities"), + ), + 0, + header(3).hash(), + header(4).hash(), + ).unwrap(); + assert!(proof_of_4.is_none()); + } } From 188ab5abe43a62a3cc7e67b9c1922cda1ef2fafe Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2019 08:59:05 +0300 Subject: [PATCH 27/42] make light+grandpa test rely on finality proofs (instead of simple justifications) --- core/consensus/common/src/import_queue.rs | 4 +- core/finality-grandpa/src/light_import.rs | 78 ++++++++++++++++++++++- core/finality-grandpa/src/tests.rs | 49 +++++++++++--- core/network/src/extra_requests.rs | 6 ++ core/network/src/sync.rs | 1 + core/network/src/test/mod.rs | 39 +++++++++--- 6 files changed, 156 insertions(+), 21 deletions(-) diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index ddff65482aaaf..fe625c17ffa6f 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -538,14 +538,14 @@ pub trait Link: Send { fn finality_proof_imported(&self, _who: Origin, _hash: &B::Hash, _number: NumberFor, _success: bool) {} /// Request a finality proof for the given block. fn request_finality_proof(&self, _hash: &B::Hash, _number: NumberFor) {} + /// Remember finality proof request builder on start. + fn set_finality_proof_request_builder(&self, _request_builder: SharedFinalityProofRequestBuilder) {} /// Disconnect from peer. fn useless_peer(&self, _who: Origin, _reason: &str) {} /// Disconnect from peer and restart sync. fn note_useless_and_restart_sync(&self, _who: Origin, _reason: &str) {} /// Restart sync. fn restart(&self) {} - /// Remember finality proof request builder on start. - fn set_finality_proof_request_builder(&self, _request_builder: SharedFinalityProofRequestBuilder) {} } /// Block import successful result. diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 60e9b88ccdc91..9c8b0d9262ae5 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -176,7 +176,6 @@ impl, RA> FinalityProofImport } } - impl LightAuthoritySet { /// Get a genesis set with given authorities. pub fn genesis(initial: Vec<(AuthorityId, u64)>) -> Self { @@ -518,7 +517,7 @@ fn on_post_finalization_error(error: ClientError, value_type: &str) -> Consensus #[cfg(test)] -mod tests { +pub mod tests { use super::*; use consensus_common::ForkChoiceStrategy; use substrate_primitives::H256; @@ -527,6 +526,81 @@ mod tests { use crate::tests::TestApi; use crate::finality_proof::tests::TestJustification; + pub struct NoJustificationsImport, RA>( + pub GrandpaLightBlockImport + ); + + impl, RA> BlockImport + for NoJustificationsImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + RA: Send + Sync, + { + type Error = ConsensusError; + + fn import_block( + &self, + mut block: ImportBlock, + new_authorities: Option>, + ) -> Result { + block.justification.take(); + self.0.import_block(block, new_authorities) + } + + fn check_block( + &self, + hash: Block::Hash, + parent_hash: Block::Hash, + ) -> Result { + self.0.check_block(hash, parent_hash) + } + } + + impl, RA> FinalityProofImport + for NoJustificationsImport where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + DigestFor: Encode, + DigestItemFor: DigestItem, + RA: Send + Sync, + { + type Error = ConsensusError; + + fn on_start(&self, link: &::consensus_common::import_queue::Link) { + self.0.on_start(link) + } + + fn import_finality_proof( + &self, + hash: Block::Hash, + number: NumberFor, + finality_proof: Vec, + verifier: &Verifier, + ) -> Result<(), Self::Error> { + self.0.import_finality_proof(hash, number, finality_proof, verifier) + } + } + + /// Creates light block import that ignores justifications that came outside of finality proofs. + pub fn light_block_import_without_justifications, RA, PRA>( + client: Arc>, + authority_set_provider: Arc>, + api: Arc, + ) -> Result, ClientError> + where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi, + { + light_block_import(client, authority_set_provider, api).map(NoJustificationsImport) + } + fn import_block( new_authorities: Option>, justification: Option, diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index d8800acb59100..7ecc345b1b823 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -29,7 +29,9 @@ use client::{ }; use test_client::{self, runtime::BlockNumber}; use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult}; -use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport}; +use consensus_common::import_queue::{SharedBlockImport, SharedJustificationImport, SharedFinalityProofImport, + SharedFinalityProofRequestBuilder, +}; use std::collections::{HashMap, HashSet}; use std::result; use runtime_primitives::traits::{ApiRef, ProvideRuntimeApi}; @@ -103,7 +105,13 @@ impl TestNetFactory for GrandpaTestNet { } fn make_block_import(&self, client: PeersClient) - -> (SharedBlockImport, Option>, Option>, PeerData) + -> ( + SharedBlockImport, + Option>, + Option>, + Option>, + PeerData, + ) { match client { PeersClient::Full(ref client) => { @@ -112,17 +120,22 @@ impl TestNetFactory for GrandpaTestNet { Arc::new(self.test_config.clone()) ).expect("Could not create block import for fresh peer."); let shared_import = Arc::new(import); - (shared_import.clone(), Some(shared_import), None, Mutex::new(Some(link))) + (shared_import.clone(), Some(shared_import), None, None, Mutex::new(Some(link))) }, PeersClient::Light(ref client) => { + use crate::light_import::tests::light_block_import_without_justifications; + let authorities_provider = Arc::new(self.test_config.clone()); - let import = light_block_import( + // forbid direct finalization using justification that cames with the block + // => light clients will try to fetch finality proofs + let import = light_block_import_without_justifications( client.clone(), authorities_provider, Arc::new(self.test_config.clone()) ).expect("Could not create block import for fresh peer."); + let finality_proof_req_builder = import.0.create_finality_proof_request_builder(); let shared_import = Arc::new(import); - (shared_import.clone(), None, Some(shared_import), Mutex::new(None)) + (shared_import.clone(), None, Some(shared_import), Some(finality_proof_req_builder), Mutex::new(None)) }, } } @@ -238,7 +251,7 @@ impl Network for MessageRouting { self.validator.note_set(set_id); let inner = self.inner.lock(); let peer = inner.peer(self.peer_id); - let messages = peer.consensus_gossip_messages_for( + let messages = peer.consensus_gossip_messages_for( GRANDPA_ENGINE_ID, make_commit_topic(set_id), ); @@ -1106,9 +1119,29 @@ fn test_bad_justification() { } #[test] -fn justification_is_fetched_by_light_client_when_consensus_data_changes() { +fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() { let _ = ::env_logger::try_init(); + let peers = &[AuthorityKeyring::Alice]; + let mut net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); + net.add_light_peer(&GrandpaTestNet::default_config()); + + // import block#1 WITH consensus data change. Light client ignores justification + // && instead fetches finality proof for block #1 + let new_authorities = vec![AuthorityId::from_raw([42; 32])]; + net.peer(0).push_authorities_change_block(new_authorities); + let net = Arc::new(Mutex::new(net)); + run_to_completion(1, net.clone(), peers); + net.lock().sync(); + + // check that the block#1 is finalized on light client + assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); +} + +#[test] +fn empty_finality_proof_is_returned_to_light_client_when_consensus_data_changes() { +/* let _ = ::env_logger::try_init(); + let peers = &[AuthorityKeyring::Alice]; let net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); @@ -1123,5 +1156,5 @@ fn justification_is_fetched_by_light_client_when_consensus_data_changes() { net.lock().sync(); // ... and check that the block#1 is finalized on light client - assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); + assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1);*/ } diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index ccc11122c52ca..50f83a8fb87d5 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -103,6 +103,12 @@ impl ExtraRequestsAggregator { self.justifications.peer_disconnected(who); self.finality_proofs.peer_disconnected(who); } + + /// Clear all data. + pub(crate) fn clear(&mut self) { + self.justifications.clear(); + self.finality_proofs.clear(); + } } /// Manages pending block extra data (e.g. justification) requests. diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index 3363f32ffca13..e5cba42185c1d 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -682,6 +682,7 @@ impl ChainSync { /// Clear all sync data. pub(crate) fn clear(&mut self) { + self.extra_requests.clear(); self.blocks.clear(); self.peers.clear(); } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index a1f69015d913f..7d67c157cb382 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -34,7 +34,10 @@ use client::block_builder::BlockBuilder; use client::backend::AuxStore; use crate::config::{ProtocolConfig, Roles}; use consensus::import_queue::{BasicQueue, ImportQueue, IncomingBlock}; -use consensus::import_queue::{Link, SharedBlockImport, SharedJustificationImport, Verifier, SharedFinalityProofImport}; +use consensus::import_queue::{ + Link, SharedBlockImport, SharedJustificationImport, Verifier, SharedFinalityProofImport, + SharedFinalityProofRequestBuilder, +}; use consensus::{Error as ConsensusError, ErrorKind as ConsensusErrorKind}; use consensus::{BlockOrigin, ForkChoiceStrategy, ImportBlock, JustificationImport}; use crate::consensus_gossip::ConsensusGossip; @@ -256,6 +259,10 @@ impl + Clone> Link for TestLink { self.link.request_finality_proof(hash, number); } + fn set_finality_proof_request_builder(&self, request_builder: SharedFinalityProofRequestBuilder) { + self.link.set_finality_proof_request_builder(request_builder); + } + fn useless_peer(&self, who: NodeIndex, reason: &str) { self.link.useless_peer(who, reason); } @@ -611,9 +618,15 @@ pub trait TestNetFactory: Sized { /// Get custom block import handle for fresh client, along with peer data. fn make_block_import(&self, client: PeersClient) - -> (SharedBlockImport, Option>, Option>, Self::PeerData) + -> ( + SharedBlockImport, + Option>, + Option>, + Option>, + Self::PeerData, + ) { - (client.as_block_import(), None, None, Default::default()) + (client.as_block_import(), None, None, None, Default::default()) } /// Get finality proof provider (if supported). @@ -641,7 +654,8 @@ pub trait TestNetFactory: Sized { let client = Arc::new(test_client::new()); let tx_pool = Arc::new(EmptyTransactionPool); let verifier = self.make_verifier(PeersClient::Full(client.clone()), config); - let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Full(client.clone())); + let (block_import, justification_import, finality_proof_import, finality_proof_request_builder, data) + = self.make_block_import(PeersClient::Full(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); let import_queue = Box::new(BasicQueue::new( @@ -649,7 +663,7 @@ pub trait TestNetFactory: Sized { block_import, justification_import, finality_proof_import, - None, + finality_proof_request_builder, )); let status_sinks = Arc::new(Mutex::new(Vec::new())); let is_offline = Arc::new(AtomicBool::new(true)); @@ -698,7 +712,8 @@ pub trait TestNetFactory: Sized { let client = Arc::new(test_client::new_light()); let tx_pool = Arc::new(EmptyTransactionPool); let verifier = self.make_verifier(PeersClient::Light(client.clone()), &config); - let (block_import, justification_import, finality_proof_import, data) = self.make_block_import(PeersClient::Light(client.clone())); + let (block_import, justification_import, finality_proof_import, finality_proof_request_builder, data) + = self.make_block_import(PeersClient::Light(client.clone())); let (network_sender, network_port) = network_channel(ProtocolId::default()); let import_queue = Box::new(BasicQueue::new( @@ -706,7 +721,7 @@ pub trait TestNetFactory: Sized { block_import, justification_import, finality_proof_import, - None, + finality_proof_request_builder, )); let status_sinks = Arc::new(Mutex::new(Vec::new())); let is_offline = Arc::new(AtomicBool::new(true)); @@ -994,8 +1009,14 @@ impl TestNetFactory for JustificationTestNet { } fn make_block_import(&self, client: PeersClient) - -> (SharedBlockImport, Option>, Option>, Self::PeerData) + -> ( + SharedBlockImport, + Option>, + Option>, + Option>, + Self::PeerData, + ) { - (client.as_block_import(), Some(Arc::new(ForceFinalized(client))), None, Default::default()) + (client.as_block_import(), Some(Arc::new(ForceFinalized(client))), None, None, Default::default()) } } From 6d3ba01610e8848d9577081dab585730461dd58e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 15 Mar 2019 10:53:07 +0300 Subject: [PATCH 28/42] empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different --- core/finality-grandpa/src/tests.rs | 75 +++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index 7ecc345b1b823..a9bce2e1b6608 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -1128,33 +1128,76 @@ fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() { // import block#1 WITH consensus data change. Light client ignores justification // && instead fetches finality proof for block #1 - let new_authorities = vec![AuthorityId::from_raw([42; 32])]; - net.peer(0).push_authorities_change_block(new_authorities); + net.peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); let net = Arc::new(Mutex::new(net)); run_to_completion(1, net.clone(), peers); net.lock().sync(); - // check that the block#1 is finalized on light client + // check that the block#1 is finalized on light client assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); } #[test] -fn empty_finality_proof_is_returned_to_light_client_when_consensus_data_changes() { -/* let _ = ::env_logger::try_init(); +fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different() { + // for debug: to ensure that without forced change light client will sync finality proof + const FORCE_CHANGE: bool = true; - let peers = &[AuthorityKeyring::Alice]; - let net = GrandpaTestNet::new(TestApi::new(make_ids(peers)), 1); + let _ = ::env_logger::try_init(); - // import block#1 WITH consensus data change + ensure if is finalized (with justification) - let new_authorities = vec![AuthorityId::from_raw([42; 32])]; - net.peer(0).push_authorities_change_block(new_authorities); - let net = Arc::new(Mutex::new(net)); - run_to_completion(1, net.clone(), peers); + // two of these guys are offline. + let genesis_authorities = if FORCE_CHANGE { + vec![ + AuthorityKeyring::Alice, + AuthorityKeyring::Bob, + AuthorityKeyring::Charlie, + AuthorityKeyring::One, + AuthorityKeyring::Two, + ] + } else { + vec![ + AuthorityKeyring::Alice, + AuthorityKeyring::Bob, + AuthorityKeyring::Charlie, + ] + }; + let peers_a = &[AuthorityKeyring::Alice, AuthorityKeyring::Bob, AuthorityKeyring::Charlie]; + let api = TestApi::new(make_ids(&genesis_authorities)); - // add && sync light peer + let voters = make_ids(peers_a); + let forced_transitions = api.forced_changes.clone(); + let net = GrandpaTestNet::new(api, 3); + let net = Arc::new(Mutex::new(net)); net.lock().add_light_peer(&GrandpaTestNet::default_config()); - net.lock().sync(); - // ... and check that the block#1 is finalized on light client - assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1);*/ + let runner_net = net.clone(); + let add_blocks = move || { + net.lock().peer(0).push_blocks(1, false); // best is #1 + + // add a forced transition at block 5. + if FORCE_CHANGE { + let parent_hash = net.lock().peer(0).client().info().unwrap().chain.best_hash; + forced_transitions.lock().insert(parent_hash, (0, ScheduledChange { + next_authorities: voters.clone(), + delay: 3, + })); + } + + // ensure block#10 enacts authorities set change => justification is generated + // normally it will reach light client, but because of the forced change, it will not + net.lock().peer(0).push_blocks(8, false); // best is #9 + net.lock().peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); // #10 + net.lock().peer(0).push_blocks(1, false); // best is #11 + net.lock().sync(); + }; + + // finalize block #11 on full clients + run_to_completion_with(11, runner_net.clone(), peers_a, add_blocks); + // request finalization by light client + runner_net.lock().sync(); + + // check block, finalized on light client + assert_eq!( + runner_net.lock().peer(3).client().info().unwrap().chain.finalized_number, + if FORCE_CHANGE { 0 } else { 10 }, + ); } From b99f39d2c809340ddd8a1a32809bc8a58c43857e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 19 Mar 2019 09:29:52 +0300 Subject: [PATCH 29/42] missing trait method impl --- core/network/src/service.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/network/src/service.rs b/core/network/src/service.rs index 9b0f10877205d..c6cdc8cb7207e 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -116,6 +116,14 @@ impl> Link for NetworkLink { )); } + fn finality_proof_imported(&self, who: NodeIndex, hash: &B::Hash, number: NumberFor, success: bool) { + let _ = self.protocol_sender.send(ProtocolMsg::FinalityProofImportResult(hash.clone(), number, success)); + if !success { + let reason = Severity::Bad(format!("Invalid finality proof provided for #{}", hash).to_string()); + let _ = self.network_sender.send(NetworkMsg::ReportPeer(who, reason)); + } + } + fn useless_peer(&self, who: NodeIndex, reason: &str) { trace!(target:"sync", "Useless peer {}, {}", who, reason); self.network_sender.send(NetworkMsg::ReportPeer(who, Severity::Useless(reason.to_string()))); From d6f01fab51401431f7241fac3e8d836bfe9a2430 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2019 17:04:15 +0300 Subject: [PATCH 30/42] fixed proof-of-finality docs --- core/finality-grandpa/src/finality_proof.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index fdb01d355fc1e..b7a9442fbaa5e 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -18,7 +18,7 @@ //! //! Finality of block B is proved by providing: //! 1) the justification for the descendant block F; -//! 2) headers sub-chain (U; F], where U is the last block known to the caller; +//! 2) headers sub-chain (B; F] if B != F; //! 3) proof of GRANDPA::authorities() if the set changes at block F. //! //! Since earliest possible justification is returned, the GRANDPA authorities set @@ -26,7 +26,7 @@ //! that enacts new GRANDPA authorities set always comes with justification). It also //! means that the `set_id` is the same at blocks B and F. //! -//! If authorities set changes several times in the (U; F] interval, multiple finality +//! If authorities set changes several times in the (B; F] interval, multiple finality //! proof fragments are returned && each should be verified separately. use std::sync::Arc; @@ -192,9 +192,9 @@ pub struct FinalityEffects { /// Single fragment of proof-of-finality. /// /// Finality for block B is proved by providing: -/// 1) the justification for the descendant block F; -/// 2) headers sub-chain (U; F], where U is the last block known to the caller; -/// 3) proof of GRANDPA::authorities() if the set changes at block F. +//! 1) the justification for the descendant block F; +//! 2) headers sub-chain (B; F] if B != F; +//! 3) proof of GRANDPA::authorities() if the set changes at block F. #[derive(Debug, PartialEq, Encode, Decode)] struct FinalityProofFragment { /// The hash of block F for which justification is provided. From 957139f8af4637d66764a59a70bca468a4aff2fe Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2019 17:06:51 +0300 Subject: [PATCH 31/42] one more doc fix --- core/finality-grandpa/src/finality_proof.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index b7a9442fbaa5e..23711550b110a 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -26,8 +26,9 @@ //! that enacts new GRANDPA authorities set always comes with justification). It also //! means that the `set_id` is the same at blocks B and F. //! -//! If authorities set changes several times in the (B; F] interval, multiple finality -//! proof fragments are returned && each should be verified separately. +//! Let U be the last finalized block known to caller. If authorities set has changed several +//! times in the (U; F] interval, multiple finality proof fragments are returned && each should +//! be verified separately. use std::sync::Arc; use log::{trace, warn}; From 262cdfab7b8a16617357adf64f60b3e36d492051 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 20 Mar 2019 17:11:28 +0300 Subject: [PATCH 32/42] fix docs --- core/finality-grandpa/src/finality_proof.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 23711550b110a..a3681063ae6cf 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -193,9 +193,9 @@ pub struct FinalityEffects { /// Single fragment of proof-of-finality. /// /// Finality for block B is proved by providing: -//! 1) the justification for the descendant block F; -//! 2) headers sub-chain (B; F] if B != F; -//! 3) proof of GRANDPA::authorities() if the set changes at block F. +/// 1) the justification for the descendant block F; +/// 2) headers sub-chain (B; F] if B != F; +/// 3) proof of GRANDPA::authorities() if the set changes at block F. #[derive(Debug, PartialEq, Encode, Decode)] struct FinalityProofFragment { /// The hash of block F for which justification is provided. From 63cde59f4f51031aa22239c38fbfd7bca446aa51 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 2 Apr 2019 13:14:19 +0300 Subject: [PATCH 33/42] initialize authorities cache (post-merge fix) --- core/client/db/src/cache/mod.rs | 11 ++++++++++- core/client/db/src/light.rs | 2 ++ core/client/src/blockchain.rs | 2 +- core/consensus/aura/src/lib.rs | 9 +++++---- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/core/client/db/src/cache/mod.rs b/core/client/db/src/cache/mod.rs index e869d7aa08f24..319a743f6841e 100644 --- a/core/client/db/src/cache/mod.rs +++ b/core/client/db/src/cache/mod.rs @@ -70,6 +70,7 @@ pub struct DbCache { key_lookup_column: Option, header_column: Option, authorities_column: Option, + genesis_hash: Block::Hash, best_finalized_block: ComplexBlockId, } @@ -80,6 +81,7 @@ impl DbCache { key_lookup_column: Option, header_column: Option, authorities_column: Option, + genesis_hash: Block::Hash, best_finalized_block: ComplexBlockId, ) -> Self { Self { @@ -88,10 +90,16 @@ impl DbCache { key_lookup_column, header_column, authorities_column, + genesis_hash, best_finalized_block, } } + /// Set genesis block hash. + pub fn set_genesis_hash(&mut self, genesis_hash: Block::Hash) { + self.genesis_hash = genesis_hash; + } + /// Begin cache transaction. pub fn transaction<'a>(&'a mut self, tx: &'a mut DBTransaction) -> DbCacheTransaction<'a, Block> { DbCacheTransaction { @@ -254,8 +262,9 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { pub struct DbCacheSync(pub RwLock>); impl BlockchainCache for DbCacheSync { - fn initialize(&self, key: &CacheKeyId, genesis_hash: Block::Hash, data: Vec) -> ClientResult<()> { + fn initialize(&self, key: &CacheKeyId, data: Vec) -> ClientResult<()> { let mut cache = self.0.write(); + let genesis_hash = cache.genesis_hash; let cache_contents = vec![(*key, data)].into_iter().collect(); let db = cache.db.clone(); let mut dbtx = DBTransaction::new(); diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 62b6486f54d7e..95e4385027649 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -91,6 +91,7 @@ impl LightStorage columns::KEY_LOOKUP, columns::HEADER, columns::CACHE, + meta.genesis_hash, ComplexBlockId::new(meta.finalized_hash, meta.finalized_number), ); @@ -406,6 +407,7 @@ impl LightBlockchainStorage for LightStorage let is_genesis = number.is_zero(); if is_genesis { + self.cache.0.write().set_genesis_hash(hash); transaction.put(columns::META, meta_keys::GENESIS_HASH, hash.as_ref()); } diff --git a/core/client/src/blockchain.rs b/core/client/src/blockchain.rs index 09ab7119de69c..c90dfafc3c1eb 100644 --- a/core/client/src/blockchain.rs +++ b/core/client/src/blockchain.rs @@ -104,7 +104,7 @@ pub trait Cache: Send + Sync { /// /// The operation should be performed once before anything else is inserted in the cache. /// Otherwise cache may end up in inconsistent state. - fn initialize(&self, key: &well_known_cache_keys::Id, genesis_hash: Block::Hash, value: Vec) -> Result<()>; + fn initialize(&self, key: &well_known_cache_keys::Id, value_at_genesis: Vec) -> Result<()>; /// Returns cached value by the given key. fn get_at(&self, key: &well_known_cache_keys::Id, block: &BlockId) -> Option>; } diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index eca357da9ee09..96e4a16dabf3e 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -44,7 +44,7 @@ use client::runtime_api::ApiExt; use aura_primitives::AURA_ENGINE_ID; use runtime_primitives::{generic, generic::BlockId, Justification}; use runtime_primitives::traits::{ - Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, AuthorityIdFor, + Block, Header, Digest, DigestItemFor, DigestItem, ProvideRuntimeApi, AuthorityIdFor, Zero, }; use primitives::Pair; use inherents::{InherentDataProviders, InherentData, RuntimeString}; @@ -693,7 +693,7 @@ impl Authorities for AuraVerifier where } } -fn initialize_genesis_authorities(genesis_hash: B::Hash, client: &C) -> Result<(), ConsensusError> where +fn initialize_authorities_cache(client: &C) -> Result<(), ConsensusError> where B: Block, C: ProvideRuntimeApi + ProvideCache, C::Api: AuthoritiesApi, @@ -705,7 +705,7 @@ fn initialize_genesis_authorities(genesis_hash: B::Hash, client: &C) -> Re }; // check if we already have initialized the cache - let genesis_id = BlockId::Hash(genesis_hash); + let genesis_id = BlockId::Number(Zero::zero()); let genesis_authorities: Option>> = cache .get_at(&well_known_cache_keys::AUTHORITIES, &genesis_id) .and_then(|v| Decode::decode(&mut &v[..])); @@ -719,7 +719,7 @@ fn initialize_genesis_authorities(genesis_hash: B::Hash, client: &C) -> Re error, ))); let genesis_authorities = client.runtime_api().authorities(&genesis_id).map_err(map_err)?; - cache.initialize(&well_known_cache_keys::AUTHORITIES, genesis_hash, genesis_authorities.encode()) + cache.initialize(&well_known_cache_keys::AUTHORITIES, genesis_authorities.encode()) .map_err(map_err)?; Ok(()) @@ -777,6 +777,7 @@ pub fn import_queue( P::Signature: Encode + Decode, { register_aura_inherent_data_provider(&inherent_data_providers, slot_duration.get())?; + initialize_authorities_cache(&*client)?; let verifier = Arc::new( AuraVerifier { From b8c71b50aae4c3eccd2d0fe028c150e4f86fe77b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2019 13:35:44 +0300 Subject: [PATCH 34/42] fixed cache initialization (post-merge fix) --- core/client/db/src/cache/list_cache.rs | 48 +++++++++++++++++--------- core/client/db/src/cache/mod.rs | 23 +++++++++--- core/client/db/src/light.rs | 24 +++++++++++-- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/core/client/db/src/cache/list_cache.rs b/core/client/db/src/cache/list_cache.rs index 1e641534f969c..a783614d586a3 100644 --- a/core/client/db/src/cache/list_cache.rs +++ b/core/client/db/src/cache/list_cache.rs @@ -46,7 +46,7 @@ use log::warn; use client::error::{ErrorKind as ClientErrorKind, Result as ClientResult}; use runtime_primitives::traits::{Block as BlockT, NumberFor, As, Zero}; -use crate::cache::{CacheItemT, ComplexBlockId}; +use crate::cache::{CacheItemT, ComplexBlockId, EntryType}; use crate::cache::list_entry::{Entry, StorageEntry}; use crate::cache::list_storage::{Storage, StorageTransaction, Metadata}; @@ -174,10 +174,10 @@ impl> ListCache parent: ComplexBlockId, block: ComplexBlockId, value: Option, - is_final: bool, + entry_type: EntryType, ) -> ClientResult>> { // this guarantee is currently provided by LightStorage && we're relying on it here - debug_assert!(!is_final || self.best_finalized_block.hash == parent.hash); + debug_assert!(entry_type != EntryType::Final || self.best_finalized_block.hash == parent.hash); // we do not store any values behind finalized if block.number != Zero::zero() && self.best_finalized_block.number >= block.number { @@ -185,6 +185,7 @@ impl> ListCache } // if the block is not final, it is possibly appended to/forking from existing unfinalized fork + let is_final = entry_type == EntryType::Final || entry_type == EntryType::Genesis; if !is_final { let mut fork_and_action = None; @@ -831,12 +832,27 @@ pub mod tests { #[test] fn list_on_block_insert_works() { + let nfin = EntryType::NonFinal; + let fin = EntryType::Final; + // when trying to insert block < finalized number assert!(ListCache::new(DummyStorage::new(), 1024, test_id(100)) - .on_block_insert(&mut DummyTransaction::new(), test_id(49), test_id(50), Some(50), false).unwrap().is_none()); + .on_block_insert( + &mut DummyTransaction::new(), + test_id(49), + test_id(50), + Some(50), + nfin, + ).unwrap().is_none()); // when trying to insert block @ finalized number assert!(ListCache::new(DummyStorage::new(), 1024, test_id(100)) - .on_block_insert(&mut DummyTransaction::new(), test_id(99), test_id(100), Some(100), false).unwrap().is_none()); + .on_block_insert( + &mut DummyTransaction::new(), + test_id(99), + test_id(100), + Some(100), + nfin, + ).unwrap().is_none()); // when trying to insert non-final block AND it appends to the best block of unfinalized fork // AND new value is the same as in the fork' best block @@ -848,7 +864,7 @@ pub mod tests { ); cache.unfinalized[0].best_block = Some(test_id(4)); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, test_id(4), test_id(5), Some(4), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, test_id(4), test_id(5), Some(4), nfin).unwrap(), Some(CommitOperation::AppendNewBlock(0, test_id(5)))); assert!(tx.inserted_entries().is_empty()); assert!(tx.removed_entries().is_empty()); @@ -856,7 +872,7 @@ pub mod tests { // when trying to insert non-final block AND it appends to the best block of unfinalized fork // AND new value is the same as in the fork' best block let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, test_id(4), test_id(5), Some(5), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, test_id(4), test_id(5), Some(5), nfin).unwrap(), Some(CommitOperation::AppendNewEntry(0, Entry { valid_from: test_id(5), value: Some(5) }))); assert_eq!(*tx.inserted_entries(), vec![test_id(5).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -872,7 +888,7 @@ pub mod tests { 1024, test_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(4), correct_id(5), Some(4), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(4), correct_id(5), Some(4), nfin).unwrap(), Some(CommitOperation::AppendNewBlock(0, correct_id(5)))); assert!(tx.inserted_entries().is_empty()); assert!(tx.removed_entries().is_empty()); @@ -880,7 +896,7 @@ pub mod tests { // when trying to insert non-final block AND it is the first block that appends to the best block of unfinalized fork // AND new value is the same as in the fork' best block let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(4), correct_id(5), Some(5), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(4), correct_id(5), Some(5), nfin).unwrap(), Some(CommitOperation::AppendNewEntry(0, Entry { valid_from: correct_id(5), value: Some(5) }))); assert_eq!(*tx.inserted_entries(), vec![correct_id(5).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -898,7 +914,7 @@ pub mod tests { 1024, correct_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(3), fork_id(0, 3, 4), Some(14), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(3), fork_id(0, 3, 4), Some(14), nfin).unwrap(), Some(CommitOperation::AddNewFork(Entry { valid_from: fork_id(0, 3, 4), value: Some(14) }))); assert_eq!(*tx.inserted_entries(), vec![fork_id(0, 3, 4).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -913,7 +929,7 @@ pub mod tests { 1024, correct_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), false).unwrap(), None); + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), nfin).unwrap(), None); assert!(tx.inserted_entries().is_empty()); assert!(tx.removed_entries().is_empty()); assert!(tx.updated_meta().is_none()); @@ -926,7 +942,7 @@ pub mod tests { 1024, correct_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), false).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), nfin).unwrap(), Some(CommitOperation::AddNewFork(Entry { valid_from: correct_id(3), value: Some(3) }))); assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -935,7 +951,7 @@ pub mod tests { // when inserting finalized entry AND there are no previous finalzed entries let cache = ListCache::new(DummyStorage::new(), 1024, correct_id(2)); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), true).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), fin).unwrap(), Some(CommitOperation::BlockFinalized(correct_id(3), Some(Entry { valid_from: correct_id(3), value: Some(3) }), Default::default()))); assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -948,14 +964,14 @@ pub mod tests { 1024, correct_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), true).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), fin).unwrap(), Some(CommitOperation::BlockFinalized(correct_id(3), None, Default::default()))); assert!(tx.inserted_entries().is_empty()); assert!(tx.removed_entries().is_empty()); assert!(tx.updated_meta().is_none()); // when inserting finalized entry AND value differs from previous finalized let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), true).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(3), fin).unwrap(), Some(CommitOperation::BlockFinalized(correct_id(3), Some(Entry { valid_from: correct_id(3), value: Some(3) }), Default::default()))); assert_eq!(*tx.inserted_entries(), vec![correct_id(3).hash].into_iter().collect()); assert!(tx.removed_entries().is_empty()); @@ -970,7 +986,7 @@ pub mod tests { 1024, correct_id(2) ); let mut tx = DummyTransaction::new(); - assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), true).unwrap(), + assert_eq!(cache.on_block_insert(&mut tx, correct_id(2), correct_id(3), Some(2), fin).unwrap(), Some(CommitOperation::BlockFinalized(correct_id(3), None, vec![0].into_iter().collect()))); } diff --git a/core/client/db/src/cache/mod.rs b/core/client/db/src/cache/mod.rs index 319a743f6841e..e4e23a5ca1d3b 100644 --- a/core/client/db/src/cache/mod.rs +++ b/core/client/db/src/cache/mod.rs @@ -38,6 +38,17 @@ mod list_storage; /// Minimal post-finalization age age of finalized blocks before they'll pruned. const PRUNE_DEPTH: u64 = 1024; +/// The type of entry that is inserted to the cache. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum EntryType { + /// Non-final entry. + NonFinal, + /// Final entry. + Final, + /// Genesis entry (inserted during cache initialization). + Genesis, +} + /// Block identifier that holds both hash and number. #[derive(Clone, Debug, Encode, Decode, PartialEq)] pub struct ComplexBlockId { @@ -190,7 +201,7 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { parent: ComplexBlockId, block: ComplexBlockId, data_at: HashMap>, - is_final: bool, + entry_type: EntryType, ) -> ClientResult { assert!(self.cache_at_op.is_empty()); @@ -211,7 +222,7 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { parent.clone(), block.clone(), value.or(cache.value_at_block(&parent)?), - is_final, + entry_type, )?; if let Some(op) = op { self.cache_at_op.insert(name, op); @@ -222,8 +233,10 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> { data_at.into_iter().try_for_each(|(name, data)| insert_op(name, Some(data)))?; missed_caches.into_iter().try_for_each(|name| insert_op(name, None))?; - if is_final { - self.best_finalized_block = Some(block); + match entry_type { + EntryType::Final | EntryType::Genesis => + self.best_finalized_block = Some(block), + EntryType::NonFinal => (), } Ok(self) @@ -273,7 +286,7 @@ impl BlockchainCache for DbCacheSync { ComplexBlockId::new(Default::default(), Zero::zero()), ComplexBlockId::new(genesis_hash, Zero::zero()), cache_contents, - true, + EntryType::Genesis, )?; let tx_ops = tx.into_ops(); db.write(dbtx).map_err(db_err)?; diff --git a/core/client/db/src/light.rs b/core/client/db/src/light.rs index 95e4385027649..2fe50305cd8c2 100644 --- a/core/client/db/src/light.rs +++ b/core/client/db/src/light.rs @@ -34,7 +34,7 @@ use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One, As, NumberFor, Digest, DigestItem}; use consensus_common::well_known_cache_keys; -use crate::cache::{DbCacheSync, DbCache, ComplexBlockId}; +use crate::cache::{DbCacheSync, DbCache, ComplexBlockId, EntryType as CacheEntryType}; use crate::utils::{self, meta_keys, Meta, db_err, open_database, read_db, block_id_to_lookup_key, read_meta}; use crate::DatabaseSettings; @@ -436,7 +436,7 @@ impl LightBlockchainStorage for LightStorage ComplexBlockId::new(*header.parent_hash(), if number.is_zero() { Zero::zero() } else { number - One::one() }), ComplexBlockId::new(hash, number), cache_at, - finalized, + if finalized { CacheEntryType::Final } else { CacheEntryType::NonFinal }, )? .into_ops(); @@ -1042,4 +1042,24 @@ pub(crate) mod tests { // leaves at same height stay. Leaves at lower heights pruned. assert_eq!(db.leaves.read().hashes(), vec![block2_a, block2_b, block2_c]); } + + #[test] + fn cache_can_be_initialized_after_genesis_inserted() { + let db = LightStorage::::new_test(); + + // before cache is initialized => None + assert_eq!(db.cache().get_at(b"test", &BlockId::Number(0)), None); + + // insert genesis block (no value for cache is provided) + insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0)); + + // after genesis is inserted => None + assert_eq!(db.cache().get_at(b"test", &BlockId::Number(0)), None); + + // initialize cache + db.cache().initialize(b"test", vec![42]).unwrap(); + + // after genesis is inserted + cache is initialized => Some + assert_eq!(db.cache().get_at(b"test", &BlockId::Number(0)), Some(vec![42])); + } } From 100f4c816e99561d9d35198fb4f77f9622e34154 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2019 15:44:56 +0300 Subject: [PATCH 35/42] post-fix merge: fix light + GRANDPA tests (bad way) --- core/finality-grandpa/src/tests.rs | 21 ++++++++++++--------- core/network/src/test/mod.rs | 5 +++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index 0d0c4365fbe1e..2b8c9da13e333 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -1314,10 +1314,13 @@ fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() { net.peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); let net = Arc::new(Mutex::new(net)); run_to_completion(1, net.clone(), peers); - net.lock().sync(); + net.lock().sync_without_disconnects(); // check that the block#1 is finalized on light client - assert_eq!(net.lock().peer(1).client().info().unwrap().chain.finalized_number, 1); + while net.lock().peer(1).client().info().unwrap().chain.finalized_number != 1 { + net.lock().tick_peer(1); + net.lock().sync_without_disconnects(); + } } #[test] @@ -1370,17 +1373,17 @@ fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_differ net.lock().peer(0).push_blocks(8, false); // best is #9 net.lock().peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); // #10 net.lock().peer(0).push_blocks(1, false); // best is #11 - net.lock().sync(); + net.lock().sync_without_disconnects(); }; // finalize block #11 on full clients run_to_completion_with(11, runner_net.clone(), peers_a, add_blocks); // request finalization by light client -// runner_net.lock().sync(); + runner_net.lock().sync_without_disconnects(); - // check block, finalized on light client - let required_finalized_number = if FORCE_CHANGE { 0 } else { 10 }; - while runner_net.lock().peer(1).client().info().unwrap().chain.finalized_number != required_finalized_number { - runner_net.lock().sync(); - } + // check block, finalized on light client + assert_eq!( + runner_net.lock().peer(3).client().info().unwrap().chain.finalized_number, + if FORCE_CHANGE { 0 } else { 10 }, + ); } diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index 6fcd55f494031..f5a09268d61c0 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -933,6 +933,11 @@ pub trait TestNetFactory: Sized { self.peers()[i].restart_sync(); } + /// Maintain sync for a peer. + fn tick_peer(&mut self, i: usize) { + self.peers()[i].sync_step(); + } + /// Perform synchronization until complete, if provided the /// given nodes set are excluded from sync. fn sync_with(&mut self, disconnect: bool, disconnected: Option>) { From 44d7714be1930eed57296da436d1603edb8971fc Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 16 Apr 2019 16:10:10 +0300 Subject: [PATCH 36/42] proper fix of empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different --- core/finality-grandpa/src/tests.rs | 2 +- core/network/src/test/mod.rs | 31 ++++++++++++++++++------------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index 2b8c9da13e333..75754f4a85d8e 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -1353,7 +1353,6 @@ fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_differ let forced_transitions = api.forced_changes.clone(); let net = GrandpaTestNet::new(api, 3); let net = Arc::new(Mutex::new(net)); - net.lock().add_light_peer(&GrandpaTestNet::default_config()); let runner_net = net.clone(); let add_blocks = move || { @@ -1379,6 +1378,7 @@ fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_differ // finalize block #11 on full clients run_to_completion_with(11, runner_net.clone(), peers_a, add_blocks); // request finalization by light client + runner_net.lock().add_light_peer(&GrandpaTestNet::default_config()); runner_net.lock().sync_without_disconnects(); // check block, finalized on light client diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index f5a09268d61c0..aca08e7b540d5 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -720,6 +720,21 @@ pub trait TestNetFactory: Sized { net } + /// Add created peer. + fn add_peer(&mut self, peer: Arc>) { + if self.started() { + peer.start(); + self.peers().iter().for_each(|other| { + other.on_connect(&*peer); + peer.on_connect(other); + }); + } + + self.mut_peers(|peers| { + peers.push(peer) + }); + } + /// Add a full peer. fn add_full_peer(&mut self, config: &ProtocolConfig) { let client = Arc::new(test_client::new()); @@ -757,7 +772,7 @@ pub trait TestNetFactory: Sized { specialization, ).unwrap(); - let peer = Arc::new(Peer::new( + self.add_peer(Arc::new(Peer::new( is_offline, is_major_syncing, peers, @@ -768,11 +783,7 @@ pub trait TestNetFactory: Sized { network_sender, network_port, data, - )); - - self.mut_peers(|peers| { - peers.push(peer) - }); + ))); } /// Add a light peer. @@ -815,7 +826,7 @@ pub trait TestNetFactory: Sized { specialization, ).unwrap(); - let peer = Arc::new(Peer::new( + self.add_peer(Arc::new(Peer::new( is_offline, is_major_syncing, peers, @@ -826,11 +837,7 @@ pub trait TestNetFactory: Sized { network_sender, network_port, data, - )); - - self.mut_peers(|peers| { - peers.push(peer) - }); + ))); } /// Start network. From 70c8b39fc59ccd81dd1e26b0494d74f80cadcefd Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 18 Apr 2019 09:31:26 +0300 Subject: [PATCH 37/42] fixed easy grumbles --- core/finality-grandpa/src/finality_proof.rs | 12 +++--------- core/finality-grandpa/src/lib.rs | 2 +- core/finality-grandpa/src/light_import.rs | 3 +-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index 3d0ed81934814..b52dfcfabb02d 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -27,8 +27,8 @@ //! means that the `set_id` is the same at blocks B and F. //! //! Let U be the last finalized block known to caller. If authorities set has changed several -//! times in the (U; F] interval, multiple finality proof fragments are returned && each should -//! be verified separately. +//! times in the (U; F] interval, multiple finality proof fragments are returned (one for each +//! authority set change) and they must be verified in-order. use std::sync::Arc; use log::{trace, warn}; @@ -397,8 +397,7 @@ pub(crate) fn prove_finality, B: BlockchainBackend, B>( blockchain: &B, current_set_id: u64, @@ -418,11 +417,6 @@ pub(crate) fn check_finality_proof, B>( remote_proof) } -/// Check proof-of-finality for the given block. -/// -/// Returns the vector of headers that MUST be validated + imported -/// AND. If at least one of those headers -/// is invalid, all other MUST be considered invalid. fn do_check_finality_proof, B, J>( blockchain: &B, current_set_id: u64, diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 6400f1a9bd283..3c072f528a651 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -97,7 +97,7 @@ mod service_integration; #[cfg(feature="service-integration")] pub use service_integration::{LinkHalfForService, BlockImportForService, BlockImportForLightService}; pub use communication::Network; -pub use finality_proof::{FinalityProofProvider, AuthoritySetForFinalityChecker, AuthoritySetForFinalityProver}; +pub use finality_proof::FinalityProofProvider; pub use light_import::light_block_import; use aux_schema::PersistentData; diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 16624008dadbf..cfe3d8c64e5d6 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -445,7 +445,7 @@ fn do_finalize_block, RA>( Ok(ImportResult::imported()) } -/// Load light impoty aux data from the store. +/// Load light import aux data from the store. fn load_aux_import_data, PRA>( last_finalized: Block::Hash, aux_store: &B, @@ -521,7 +521,6 @@ fn on_post_finalization_error(error: ClientError, value_type: &str) -> Consensus ConsensusError::from(ConsensusErrorKind::ClientImport(error.to_string())) } - #[cfg(test)] pub mod tests { use super::*; From ff3ceebc133312958461f80aae1b81dc703ca8f0 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 18 Apr 2019 12:00:29 +0300 Subject: [PATCH 38/42] import finality proofs in BlockImportWorker thread --- core/consensus/common/src/import_queue.rs | 135 ++++++++++++++++++---- 1 file changed, 111 insertions(+), 24 deletions(-) diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index 45fa081529fef..cbfe14fe67f62 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -154,7 +154,12 @@ impl BasicQueue { finality_proof_request_builder: Option>, ) -> Self { let (result_sender, result_port) = channel::unbounded(); - let worker_sender = BlockImportWorker::new(result_sender, verifier.clone(), block_import); + let worker_sender = BlockImportWorker::new( + result_sender, + verifier.clone(), + block_import, + finality_proof_import.clone(), + ); let importer_sender = BlockImporter::new( result_port, worker_sender, @@ -234,14 +239,17 @@ pub enum BlockImportMsg { Synchronize, } +#[cfg_attr(test, derive(Debug, PartialEq))] pub enum BlockImportWorkerMsg { ImportBlocks(BlockOrigin, Vec>), - Imported( + ImportedBlocks( Vec<( Result>, BlockImportError>, B::Hash, )>, ), + ImportFinalityProof(Origin, B::Hash, NumberFor, Vec), + ImportedFinalityProof(Origin, B::Hash, NumberFor, bool), #[cfg(any(test, feature = "test-helpers"))] Synchronize, } @@ -360,13 +368,19 @@ impl BlockImporter { }; let results = match msg { - BlockImportWorkerMsg::Imported(results) => (results), + BlockImportWorkerMsg::ImportedBlocks(results) => (results), + BlockImportWorkerMsg::ImportedFinalityProof(who, hash, number, success) => { + link.finality_proof_imported(who, &hash, number, success); + return true; + }, #[cfg(any(test, feature = "test-helpers"))] BlockImportWorkerMsg::Synchronize => { link.synchronized(); return true; }, - _ => unreachable!("Import Worker does not send ImportBlocks message; qed"), + BlockImportWorkerMsg::ImportBlocks(_, _) + | BlockImportWorkerMsg::ImportFinalityProof(_, _, _, _) + => unreachable!("Import Worker does not send Import* message; qed"), }; let mut has_error = false; let mut hashes = vec![]; @@ -448,16 +462,10 @@ impl BlockImporter { } fn handle_import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor, finality_proof: Vec) { - let success = self.finality_proof_import.as_ref().map(|finality_proof_import| { - finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier) - .map_err(|e| { - debug!("Finality proof import failed with {:?} for hash: {:?} number: {:?} coming from node: {:?}", e, hash, number, who); - e - }).is_ok() - }).unwrap_or(false); - if let Some(link) = self.link.as_ref() { - link.finality_proof_imported(who, &hash, number, success); - } + trace!(target: "sync", "Scheduling finality proof of {}/{} for import", number, hash); + self.worker_sender + .send(BlockImportWorkerMsg::ImportFinalityProof(who, hash, number, finality_proof)) + .expect("1. This is holding a sender to the worker, 2. the worker should not quit while a sender is still held; qed"); } fn handle_import_blocks(&mut self, origin: BlockOrigin, blocks: Vec>) { @@ -471,6 +479,7 @@ impl BlockImporter { struct BlockImportWorker> { result_sender: Sender>, block_import: SharedBlockImport, + finality_proof_import: Option>, verifier: Arc, } @@ -479,6 +488,7 @@ impl> BlockImportWorker { result_sender: Sender>, verifier: Arc, block_import: SharedBlockImport, + finality_proof_import: Option>, ) -> Sender> { let (sender, port) = channel::bounded(4); let _ = thread::Builder::new() @@ -488,6 +498,7 @@ impl> BlockImportWorker { result_sender, verifier, block_import, + finality_proof_import, }; for msg in port.iter() { // Working until all senders have been dropped... @@ -495,11 +506,16 @@ impl> BlockImportWorker { BlockImportWorkerMsg::ImportBlocks(origin, blocks) => { worker.import_a_batch_of_blocks(origin, blocks); }, + BlockImportWorkerMsg::ImportFinalityProof(who, hash, number, proof) => { + worker.import_finality_proof(who, hash, number, proof); + }, #[cfg(any(test, feature = "test-helpers"))] BlockImportWorkerMsg::Synchronize => { let _ = worker.result_sender.send(BlockImportWorkerMsg::Synchronize); }, - _ => unreachable!("Import Worker does not receive the Imported message; qed"), + BlockImportWorkerMsg::ImportedBlocks(_) + | BlockImportWorkerMsg::ImportedFinalityProof(_, _, _, _) + => unreachable!("Import Worker does not receive the Imported* messages; qed"), } } }) @@ -549,10 +565,32 @@ impl> BlockImportWorker { let _ = self .result_sender - .send(BlockImportWorkerMsg::Imported(results)); + .send(BlockImportWorkerMsg::ImportedBlocks(results)); trace!(target: "sync", "Imported {} of {}", imported, count); } + + fn import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor, finality_proof: Vec) { + let success = self.finality_proof_import.as_ref().map(|finality_proof_import| { + finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier) + .map_err(|e| { + debug!( + "Finality proof import failed with {:?} for hash: {:?} number: {:?} coming from node: {:?}", + e, + hash, + number, + who, + ); + e + }).is_ok() + }).unwrap_or(false); + + let _ = self + .result_sender + .send(BlockImportWorkerMsg::ImportedFinalityProof(who, hash, number, success)); + + trace!(target: "sync", "Imported finality proof for {}/{}", number, hash); + } } /// Hooks that the verification queue can use to influence the synchronization @@ -689,6 +727,7 @@ mod tests { #[derive(Debug, PartialEq)] enum LinkMsg { BlockImported, + FinalityProofImported, Disconnected, Restarted, } @@ -710,6 +749,9 @@ mod tests { fn block_imported(&self, _hash: &Hash, _number: NumberFor) { let _ = self.sender.send(LinkMsg::BlockImported); } + fn finality_proof_imported(&self, _: Origin, _: &Hash, _: NumberFor, _: bool) { + let _ = self.sender.send(LinkMsg::FinalityProofImported); + } fn useless_peer(&self, _: Origin, _: &str) { let _ = self.sender.send(LinkMsg::Disconnected); } @@ -758,17 +800,17 @@ mod tests { // Send a known let results = vec![(Ok(BlockImportResult::ImportedKnown(Default::default())), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported)); // Send a second known let results = vec![(Ok(BlockImportResult::ImportedKnown(Default::default())), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported)); // Send an unknown let results = vec![(Ok(BlockImportResult::ImportedUnknown(Default::default(), Default::default(), None)), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported)); // Send an unknown with peer and bad justification @@ -781,34 +823,79 @@ mod tests { needs_finality_proof: false, }, Some(peer_id.clone()))), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported)); assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected)); // Send an incomplete header let results = vec![(Err(BlockImportError::IncompleteHeader(Some(peer_id.clone()))), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected)); assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted)); // Send an unknown parent let results = vec![(Err(BlockImportError::UnknownParent), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted)); // Send a verification failed let results = vec![(Err(BlockImportError::VerificationFailed(Some(peer_id.clone()), String::new())), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected)); assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted)); // Send an error let results = vec![(Err(BlockImportError::Error), Default::default())]; - let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap(); + let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap(); assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted)); // Drop the importer sender first, ensuring graceful shutdown. drop(importer_sender); } + + #[test] + fn process_finality_proof_import_result_works() { + let (result_sender, result_port) = channel::unbounded(); + let (worker_sender, worker_receiver) = channel::unbounded(); + let (link_sender, link_port) = channel::unbounded(); + let importer_sender = BlockImporter::::new(result_port, worker_sender, Arc::new(()), None, None, None); + let link = TestLink::new(link_sender); + let (ack_sender, start_ack_port) = channel::bounded(4); + let _ = importer_sender.send(BlockImportMsg::Start(Box::new(link.clone()), ack_sender)); + let who = Origin::random(); + + // Ensure the importer handles Start before any result messages. + start_ack_port.recv().unwrap().unwrap(); + + // Send finality proof import request to BlockImporter + importer_sender.send(BlockImportMsg::ImportFinalityProof( + who.clone(), + Default::default(), + 1, + vec![42], + )).unwrap(); + + // Wait until this request is redirected to the BlockImportWorker + assert_eq!(worker_receiver.recv(), Ok(BlockImportWorkerMsg::ImportFinalityProof( + who.clone(), + Default::default(), + 1, + vec![42], + ))); + + // Send ack of proof import from BlockImportWorker to BlockImporter + result_sender.send(BlockImportWorkerMsg::ImportedFinalityProof( + who.clone(), + Default::default(), + 1, + true, + )).unwrap(); + + // Wait for finality proof import result + assert_eq!(link_port.recv(), Ok(LinkMsg::FinalityProofImported)); + + // Drop the importer sender first, ensuring graceful shutdown. + drop(importer_sender); + } } From e108f63a13911b73c481e29320658bafeb5c9855 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 18 Apr 2019 17:33:19 +0300 Subject: [PATCH 39/42] allow import of finality proofs for non-requested blocks --- core/consensus/common/src/block_import.rs | 4 +- core/consensus/common/src/import_queue.rs | 40 +++--- core/finality-grandpa/src/light_import.rs | 8 +- core/network/src/extra_requests.rs | 146 ++++++++++++++++------ core/network/src/protocol.rs | 7 +- core/network/src/service.rs | 15 ++- core/network/src/sync.rs | 16 ++- core/network/src/test/mod.rs | 9 +- 8 files changed, 177 insertions(+), 68 deletions(-) diff --git a/core/consensus/common/src/block_import.rs b/core/consensus/common/src/block_import.rs index 3f01979f05f20..1a6a8d1f5078d 100644 --- a/core/consensus/common/src/block_import.rs +++ b/core/consensus/common/src/block_import.rs @@ -215,14 +215,14 @@ pub trait FinalityProofImport { /// Called by the import queue when it is started. fn on_start(&self, _link: &crate::import_queue::Link) { } - /// Import a Block justification and finalize the given block. + /// Import a Block justification and finalize the given block. Returns finalized block or error. fn import_finality_proof( &self, hash: B::Hash, number: NumberFor, finality_proof: Vec, verifier: &Verifier, - ) -> Result<(), Self::Error>; + ) -> Result<(B::Hash, NumberFor), Self::Error>; } /// Finality proof request builder. diff --git a/core/consensus/common/src/import_queue.rs b/core/consensus/common/src/import_queue.rs index cbfe14fe67f62..a190c23f7858c 100644 --- a/core/consensus/common/src/import_queue.rs +++ b/core/consensus/common/src/import_queue.rs @@ -249,7 +249,7 @@ pub enum BlockImportWorkerMsg { )>, ), ImportFinalityProof(Origin, B::Hash, NumberFor, Vec), - ImportedFinalityProof(Origin, B::Hash, NumberFor, bool), + ImportedFinalityProof(Origin, (B::Hash, NumberFor), Result<(B::Hash, NumberFor), ()>), #[cfg(any(test, feature = "test-helpers"))] Synchronize, } @@ -369,8 +369,8 @@ impl BlockImporter { let results = match msg { BlockImportWorkerMsg::ImportedBlocks(results) => (results), - BlockImportWorkerMsg::ImportedFinalityProof(who, hash, number, success) => { - link.finality_proof_imported(who, &hash, number, success); + BlockImportWorkerMsg::ImportedFinalityProof(who, request_block, finalization_result) => { + link.finality_proof_imported(who, request_block, finalization_result); return true; }, #[cfg(any(test, feature = "test-helpers"))] @@ -514,7 +514,7 @@ impl> BlockImportWorker { let _ = worker.result_sender.send(BlockImportWorkerMsg::Synchronize); }, BlockImportWorkerMsg::ImportedBlocks(_) - | BlockImportWorkerMsg::ImportedFinalityProof(_, _, _, _) + | BlockImportWorkerMsg::ImportedFinalityProof(_, _, _) => unreachable!("Import Worker does not receive the Imported* messages; qed"), } } @@ -571,7 +571,7 @@ impl> BlockImportWorker { } fn import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor, finality_proof: Vec) { - let success = self.finality_proof_import.as_ref().map(|finality_proof_import| { + let result = self.finality_proof_import.as_ref().map(|finality_proof_import| { finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier) .map_err(|e| { debug!( @@ -581,13 +581,12 @@ impl> BlockImportWorker { number, who, ); - e - }).is_ok() - }).unwrap_or(false); + }) + }).unwrap_or(Err(())); let _ = self .result_sender - .send(BlockImportWorkerMsg::ImportedFinalityProof(who, hash, number, success)); + .send(BlockImportWorkerMsg::ImportedFinalityProof(who, (hash, number), result)); trace!(target: "sync", "Imported finality proof for {}/{}", number, hash); } @@ -607,7 +606,16 @@ pub trait Link: Send { /// Request a justification for the given block. fn request_justification(&self, _hash: &B::Hash, _number: NumberFor) {} /// Finality proof import result. - fn finality_proof_imported(&self, _who: Origin, _hash: &B::Hash, _number: NumberFor, _success: bool) {} + /// + /// Even though we have asked for finality proof of block A, provider could return proof of + /// some earlier block B, if the proof for A was too large. The sync module should continue + /// asking for proof of A in this case. + fn finality_proof_imported( + &self, + _who: Origin, + _request_block: (B::Hash, NumberFor), + _finalization_result: Result<(B::Hash, NumberFor), ()>, + ) {} /// Request a finality proof for the given block. fn request_finality_proof(&self, _hash: &B::Hash, _number: NumberFor) {} /// Remember finality proof request builder on start. @@ -749,7 +757,12 @@ mod tests { fn block_imported(&self, _hash: &Hash, _number: NumberFor) { let _ = self.sender.send(LinkMsg::BlockImported); } - fn finality_proof_imported(&self, _: Origin, _: &Hash, _: NumberFor, _: bool) { + fn finality_proof_imported( + &self, + _: Origin, + _: (Hash, NumberFor), + _: Result<(Hash, NumberFor), ()>, + ) { let _ = self.sender.send(LinkMsg::FinalityProofImported); } fn useless_peer(&self, _: Origin, _: &str) { @@ -886,9 +899,8 @@ mod tests { // Send ack of proof import from BlockImportWorker to BlockImporter result_sender.send(BlockImportWorkerMsg::ImportedFinalityProof( who.clone(), - Default::default(), - 1, - true, + (Default::default(), 0), + Ok((Default::default(), 0)), )).unwrap(); // Wait for finality proof import result diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index cfe3d8c64e5d6..65d7563c2dbc7 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -164,7 +164,7 @@ impl, RA> FinalityProofImport number: NumberFor, finality_proof: Vec, verifier: &Verifier, - ) -> Result<(), Self::Error> { + ) -> Result<(Block::Hash, NumberFor), Self::Error> { do_import_finality_proof::<_, _, _, _, GrandpaJustification>( &*self.client, &*self.authority_set_provider, @@ -281,7 +281,7 @@ fn do_import_finality_proof, RA, J>( _number: NumberFor, finality_proof: Vec, verifier: &Verifier, -) -> Result<(), ConsensusError> +) -> Result<(Block::Hash, NumberFor), ConsensusError> where B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, @@ -333,7 +333,7 @@ fn do_import_finality_proof, RA, J>( finality_effects.new_authorities, ); - Ok(()) + Ok((finalized_block_hash, finalized_block_number)) } /// Try to import justification. @@ -585,7 +585,7 @@ pub mod tests { number: NumberFor, finality_proof: Vec, verifier: &Verifier, - ) -> Result<(), Self::Error> { + ) -> Result<(Block::Hash, NumberFor), Self::Error> { self.0.import_finality_proof(hash, number, finality_proof, verifier) } } diff --git a/core/network/src/extra_requests.rs b/core/network/src/extra_requests.rs index c353428593178..634dd5283031a 100644 --- a/core/network/src/extra_requests.rs +++ b/core/network/src/extra_requests.rs @@ -16,7 +16,7 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::time::{Duration, Instant}; -use log::{trace, warn, debug}; +use log::{trace, warn}; use client::error::Error as ClientError; use consensus::import_queue::{ImportQueue, SharedFinalityProofRequestBuilder}; use fork_tree::ForkTree; @@ -266,39 +266,13 @@ impl> ExtraRequests { /// Process the import result of an extra. /// Queues a retry in case the import failed. - pub(crate) fn on_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - let request = (hash, number); - - if !self.importing_requests.remove(&request) { - debug!(target: "sync", "Got {} import result for unknown {} {:?} {:?} request.", - self.essence.type_name(), - self.essence.type_name(), - request.0, - request.1, - ); - - return; - } - - if success { - if self.tree.finalize_root(&request.0).is_none() { - warn!(target: "sync", "Imported {} for {:?} {:?} which isn't a root in the tree: {:?}", - self.essence.type_name(), - request.0, - request.1, - self.tree.roots().collect::>(), - ); - return; - }; - - self.previous_requests.clear(); - self.peer_requests.clear(); - self.pending_requests = - self.tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect(); - - return; - } - self.pending_requests.push_front(request); + /// Returns true if import has been queued. + pub(crate) fn on_import_result( + &mut self, + request: (B::Hash, NumberFor), + finalization_result: Result<(B::Hash, NumberFor), ()>, + ) -> bool { + self.try_finalize_root(request, finalization_result, true) } /// Processes the response for the request previously sent to the given @@ -338,9 +312,12 @@ impl> ExtraRequests { ) -> Result<(), fork_tree::Error> where F: Fn(&B::Hash, &B::Hash) -> Result { - if self.importing_requests.contains(&(*best_finalized_hash, best_finalized_number)) { - // we imported this extra data ourselves, so we should get back a response - // from the import queue through `on_import_result` + let is_scheduled_root = self.try_finalize_root( + (*best_finalized_hash, best_finalized_number), + Ok((*best_finalized_hash, best_finalized_number)), + false, + ); + if is_scheduled_root { return Ok(()); } @@ -364,6 +341,46 @@ impl> ExtraRequests { self.peer_requests.clear(); self.previous_requests.clear(); } + + /// Try to finalize pending root. + /// Returns true if import of this request has been scheduled. + fn try_finalize_root( + &mut self, + request: (B::Hash, NumberFor), + finalization_result: Result<(B::Hash, NumberFor), ()>, + reschedule_on_failure: bool, + ) -> bool { + if !self.importing_requests.remove(&request) { + return false; + } + + let (finalized_hash, finalized_number) = match finalization_result { + Ok((finalized_hash, finalized_number)) => (finalized_hash, finalized_number), + Err(_) => { + if reschedule_on_failure { + self.pending_requests.push_front(request); + } + return true; + }, + }; + + if self.tree.finalize_root(&finalized_hash).is_none() { + warn!(target: "sync", "Imported {} for {:?} {:?} which isn't a root in the tree: {:?}", + self.essence.type_name(), + finalized_hash, + finalized_number, + self.tree.roots().collect::>(), + ); + return true; + }; + + self.previous_requests.clear(); + self.peer_requests.clear(); + self.pending_requests = + self.tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect(); + + true + } } pub(crate) struct JustificationsRequestsEssence; @@ -421,4 +438,57 @@ impl ExtraRequestsEssence for FinalityProofRequestsEssence { fn peer_downloading_state(&self, block: B::Hash) -> PeerSyncState { PeerSyncState::DownloadingFinalityProof(block) } -} \ No newline at end of file +} + +#[cfg(test)] +mod tests { + use client::error::Error as ClientError; + use test_client::runtime::{Block, Hash}; + use super::ExtraRequestsAggregator; + + #[test] + fn request_is_rescheduled_when_earlier_block_is_finalized() { + let _ = ::env_logger::try_init(); + + let mut extra_requests = ExtraRequestsAggregator::::new(); + + let hash4 = [4; 32].into(); + let hash5 = [5; 32].into(); + let hash6 = [6; 32].into(); + let hash7 = [7; 32].into(); + + fn is_descendent_of(base: &Hash, target: &Hash) -> Result { + Ok(target[0] >= base[0]) + } + + // make #4 last finalized block + extra_requests.finality_proofs().tree.import(hash4, 4, (), &is_descendent_of).unwrap(); + extra_requests.finality_proofs().tree.finalize_root(&hash4); + + // schedule request for #6 + extra_requests.finality_proofs().queue_request((hash6, 6), is_descendent_of); + + // receive finality proof for #5 + extra_requests.finality_proofs().importing_requests.insert((hash6, 6)); + extra_requests.finality_proofs().on_block_finalized(&hash5, 5, is_descendent_of).unwrap(); + extra_requests.finality_proofs().on_import_result((hash6, 6), Ok((hash5, 5))); + + // ensure that request for #6 is still pending + assert_eq!( + extra_requests.finality_proofs().pending_requests.iter().collect::>(), + vec![&(hash6, 6)], + ); + + // receive finality proof for #7 + extra_requests.finality_proofs().importing_requests.insert((hash6, 6)); + extra_requests.finality_proofs().on_block_finalized(&hash6, 6, is_descendent_of).unwrap(); + extra_requests.finality_proofs().on_block_finalized(&hash7, 7, is_descendent_of).unwrap(); + extra_requests.finality_proofs().on_import_result((hash6, 6), Ok((hash7, 7))); + + // ensure that there's no request for #6 + assert_eq!( + extra_requests.finality_proofs().pending_requests.iter().collect::>(), + Vec::<&(Hash, u64)>::new(), + ); + } +} diff --git a/core/network/src/protocol.rs b/core/network/src/protocol.rs index bff021f5e2061..dae49d457ac27 100644 --- a/core/network/src/protocol.rs +++ b/core/network/src/protocol.rs @@ -232,7 +232,7 @@ pub enum ProtocolMsg> { /// Tell protocol to request finality proof for a block. RequestFinalityProof(B::Hash, NumberFor), /// Inform protocol whether a finality proof was successfully imported. - FinalityProofImportResult(B::Hash, NumberFor, bool), + FinalityProofImportResult((B::Hash, NumberFor), Result<(B::Hash, NumberFor), ()>), /// Propagate a block to peers. AnnounceBlock(B::Hash), /// A block has been imported (sent by the client). @@ -421,7 +421,10 @@ impl, H: ExHashT> Protocol { ProtocolContext::new(&mut self.context_data, &self.network_chan); self.sync.request_finality_proof(&hash, number, &mut context); }, - ProtocolMsg::FinalityProofImportResult(hash, number, success) => self.sync.finality_proof_import_result(hash, number, success), + ProtocolMsg::FinalityProofImportResult( + requested_block, + finalziation_result, + ) => self.sync.finality_proof_import_result(requested_block, finalziation_result), ProtocolMsg::PropagateExtrinsics => self.propagate_extrinsics(), ProtocolMsg::Tick => self.tick(), #[cfg(any(test, feature = "test-helpers"))] diff --git a/core/network/src/service.rs b/core/network/src/service.rs index 1de0f7395edcf..18a097486bcf1 100644 --- a/core/network/src/service.rs +++ b/core/network/src/service.rs @@ -118,10 +118,19 @@ impl> Link for NetworkLink { )); } - fn finality_proof_imported(&self, who: PeerId, hash: &B::Hash, number: NumberFor, success: bool) { - let _ = self.protocol_sender.send(ProtocolMsg::FinalityProofImportResult(hash.clone(), number, success)); + fn finality_proof_imported( + &self, + who: PeerId, + request_block: (B::Hash, NumberFor), + finalization_result: Result<(B::Hash, NumberFor), ()>, + ) { + let success = finalization_result.is_ok(); + let _ = self.protocol_sender.send(ProtocolMsg::FinalityProofImportResult( + request_block, + finalization_result, + )); if !success { - let reason = Severity::Bad(format!("Invalid finality proof provided for #{}", hash).to_string()); + let reason = Severity::Bad(format!("Invalid finality proof provided for #{}", request_block.0)); let _ = self.network_sender.send(NetworkMsg::ReportPeer(who, reason)); } } diff --git a/core/network/src/sync.rs b/core/network/src/sync.rs index b051064bf6ae4..725b909185c70 100644 --- a/core/network/src/sync.rs +++ b/core/network/src/sync.rs @@ -563,7 +563,13 @@ impl ChainSync { } pub fn justification_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - self.extra_requests.justifications().on_import_result(hash, number, success); + let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; + if !self.extra_requests.justifications().on_import_result((hash, number), finalization_result) { + debug!(target: "sync", "Got justification import result for unknown justification {:?} {:?} request.", + hash, + number, + ); + } } /// Request a finality proof for the given block. @@ -578,8 +584,12 @@ impl ChainSync { self.extra_requests.finality_proofs().dispatch(&mut self.peers, protocol); } - pub fn finality_proof_import_result(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - self.extra_requests.finality_proofs().on_import_result(hash, number, success); + pub fn finality_proof_import_result( + &mut self, + request_block: (B::Hash, NumberFor), + finalization_result: Result<(B::Hash, NumberFor), ()>, + ) { + self.extra_requests.finality_proofs().on_import_result(request_block, finalization_result); } pub fn set_finality_proof_request_builder(&mut self, request_builder: SharedFinalityProofRequestBuilder) { diff --git a/core/network/src/test/mod.rs b/core/network/src/test/mod.rs index aca08e7b540d5..5b6433e79f3b6 100644 --- a/core/network/src/test/mod.rs +++ b/core/network/src/test/mod.rs @@ -231,8 +231,13 @@ impl> Link for TestLink { self.link.request_justification(hash, number); } - fn finality_proof_imported(&self, who: PeerId, hash: &Hash, number:NumberFor, success: bool) { - self.link.finality_proof_imported(who, hash, number, success); + fn finality_proof_imported( + &self, + who: PeerId, + request_block: (Hash, NumberFor), + finalization_result: Result<(Hash, NumberFor), ()>, + ) { + self.link.finality_proof_imported(who, request_block, finalization_result); } fn request_finality_proof(&self, hash: &Hash, number: NumberFor) { From e2b5458efee98a19d536b18543a13a429406dc9e Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 18 Apr 2019 17:47:00 +0300 Subject: [PATCH 40/42] limit number of fragments in finality proof --- core/finality-grandpa/src/finality_proof.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/finality-grandpa/src/finality_proof.rs b/core/finality-grandpa/src/finality_proof.rs index b52dfcfabb02d..4cffe0dd981c1 100644 --- a/core/finality-grandpa/src/finality_proof.rs +++ b/core/finality-grandpa/src/finality_proof.rs @@ -29,6 +29,10 @@ //! Let U be the last finalized block known to caller. If authorities set has changed several //! times in the (U; F] interval, multiple finality proof fragments are returned (one for each //! authority set change) and they must be verified in-order. +//! +//! Finality proof provider can choose how to provide finality proof on its own. The incomplete +//! finality proof (that finalizes some block C that is ancestor of the B and descendant +//! of the U) could be returned. use std::sync::Arc; use log::{trace, warn}; @@ -51,6 +55,9 @@ use substrate_telemetry::{telemetry, CONSENSUS_INFO}; use crate::justification::GrandpaJustification; +/// Maximum number of fragments that we want to return in a single prove_finality call. +const MAX_FRAGMENTS_IN_PROOF: usize = 8; + /// GRANDPA authority set related methods for the finality proof provider. pub trait AuthoritySetForFinalityProver: Send + Sync { /// Call GrandpaApi::grandpa_authorities at given block. @@ -290,6 +297,7 @@ pub(crate) fn prove_finality, B: BlockchainBackend, B: BlockchainBackend Date: Fri, 3 May 2019 12:11:37 +0300 Subject: [PATCH 41/42] GRANDPA post-merge fix --- core/finality-grandpa/src/light_import.rs | 9 +-------- core/finality-grandpa/src/tests.rs | 6 ++++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/core/finality-grandpa/src/light_import.rs b/core/finality-grandpa/src/light_import.rs index 65d7563c2dbc7..f73b41f0ae02e 100644 --- a/core/finality-grandpa/src/light_import.rs +++ b/core/finality-grandpa/src/light_import.rs @@ -33,8 +33,7 @@ use consensus_common::{ }; use runtime_primitives::Justification; use runtime_primitives::traits::{ - NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, - DigestItem, DigestFor, DigestItemFor, + NumberFor, Block as BlockT, Header as HeaderT, ProvideRuntimeApi, DigestFor, }; use fg_primitives::GrandpaApi; use runtime_primitives::generic::BlockId; @@ -111,7 +110,6 @@ impl, RA> BlockImport B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, DigestFor: Encode, - DigestItemFor: DigestItem, RA: Send + Sync, { type Error = ConsensusError; @@ -139,7 +137,6 @@ impl, RA> FinalityProofImport B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, DigestFor: Encode, - DigestItemFor: DigestItem, RA: Send + Sync, { type Error = ConsensusError; @@ -228,7 +225,6 @@ fn do_import_block, RA, J>( RA: Send + Sync, NumberFor: grandpa::BlockNumberOps, DigestFor: Encode, - DigestItemFor: DigestItem, J: ProvableJustification, { let hash = block.post_header().hash(); @@ -287,7 +283,6 @@ fn do_import_finality_proof, RA, J>( E: CallExecutor + 'static + Clone + Send + Sync, RA: Send + Sync, DigestFor: Encode, - DigestItemFor: DigestItem, NumberFor: grandpa::BlockNumberOps, J: ProvableJustification, { @@ -541,7 +536,6 @@ pub mod tests { B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, DigestFor: Encode, - DigestItemFor: DigestItem, RA: Send + Sync, { type Error = ConsensusError; @@ -570,7 +564,6 @@ pub mod tests { B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, DigestFor: Encode, - DigestItemFor: DigestItem, RA: Send + Sync, { type Error = ConsensusError; diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index edc62c42975b4..df3c117e0b740 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -1379,7 +1379,7 @@ fn finality_proof_is_fetched_by_light_client_when_consensus_data_changes() { // import block#1 WITH consensus data change. Light client ignores justification // && instead fetches finality proof for block #1 - net.peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); + net.peer(0).push_authorities_change_block(vec![substrate_primitives::sr25519::Public::from_raw([42; 32])]); let net = Arc::new(Mutex::new(net)); run_to_completion(1, net.clone(), peers); net.lock().sync_without_disconnects(); @@ -1438,7 +1438,9 @@ fn empty_finality_proof_is_returned_to_light_client_when_authority_set_is_differ // ensure block#10 enacts authorities set change => justification is generated // normally it will reach light client, but because of the forced change, it will not net.lock().peer(0).push_blocks(8, false); // best is #9 - net.lock().peer(0).push_authorities_change_block(vec![AuthorityId::from_raw([42; 32])]); // #10 + net.lock().peer(0).push_authorities_change_block( + vec![substrate_primitives::sr25519::Public::from_raw([42; 32])] + ); // #10 net.lock().peer(0).push_blocks(1, false); // best is #11 net.lock().sync_without_disconnects(); From d6cbbf3143f4ab61f04cc47b3ee77b6679d73a79 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 3 May 2019 12:18:06 +0300 Subject: [PATCH 42/42] BABE: pos-merge fix --- core/consensus/babe/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/consensus/babe/src/lib.rs b/core/consensus/babe/src/lib.rs index c86e14de31fe6..b26be4898d538 100644 --- a/core/consensus/babe/src/lib.rs +++ b/core/consensus/babe/src/lib.rs @@ -907,7 +907,7 @@ mod tests { impl TestNetFactory for BabeTestNet { type Specialization = DummySpecialization; - type Verifier = BabeVerifier; + type Verifier = BabeVerifier; type PeerData = (); /// Create new test network with peers and given config. @@ -919,9 +919,10 @@ mod tests { } } - fn make_verifier(&self, client: Arc, _cfg: &ProtocolConfig) + fn make_verifier(&self, client: PeersClient, _cfg: &ProtocolConfig) -> Arc { + let client = client.as_full().expect("only full clients are used in test"); trace!(target: "babe", "Creating a verifier"); let config = Config::get_or_compute(&*client) .expect("slot duration available"); @@ -994,7 +995,7 @@ mod tests { debug!(target: "babe", "checkpoint 4"); let mut runtime = current_thread::Runtime::new().unwrap(); for (peer_id, key) in peers { - let client = net.lock().peer(*peer_id).client().clone(); + let client = net.lock().peer(*peer_id).client().as_full().unwrap(); let environ = Arc::new(DummyFactory(client.clone())); import_notifications.push( client.import_notification_stream()