diff --git a/core/client/src/client.rs b/core/client/src/client.rs index d028037b67ca7..640484f9d5422 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -32,6 +32,7 @@ use consensus::{ Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult, BlockOrigin, ForkChoiceStrategy, well_known_cache_keys::Id as CacheKeyId, + SelectChain, self, }; use runtime_primitives::traits::{ Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, @@ -71,7 +72,6 @@ use crate::error; use crate::in_mem; use crate::block_builder::{self, api::BlockBuilder as BlockBuilderAPI}; use crate::genesis; -use consensus; use substrate_telemetry::{telemetry, SUBSTRATE_INFO}; use log::{info, trace, warn}; @@ -119,7 +119,7 @@ pub struct Client where Block: BlockT { storage_notifications: Mutex>, import_notification_sinks: Mutex>>>, finality_notification_sinks: Mutex>>>, - import_lock: Mutex<()>, + import_lock: Arc>, // holds the block hash currently being imported. TODO: replace this with block queue importing_block: RwLock>, execution_strategies: ExecutionStrategies, @@ -149,15 +149,6 @@ pub trait BlockchainEvents { fn storage_changes_notification_stream(&self, filter_keys: Option<&[StorageKey]>) -> error::Result>; } -/// Chain head information. -pub trait ChainHead { - /// Get best block header. - fn best_block_header(&self) -> Result<::Header, error::Error>; - /// Get all leaves of the chain: block hashes that have no children currently. - /// Leaves that can never be finalized will not be returned. - fn leaves(&self) -> Result::Hash>, error::Error>; -} - /// Fetch block body by ID. pub trait BlockBody { /// Get block body by ID. Returns `None` if the body is not stored. @@ -260,7 +251,7 @@ pub fn new_in_mem( new_with_backend(Arc::new(in_mem::Backend::new()), executor, genesis_storage) } -/// Create a client with the explicitely provided backend. +/// Create a client with the explicitly provided backend. /// This is useful for testing backend implementations. pub fn new_with_backend( backend: Arc, @@ -329,10 +320,23 @@ impl Client where } /// Expose backend reference. To be used in tests only + #[doc(hidden)] + #[deprecated(note="Rather than relying on `client` to provide this, access \ + to the backend should be handled at setup only - see #1134. This function \ + will be removed once that is in place.")] pub fn backend(&self) -> &Arc { &self.backend } + /// Expose reference to import lock + #[doc(hidden)] + #[deprecated(note="Rather than relying on `client` to provide this, access \ + to the backend should be handled at setup only - see #1134. This function \ + will be removed once that is in place.")] + pub fn import_lock(&self) -> Arc> { + self.import_lock.clone() + } + /// Return storage entry keys in state in a block of given hash with given prefix. pub fn storage_keys(&self, id: &BlockId, key_prefix: &StorageKey) -> error::Result> { let keys = self.state_at(id)?.keys(&key_prefix.0).into_iter().map(StorageKey).collect(); @@ -1102,7 +1106,7 @@ impl Client where }; match hash_and_number { Some((hash, number)) => { - if self.backend().have_state_at(&hash, number) { + if self.backend.have_state_at(&hash, number) { Ok(BlockStatus::InChainWithState) } else { Ok(BlockStatus::InChainPruned) @@ -1138,132 +1142,6 @@ impl Client where }) } - /// Get best block header. - pub fn best_block_header(&self) -> error::Result<::Header> { - let info = self.backend.blockchain().info().map_err(|e| error::Error::from_blockchain(Box::new(e)))?; - Ok(self.header(&BlockId::Hash(info.best_hash))?.expect("Best block header must always exist")) - } - - /// Get the most recent block hash of the best (longest) chains - /// that contain block with the given `target_hash`. - /// - /// The search space is always limited to blocks which are in the finalized - /// chain or descendents of it. - /// - /// If `maybe_max_block_number` is `Some(max_block_number)` - /// the search is limited to block `numbers <= max_block_number`. - /// in other words as if there were no blocks greater `max_block_number`. - /// TODO : we want to move this implement to `blockchain::Backend`, see [#1443](https://github.com/paritytech/substrate/issues/1443) - /// Returns `Ok(None)` if `target_hash` is not found in search space. - /// TODO: document time complexity of this, see [#1444](https://github.com/paritytech/substrate/issues/1444) - pub fn best_containing(&self, target_hash: Block::Hash, maybe_max_number: Option>) - -> error::Result> - { - let target_header = { - match self.backend.blockchain().header(BlockId::Hash(target_hash))? { - Some(x) => x, - // target not in blockchain - None => { return Ok(None); }, - } - }; - - if let Some(max_number) = maybe_max_number { - // target outside search range - if target_header.number() > &max_number { - return Ok(None); - } - } - - let (leaves, best_already_checked) = { - // ensure no blocks are imported during this code block. - // an import could trigger a reorg which could change the canonical chain. - // we depend on the canonical chain staying the same during this code block. - let _import_lock = self.import_lock.lock(); - - let info = self.backend.blockchain().info()?; - - let canon_hash = self.backend.blockchain().hash(*target_header.number())? - .ok_or_else(|| error::Error::from(format!("failed to get hash for block number {}", target_header.number())))?; - - if canon_hash == target_hash { - // if no block at the given max depth exists fallback to the best block - if let Some(max_number) = maybe_max_number { - if let Some(header) = self.backend.blockchain().hash(max_number)? { - return Ok(Some(header)); - } - } - - return Ok(Some(info.best_hash)); - } else if info.finalized_number >= *target_header.number() { - // header is on a dead fork. - return Ok(None); - } - - (self.backend.blockchain().leaves()?, info.best_hash) - }; - - // for each chain. longest chain first. shortest last - for leaf_hash in leaves { - // ignore canonical chain which we already checked above - if leaf_hash == best_already_checked { - continue; - } - - // start at the leaf - let mut current_hash = leaf_hash; - - // if search is not restricted then the leaf is the best - let mut best_hash = leaf_hash; - - // go backwards entering the search space - // waiting until we are <= max_number - if let Some(max_number) = maybe_max_number { - loop { - let current_header = self.backend.blockchain().header(BlockId::Hash(current_hash.clone()))? - .ok_or_else(|| error::Error::from(format!("failed to get header for hash {}", current_hash)))?; - - if current_header.number() <= &max_number { - best_hash = current_header.hash(); - break; - } - - current_hash = *current_header.parent_hash(); - } - } - - // go backwards through the chain (via parent links) - loop { - // until we find target - if current_hash == target_hash { - return Ok(Some(best_hash)); - } - - let current_header = self.backend.blockchain().header(BlockId::Hash(current_hash.clone()))? - .ok_or_else(|| error::Error::from(format!("failed to get header for hash {}", current_hash)))?; - - // stop search in this chain once we go below the target's block number - if current_header.number() < target_header.number() { - break; - } - - current_hash = *current_header.parent_hash(); - } - } - - // header may be on a dead fork -- the only leaves that are considered are - // those which can still be finalized. - // - // FIXME #1558 only issue this warning when not on a dead fork - warn!( - "Block {:?} exists in chain but not found when following all \ - leaves backwards. Number limit = {:?}", - target_hash, - maybe_max_number, - ); - - Ok(None) - } - /// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors. pub fn uncles(&self, target_hash: Block::Hash, max_generation: NumberFor) -> error::Result> { let load_header = |id: Block::Hash| -> error::Result { @@ -1515,14 +1393,168 @@ where } } -impl ChainHead for Client +/// Implement Longest Chain Select implementation +/// where 'longest' is defined as the highest number of blocks +pub struct LongestChain { + backend: Arc, + import_lock: Arc>, + _phantom: PhantomData +} + +impl Clone for LongestChain { + fn clone(&self) -> Self { + let backend = self.backend.clone(); + let import_lock = self.import_lock.clone(); + LongestChain { + backend, + import_lock, + _phantom: Default::default() + } + } +} + +impl LongestChain where B: backend::Backend, - E: CallExecutor, Block: BlockT, { + /// Instantiate a new LongestChain for Backend B + pub fn new(backend: Arc, import_lock: Arc>) -> Self { + LongestChain { + backend, + import_lock, + _phantom: Default::default() + } + } + fn best_block_header(&self) -> error::Result<::Header> { - Client::best_block_header(self) + let info : ChainInfo = match self.backend.blockchain().info() { + Ok(i) => i, + Err(e) => return Err(error::Error::from_blockchain(Box::new(e))) + }; + Ok(self.backend.blockchain().header(BlockId::Hash(info.best_hash))? + .expect("Best block header must always exist")) + } + + /// Get the most recent block hash of the best (longest) chains + /// that contain block with the given `target_hash`. + /// + /// The search space is always limited to blocks which are in the finalized + /// chain or descendents of it. + /// + /// If `maybe_max_block_number` is `Some(max_block_number)` + /// the search is limited to block `numbers <= max_block_number`. + /// in other words as if there were no blocks greater `max_block_number`. + /// Returns `Ok(None)` if `target_hash` is not found in search space. + /// TODO: document time complexity of this, see [#1444](https://github.com/paritytech/substrate/issues/1444) + fn best_containing( + &self, + target_hash: Block::Hash, + maybe_max_number: Option> + ) -> error::Result> { + let target_header = { + match self.backend.blockchain().header(BlockId::Hash(target_hash))? { + Some(x) => x, + // target not in blockchain + None => { return Ok(None); }, + } + }; + + if let Some(max_number) = maybe_max_number { + // target outside search range + if target_header.number() > &max_number { + return Ok(None); + } + } + + let (leaves, best_already_checked) = { + // ensure no blocks are imported during this code block. + // an import could trigger a reorg which could change the canonical chain. + // we depend on the canonical chain staying the same during this code block. + let _import_lock = self.import_lock.lock(); + + let info = self.backend.blockchain().info()?; + + let canon_hash = self.backend.blockchain().hash(*target_header.number())? + .ok_or_else(|| error::Error::from(format!("failed to get hash for block number {}", target_header.number())))?; + + if canon_hash == target_hash { + // if no block at the given max depth exists fallback to the best block + if let Some(max_number) = maybe_max_number { + if let Some(header) = self.backend.blockchain().hash(max_number)? { + return Ok(Some(header)); + } + } + + return Ok(Some(info.best_hash)); + } else if info.finalized_number >= *target_header.number() { + // header is on a dead fork. + return Ok(None); + } + + (self.backend.blockchain().leaves()?, info.best_hash) + }; + + // for each chain. longest chain first. shortest last + for leaf_hash in leaves { + // ignore canonical chain which we already checked above + if leaf_hash == best_already_checked { + continue; + } + + // start at the leaf + let mut current_hash = leaf_hash; + + // if search is not restricted then the leaf is the best + let mut best_hash = leaf_hash; + + // go backwards entering the search space + // waiting until we are <= max_number + if let Some(max_number) = maybe_max_number { + loop { + let current_header = self.backend.blockchain().header(BlockId::Hash(current_hash.clone()))? + .ok_or_else(|| error::Error::from(format!("failed to get header for hash {}", current_hash)))?; + + if current_header.number() <= &max_number { + best_hash = current_header.hash(); + break; + } + + current_hash = *current_header.parent_hash(); + } + } + + // go backwards through the chain (via parent links) + loop { + // until we find target + if current_hash == target_hash { + return Ok(Some(best_hash)); + } + + let current_header = self.backend.blockchain().header(BlockId::Hash(current_hash.clone()))? + .ok_or_else(|| error::Error::from(format!("failed to get header for hash {}", current_hash)))?; + + // stop search in this chain once we go below the target's block number + if current_header.number() < target_header.number() { + break; + } + + current_hash = *current_header.parent_hash(); + } + } + + // header may be on a dead fork -- the only leaves that are considered are + // those which can still be finalized. + // + // FIXME #1558 only issue this warning when not on a dead fork + warn!( + "Block {:?} exists in chain but not found when following all \ + leaves backwards. Number limit = {:?}", + target_hash, + maybe_max_number, + ); + + Ok(None) } fn leaves(&self) -> Result::Hash>, error::Error> { @@ -1530,6 +1562,34 @@ where } } +impl SelectChain for LongestChain +where + B: backend::Backend, + Block: BlockT, +{ + + fn leaves(&self) -> Result::Hash>, ConsensusError> { + LongestChain::leaves(self) + .map_err(|e| ConsensusErrorKind::ChainLookup(e.to_string()).into()) + } + + fn best_chain(&self) + -> Result<::Header, ConsensusError> + { + LongestChain::best_block_header(&self) + .map_err(|e| ConsensusErrorKind::ChainLookup(e.to_string()).into()) + } + + fn finality_target( + &self, + target_hash: Block::Hash, + maybe_max_number: Option> + ) -> Result, ConsensusError> { + LongestChain::best_containing(self, target_hash, maybe_max_number) + .map_err(|e| ConsensusErrorKind::ChainLookup(e.to_string()).into()) + } +} + impl BlockBody for Client where B: backend::Backend, @@ -1576,7 +1636,7 @@ pub(crate) mod tests { use runtime_primitives::traits::DigestItem as DigestItemT; use runtime_primitives::generic::DigestItem; use test_client::{self, TestClient, AccountKeyring}; - use consensus::BlockOrigin; + use consensus::{BlockOrigin, SelectChain}; use test_client::client::backend::Backend as TestBackend; use test_client::BlockBuilderExt; use test_client::runtime::{self, Block, Transfer, RuntimeApi, TestAPI}; @@ -1753,8 +1813,14 @@ pub(crate) mod tests { let client = test_client::new(); let genesis_hash = client.info().unwrap().chain.genesis_hash; + let longest_chain_select = test_client::client::LongestChain::new( + client.backend().clone(), + client.import_lock() + ); + - assert_eq!(genesis_hash.clone(), client.best_containing(genesis_hash.clone(), None).unwrap().unwrap()); + assert_eq!(genesis_hash.clone(), longest_chain_select.finality_target( + genesis_hash.clone(), None).unwrap().unwrap()); } #[test] @@ -1765,8 +1831,13 @@ pub(crate) mod tests { let client = test_client::new(); let uninserted_block = client.new_block().unwrap().bake().unwrap(); + let backend = client.backend().as_in_memory(); + let longest_chain_select = test_client::client::LongestChain::new( + Arc::new(backend), + client.import_lock()); - assert_eq!(None, client.best_containing(uninserted_block.hash().clone(), None).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + uninserted_block.hash().clone(), None).unwrap()); } #[test] @@ -1881,7 +1952,7 @@ pub(crate) mod tests { } #[test] - fn best_containing_with_single_chain_3_blocks() { + fn best_containing_on_longest_chain_with_single_chain_3_blocks() { // block tree: // G -> A1 -> A2 @@ -1897,18 +1968,20 @@ pub(crate) mod tests { let genesis_hash = client.info().unwrap().chain.genesis_hash; - assert_eq!(a2.hash(), client.best_containing(genesis_hash, None).unwrap().unwrap()); - assert_eq!(a2.hash(), client.best_containing(a1.hash(), None).unwrap().unwrap()); - assert_eq!(a2.hash(), client.best_containing(a2.hash(), None).unwrap().unwrap()); + let longest_chain_select = test_client::client::LongestChain::new( + Arc::new(client.backend().as_in_memory()), + client.import_lock()); + + assert_eq!(a2.hash(), longest_chain_select.finality_target( + genesis_hash, None).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + a1.hash(), None).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + a2.hash(), None).unwrap().unwrap()); } #[test] - fn best_containing_with_multiple_forks() { - // NOTE: we use the version of the trait from `test_client` - // because that is actually different than the version linked to - // in the test facade crate. - use test_client::blockchain::Backend as BlockchainBackendT; - + fn best_containing_on_longest_chain_with_multiple_forks() { // block tree: // G -> A1 -> A2 -> A3 -> A4 -> A5 // A1 -> B2 -> B3 -> B4 @@ -1983,7 +2056,11 @@ pub(crate) mod tests { assert_eq!(client.info().unwrap().chain.best_hash, a5.hash()); let genesis_hash = client.info().unwrap().chain.genesis_hash; - let leaves = BlockchainBackendT::leaves(client.backend().blockchain()).unwrap(); + let longest_chain_select = test_client::client::LongestChain::new( + Arc::new(client.backend().as_in_memory()), + client.import_lock()); + + let leaves = longest_chain_select.leaves().unwrap(); assert!(leaves.contains(&a5.hash())); assert!(leaves.contains(&b4.hash())); @@ -1993,131 +2070,208 @@ pub(crate) mod tests { // search without restriction - assert_eq!(a5.hash(), client.best_containing(genesis_hash, None).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a1.hash(), None).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a2.hash(), None).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a3.hash(), None).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a4.hash(), None).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a5.hash(), None).unwrap().unwrap()); - - assert_eq!(b4.hash(), client.best_containing(b2.hash(), None).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b3.hash(), None).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b4.hash(), None).unwrap().unwrap()); - - assert_eq!(c3.hash(), client.best_containing(c3.hash(), None).unwrap().unwrap()); - - assert_eq!(d2.hash(), client.best_containing(d2.hash(), None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + genesis_hash, None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a1.hash(), None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a2.hash(), None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a3.hash(), None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a4.hash(), None).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a5.hash(), None).unwrap().unwrap()); + + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b2.hash(), None).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b3.hash(), None).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b4.hash(), None).unwrap().unwrap()); + + assert_eq!(c3.hash(), longest_chain_select.finality_target( + c3.hash(), None).unwrap().unwrap()); + + assert_eq!(d2.hash(), longest_chain_select.finality_target( + d2.hash(), None).unwrap().unwrap()); // search only blocks with number <= 5. equivalent to without restriction for this scenario - assert_eq!(a5.hash(), client.best_containing(genesis_hash, Some(5)).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a1.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a2.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a3.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a4.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(a5.hash(), client.best_containing(a5.hash(), Some(5)).unwrap().unwrap()); - - assert_eq!(b4.hash(), client.best_containing(b2.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b3.hash(), Some(5)).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b4.hash(), Some(5)).unwrap().unwrap()); - - assert_eq!(c3.hash(), client.best_containing(c3.hash(), Some(5)).unwrap().unwrap()); - - assert_eq!(d2.hash(), client.best_containing(d2.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + genesis_hash, Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a1.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a2.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a3.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a4.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(a5.hash(), longest_chain_select.finality_target( + a5.hash(), Some(5)).unwrap().unwrap()); + + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b2.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b3.hash(), Some(5)).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b4.hash(), Some(5)).unwrap().unwrap()); + + assert_eq!(c3.hash(), longest_chain_select.finality_target( + c3.hash(), Some(5)).unwrap().unwrap()); + + assert_eq!(d2.hash(), longest_chain_select.finality_target( + d2.hash(), Some(5)).unwrap().unwrap()); // search only blocks with number <= 4 - assert_eq!(a4.hash(), client.best_containing(genesis_hash, Some(4)).unwrap().unwrap()); - assert_eq!(a4.hash(), client.best_containing(a1.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(a4.hash(), client.best_containing(a2.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(a4.hash(), client.best_containing(a3.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(a4.hash(), client.best_containing(a4.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(a5.hash(), Some(4)).unwrap()); - - assert_eq!(b4.hash(), client.best_containing(b2.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b3.hash(), Some(4)).unwrap().unwrap()); - assert_eq!(b4.hash(), client.best_containing(b4.hash(), Some(4)).unwrap().unwrap()); - - assert_eq!(c3.hash(), client.best_containing(c3.hash(), Some(4)).unwrap().unwrap()); - - assert_eq!(d2.hash(), client.best_containing(d2.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(a4.hash(), longest_chain_select.finality_target( + genesis_hash, Some(4)).unwrap().unwrap()); + assert_eq!(a4.hash(), longest_chain_select.finality_target( + a1.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(a4.hash(), longest_chain_select.finality_target( + a2.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(a4.hash(), longest_chain_select.finality_target( + a3.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(a4.hash(), longest_chain_select.finality_target( + a4.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a5.hash(), Some(4)).unwrap()); + + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b2.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b3.hash(), Some(4)).unwrap().unwrap()); + assert_eq!(b4.hash(), longest_chain_select.finality_target( + b4.hash(), Some(4)).unwrap().unwrap()); + + assert_eq!(c3.hash(), longest_chain_select.finality_target( + c3.hash(), Some(4)).unwrap().unwrap()); + + assert_eq!(d2.hash(), longest_chain_select.finality_target( + d2.hash(), Some(4)).unwrap().unwrap()); // search only blocks with number <= 3 - assert_eq!(a3.hash(), client.best_containing(genesis_hash, Some(3)).unwrap().unwrap()); - assert_eq!(a3.hash(), client.best_containing(a1.hash(), Some(3)).unwrap().unwrap()); - assert_eq!(a3.hash(), client.best_containing(a2.hash(), Some(3)).unwrap().unwrap()); - assert_eq!(a3.hash(), client.best_containing(a3.hash(), Some(3)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(a4.hash(), Some(3)).unwrap()); - assert_eq!(None, client.best_containing(a5.hash(), Some(3)).unwrap()); - - assert_eq!(b3.hash(), client.best_containing(b2.hash(), Some(3)).unwrap().unwrap()); - assert_eq!(b3.hash(), client.best_containing(b3.hash(), Some(3)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(b4.hash(), Some(3)).unwrap()); - - assert_eq!(c3.hash(), client.best_containing(c3.hash(), Some(3)).unwrap().unwrap()); - - assert_eq!(d2.hash(), client.best_containing(d2.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(a3.hash(), longest_chain_select.finality_target( + genesis_hash, Some(3)).unwrap().unwrap()); + assert_eq!(a3.hash(), longest_chain_select.finality_target( + a1.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(a3.hash(), longest_chain_select.finality_target( + a2.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(a3.hash(), longest_chain_select.finality_target( + a3.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a4.hash(), Some(3)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a5.hash(), Some(3)).unwrap()); + + assert_eq!(b3.hash(), longest_chain_select.finality_target( + b2.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(b3.hash(), longest_chain_select.finality_target( + b3.hash(), Some(3)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b4.hash(), Some(3)).unwrap()); + + assert_eq!(c3.hash(), longest_chain_select.finality_target( + c3.hash(), Some(3)).unwrap().unwrap()); + + assert_eq!(d2.hash(), longest_chain_select.finality_target( + d2.hash(), Some(3)).unwrap().unwrap()); // search only blocks with number <= 2 - assert_eq!(a2.hash(), client.best_containing(genesis_hash, Some(2)).unwrap().unwrap()); - assert_eq!(a2.hash(), client.best_containing(a1.hash(), Some(2)).unwrap().unwrap()); - assert_eq!(a2.hash(), client.best_containing(a2.hash(), Some(2)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(a3.hash(), Some(2)).unwrap()); - assert_eq!(None, client.best_containing(a4.hash(), Some(2)).unwrap()); - assert_eq!(None, client.best_containing(a5.hash(), Some(2)).unwrap()); - - assert_eq!(b2.hash(), client.best_containing(b2.hash(), Some(2)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(b3.hash(), Some(2)).unwrap()); - assert_eq!(None, client.best_containing(b4.hash(), Some(2)).unwrap()); - - assert_eq!(None, client.best_containing(c3.hash(), Some(2)).unwrap()); - - assert_eq!(d2.hash(), client.best_containing(d2.hash(), Some(2)).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + genesis_hash, Some(2)).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + a1.hash(), Some(2)).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + a2.hash(), Some(2)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a3.hash(), Some(2)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a4.hash(), Some(2)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a5.hash(), Some(2)).unwrap()); + + assert_eq!(b2.hash(), longest_chain_select.finality_target( + b2.hash(), Some(2)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b3.hash(), Some(2)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b4.hash(), Some(2)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + c3.hash(), Some(2)).unwrap()); + + assert_eq!(d2.hash(), longest_chain_select.finality_target( + d2.hash(), Some(2)).unwrap().unwrap()); // search only blocks with number <= 1 - assert_eq!(a1.hash(), client.best_containing(genesis_hash, Some(1)).unwrap().unwrap()); - assert_eq!(a1.hash(), client.best_containing(a1.hash(), Some(1)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(a2.hash(), Some(1)).unwrap()); - assert_eq!(None, client.best_containing(a3.hash(), Some(1)).unwrap()); - assert_eq!(None, client.best_containing(a4.hash(), Some(1)).unwrap()); - assert_eq!(None, client.best_containing(a5.hash(), Some(1)).unwrap()); - - assert_eq!(None, client.best_containing(b2.hash(), Some(1)).unwrap()); - assert_eq!(None, client.best_containing(b3.hash(), Some(1)).unwrap()); - assert_eq!(None, client.best_containing(b4.hash(), Some(1)).unwrap()); - - assert_eq!(None, client.best_containing(c3.hash(), Some(1)).unwrap()); - - assert_eq!(None, client.best_containing(d2.hash(), Some(1)).unwrap()); + assert_eq!(a1.hash(), longest_chain_select.finality_target( + genesis_hash, Some(1)).unwrap().unwrap()); + assert_eq!(a1.hash(), longest_chain_select.finality_target( + a1.hash(), Some(1)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a2.hash(), Some(1)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a3.hash(), Some(1)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a4.hash(), Some(1)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a5.hash(), Some(1)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + b2.hash(), Some(1)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b3.hash(), Some(1)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b4.hash(), Some(1)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + c3.hash(), Some(1)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + d2.hash(), Some(1)).unwrap()); // search only blocks with number <= 0 - assert_eq!(genesis_hash, client.best_containing(genesis_hash, Some(0)).unwrap().unwrap()); - assert_eq!(None, client.best_containing(a1.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(a2.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(a3.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(a4.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(a5.hash(), Some(0)).unwrap()); - - assert_eq!(None, client.best_containing(b2.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(b3.hash(), Some(0)).unwrap()); - assert_eq!(None, client.best_containing(b4.hash(), Some(0)).unwrap()); - - assert_eq!(None, client.best_containing(c3.hash().clone(), Some(0)).unwrap()); - - assert_eq!(None, client.best_containing(d2.hash().clone(), Some(0)).unwrap()); + assert_eq!(genesis_hash, longest_chain_select.finality_target( + genesis_hash, Some(0)).unwrap().unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a1.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a2.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a3.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a4.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + a5.hash(), Some(0)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + b2.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b3.hash(), Some(0)).unwrap()); + assert_eq!(None, longest_chain_select.finality_target( + b4.hash(), Some(0)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + c3.hash().clone(), Some(0)).unwrap()); + + assert_eq!(None, longest_chain_select.finality_target( + d2.hash().clone(), Some(0)).unwrap()); } #[test] - fn best_containing_with_max_depth_higher_than_best() { + fn best_containing_on_longest_chain_with_max_depth_higher_than_best() { // block tree: // G -> A1 -> A2 @@ -2132,8 +2286,13 @@ pub(crate) mod tests { client.import(BlockOrigin::Own, a2.clone()).unwrap(); let genesis_hash = client.info().unwrap().chain.genesis_hash; + let longest_chain_select = test_client::client::LongestChain::new( + Arc::new(client.backend().as_in_memory()), + client.import_lock() + ); - assert_eq!(a2.hash(), client.best_containing(genesis_hash, Some(10)).unwrap().unwrap()); + assert_eq!(a2.hash(), longest_chain_select.finality_target( + genesis_hash, Some(10)).unwrap().unwrap()); } #[test] diff --git a/core/client/src/lib.rs b/core/client/src/lib.rs index d2da243d14a4d..fe33c56262b3c 100644 --- a/core/client/src/lib.rs +++ b/core/client/src/lib.rs @@ -58,7 +58,8 @@ pub use crate::client::{ new_with_backend, new_in_mem, BlockBody, BlockStatus, ImportNotifications, FinalityNotifications, BlockchainEvents, - BlockImportNotification, Client, ClientInfo, ChainHead, ExecutionStrategies, + BlockImportNotification, Client, ClientInfo, ExecutionStrategies, + LongestChain }; #[cfg(feature = "std")] pub use crate::notifications::{StorageEventStream, StorageChangeSet}; diff --git a/core/consensus/aura/src/lib.rs b/core/consensus/aura/src/lib.rs index 6b9e9ad0431c7..d5123feb4f17e 100644 --- a/core/consensus/aura/src/lib.rs +++ b/core/consensus/aura/src/lib.rs @@ -25,18 +25,16 @@ //! //! Blocks from future steps will be either deferred or rejected depending on how //! far in the future they are. -#![deny(warnings)] #![forbid(missing_docs, unsafe_code)] use std::{sync::Arc, time::Duration, thread, marker::PhantomData, hash::Hash, fmt::Debug}; use parity_codec::{Encode, Decode}; use consensus_common::{self, Authorities, BlockImport, Environment, Proposer, ForkChoiceStrategy, ImportBlock, BlockOrigin, Error as ConsensusError, + SelectChain, well_known_cache_keys }; -use consensus_common::well_known_cache_keys; use consensus_common::import_queue::{Verifier, BasicQueue, SharedBlockImport, SharedJustificationImport}; use client::{ - ChainHead, block_builder::api::BlockBuilder as BlockBuilderApi, blockchain::ProvideCache, runtime_api::{ApiExt, Core as CoreApi}, @@ -182,10 +180,11 @@ impl SlotCompatible for AuraSlotCompatible { /// Start the aura worker in a separate thread. #[deprecated(since = "1.1", note = "Please spawn a thread manually")] -pub fn start_aura_thread( +pub fn start_aura_thread( slot_duration: SlotDuration, local_key: Arc

, client: Arc, + select_chain: SC, block_import: Arc, env: Arc, sync_oracle: SO, @@ -194,8 +193,9 @@ pub fn start_aura_thread( force_authoring: bool, ) -> Result<(), consensus_common::Error> where B: Block + 'static, - C: ChainHead + ProvideRuntimeApi + ProvideCache + Send + Sync + 'static, + C: ProvideRuntimeApi + ProvideCache + Send + Sync + 'static, C::Api: AuthoritiesApi, + SC: SelectChain + Clone + 'static, E: Environment + Send + Sync + 'static, E::Proposer: Proposer + Send + 'static, <>::Create as IntoFuture>::Future: Send + 'static, @@ -222,7 +222,7 @@ pub fn start_aura_thread( #[allow(deprecated)] // The function we are in is also deprecated. slots::start_slot_worker_thread::<_, _, _, _, AuraSlotCompatible, u64, _>( slot_duration.0, - client, + select_chain, Arc::new(worker), sync_oracle, on_exit, @@ -231,10 +231,11 @@ pub fn start_aura_thread( } /// Start the aura worker. The returned future should be run in a tokio runtime. -pub fn start_aura( +pub fn start_aura( slot_duration: SlotDuration, local_key: Arc

, client: Arc, + select_chain: SC, block_import: Arc, env: Arc, sync_oracle: SO, @@ -243,8 +244,9 @@ pub fn start_aura( force_authoring: bool, ) -> Result, consensus_common::Error> where B: Block, - C: ChainHead + ProvideRuntimeApi + ProvideCache, + C: ProvideRuntimeApi + ProvideCache, C::Api: AuthoritiesApi, + SC: SelectChain + Clone, E: Environment, E::Proposer: Proposer, <>::Create as IntoFuture>::Future: Send + 'static, @@ -268,7 +270,7 @@ pub fn start_aura( }; slots::start_slot_worker::<_, _, _, _, _, AuraSlotCompatible, _>( slot_duration.0, - client, + select_chain, Arc::new(worker), sync_oracle, on_exit, @@ -804,7 +806,7 @@ mod tests { use tokio::runtime::current_thread; use keyring::sr25519::Keyring; use primitives::sr25519; - use client::BlockchainEvents; + use client::{LongestChain, BlockchainEvents}; use test_client; type Error = client::error::Error; @@ -916,6 +918,10 @@ 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 select_chain = LongestChain::new( + client.backend().clone(), + client.import_lock().clone(), + ); let environ = Arc::new(DummyFactory(client.clone())); import_notifications.push( client.import_notification_stream() @@ -931,10 +937,11 @@ mod tests { &inherent_data_providers, slot_duration.get() ).expect("Registers aura inherent data provider"); - let aura = start_aura::<_, _, _, _, sr25519::Pair, _, _, _>( + let aura = start_aura::<_, _, _, _, _, sr25519::Pair, _, _, _>( slot_duration, Arc::new(key.clone().into()), client.clone(), + select_chain, client, environ.clone(), DummyOracle, diff --git a/core/consensus/babe/src/lib.rs b/core/consensus/babe/src/lib.rs index c86e14de31fe6..a016e835c5fd8 100644 --- a/core/consensus/babe/src/lib.rs +++ b/core/consensus/babe/src/lib.rs @@ -58,10 +58,9 @@ use srml_babe::{ BabeInherentData, timestamp::{TimestampInherentData, InherentType as TimestampInherent} }; -use consensus_common::well_known_cache_keys; +use consensus_common::{SelectChain, well_known_cache_keys}; use consensus_common::import_queue::{Verifier, BasicQueue}; use client::{ - ChainHead, block_builder::api::BlockBuilder as BlockBuilderApi, blockchain::ProvideCache, runtime_api::ApiExt, @@ -249,7 +248,7 @@ impl SlotCompatible for BabeSlotCompatible { } /// Parameters for BABE. -pub struct BabeParams { +pub struct BabeParams { /// The configuration for BABE. Includes the slot duration, threshold, and /// other parameters. @@ -261,6 +260,9 @@ pub struct BabeParams { /// The client to use pub client: Arc, + /// The SelectChain Strategy + pub select_chain: SC, + /// A block importer pub block_import: Arc, @@ -281,28 +283,30 @@ pub struct BabeParams { } /// Start the babe worker. The returned future should be run in a tokio runtime. -pub fn start_babe(BabeParams { +pub fn start_babe(BabeParams { config, local_key, client, + select_chain, block_import, env, sync_oracle, on_exit, inherent_data_providers, force_authoring, -}: BabeParams) -> Result< +}: BabeParams) -> Result< impl Future, consensus_common::Error, > where B: Block, - C: ChainHead + ProvideRuntimeApi + ProvideCache, + C: ProvideRuntimeApi + ProvideCache, C::Api: AuthoritiesApi, E: Environment, E::Proposer: Proposer, <>::Create as IntoFuture>::Future: Send + 'static, I: BlockImport + Send + Sync + 'static, SO: SyncOracle + Send + Sync + Clone, + SC: SelectChain, DigestItemFor: CompatibleDigestItem + DigestItem, Error: ::std::error::Error + Send + From<::consensus_common::Error> + From + 'static, OnExit: Future, @@ -319,7 +323,7 @@ pub fn start_babe(BabeParams { }; slots::start_slot_worker::<_, _, _, _, _, BabeSlotCompatible, _>( config.0, - client, + select_chain, Arc::new(worker), sync_oracle, on_exit, @@ -847,10 +851,13 @@ fn claim_slot( } #[cfg(test)] -#[allow(dead_code, unused_imports)] +#[allow(dead_code, unused_imports, deprecated)] +// FIXME #2532: need to allow deprecated until refactor is done https://github.com/paritytech/substrate/issues/2532 + mod tests { use super::*; + use client::LongestChain; use consensus_common::NoNetwork as DummyOracle; use network::test::*; use network::test::{Block as TestBlock, PeersClient}; @@ -1014,6 +1021,7 @@ mod tests { config, local_key: Arc::new(key.clone().into()), block_import: client.clone(), + select_chain: LongestChain::new(client.backend().clone(), client.import_lock().clone()), client, env: environ.clone(), sync_oracle: DummyOracle, diff --git a/core/consensus/common/src/error.rs b/core/consensus/common/src/error.rs index f338dc9ce402f..71517c544062e 100644 --- a/core/consensus/common/src/error.rs +++ b/core/consensus/common/src/error.rs @@ -105,5 +105,11 @@ error_chain! { description("Import failed"), display("Import failed: {}", reason), } + + /// Error from the client while importing + ChainLookup(reason: String) { + description("Looking up chain failed"), + display("Chain lookup failed: {}", reason), + } } } diff --git a/core/consensus/common/src/lib.rs b/core/consensus/common/src/lib.rs index 7d846a32e3578..be7900d853a94 100644 --- a/core/consensus/common/src/lib.rs +++ b/core/consensus/common/src/lib.rs @@ -40,6 +40,7 @@ pub use inherents::InherentData; pub mod offline_tracker; pub mod error; mod block_import; +mod select_chain; pub mod import_queue; pub mod evaluation; @@ -50,6 +51,7 @@ pub use self::error::{Error, ErrorKind}; pub use block_import::{ BlockImport, BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult, JustificationImport, }; +pub use select_chain::SelectChain; /// Trait for getting the authorities at a given block. pub trait Authorities { diff --git a/core/consensus/common/src/select_chain.rs b/core/consensus/common/src/select_chain.rs new file mode 100644 index 0000000000000..47c65d1fe78e5 --- /dev/null +++ b/core/consensus/common/src/select_chain.rs @@ -0,0 +1,54 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate Consensus Common. + +// Substrate Demo 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 Consensus Common 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 Consensus Common. If not, see . + +use crate::error::Error; +use runtime_primitives::traits::{Block as BlockT, NumberFor}; + + +/// The SelectChain trait defines the strategy upon which the head is chosen +/// if multiple forks are present for an opaque definition of "best" in the +/// specific chain build. +/// +/// The Strategy can be customised for the two use cases of authoring new blocks +/// upon the best chain or which fork to finalise. Unless implemented differently +/// by default finalisation methods fall back to use authoring, so as a minimum +/// `_authoring`-functions must be implemented. +/// +/// Any particular user must make explicit, however, whether they intend to finalise +/// or author through the using the right function call, as these might differ in +/// some implementations. +/// +/// Non-deterministicly finalising chains may only use the `_authoring` functions. +pub trait SelectChain: Sync + Send + Clone { + + /// Get all leaves of the chain: block hashes that have no children currently. + /// Leaves that can never be finalized will not be returned. + fn leaves(&self) -> Result::Hash>, Error>; + + /// Among those `leaves` deterministically pick one chain as the generally + /// best chain to author new blocks upon and probably finalize. + fn best_chain(&self) -> Result<::Header, Error>; + + /// Get the best ancestor of `target_hash` that we should attempt + /// to finalize next. + fn finality_target( + &self, + target_hash: ::Hash, + _maybe_max_number: Option> + ) -> Result::Hash>, Error> { + Ok(Some(target_hash)) + } +} \ No newline at end of file diff --git a/core/consensus/rhd/src/service.rs b/core/consensus/rhd/src/service.rs index e2858f767a8c0..f59393c530356 100644 --- a/core/consensus/rhd/src/service.rs +++ b/core/consensus/rhd/src/service.rs @@ -22,7 +22,7 @@ use std::thread; use std::time::{Duration, Instant}; use std::sync::Arc; -use client::{BlockchainEvents, ChainHead, BlockBody}; +use client::{BlockchainEvents, BlockBody}; use futures::prelude::*; use transaction_pool::txpool::{Pool as TransactionPool, ChainApi as PoolChainApi}; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, BlockNumberToHash}; @@ -33,7 +33,7 @@ use tokio::runtime::current_thread::Runtime as LocalRuntime; use tokio::timer::Interval; use parking_lot::RwLock; -use consensus::offline_tracker::OfflineTracker; +use consensus::{self, offline_tracker::OfflineTracker}; use super::{Network, ProposerFactory, AuthoringApi}; use {consensus, primitives, ed25519, error, BftService, LocalProposer}; @@ -87,9 +87,9 @@ impl Service { A: AuthoringApi + BlockNumberToHash + 'static, P: PoolChainApi::Block> + 'static, C: BlockchainEvents<::Block> - + ChainHead<::Block> - + BlockBody<::Block>, - C: consensus::BlockImport<::Block> + + BlockBody<::Block> + + consensus::SelectChain<::Block> + + consensus::BlockImport<::Block> + consensus::Authorities<::Block> + Send + Sync + 'static, primitives::H256: From<<::Block as BlockT>::Hash>, <::Block as BlockT>::Hash: PartialEq + PartialEq, diff --git a/core/consensus/slots/src/lib.rs b/core/consensus/slots/src/lib.rs index ee8004661d038..2f8eedb497d39 100644 --- a/core/consensus/slots/src/lib.rs +++ b/core/consensus/slots/src/lib.rs @@ -26,9 +26,8 @@ mod slots; pub use slots::{slot_now, SlotInfo, Slots}; -use client::ChainHead; use codec::{Decode, Encode}; -use consensus_common::SyncOracle; +use consensus_common::{SyncOracle, SelectChain}; use futures::prelude::*; use futures::{ future::{self, Either}, @@ -73,7 +72,7 @@ pub fn inherent_to_common_error(err: inherents::RuntimeString) -> consensus_comm #[deprecated(since = "1.1", note = "Please spawn a thread manually")] pub fn start_slot_worker_thread( slot_duration: SlotDuration, - client: Arc, + select_chain: C, worker: Arc, sync_oracle: SO, on_exit: OnExit, @@ -81,7 +80,7 @@ pub fn start_slot_worker_thread( ) -> Result<(), consensus_common::Error> where B: Block + 'static, - C: ChainHead + Send + Sync + 'static, + C: SelectChain + Clone + 'static, W: SlotWorker + Send + Sync + 'static, SO: SyncOracle + Send + Clone + 'static, SC: SlotCompatible + 'static, @@ -101,9 +100,9 @@ where } }; - let slot_worker_future = match start_slot_worker::<_, _, _, _, _, SC, _>( + let slot_worker_future = match start_slot_worker::<_, _, _, T, _, SC, _>( slot_duration.clone(), - client, + select_chain, worker, sync_oracle, on_exit, @@ -134,7 +133,7 @@ where /// Start a new slot worker. pub fn start_slot_worker( slot_duration: SlotDuration, - client: Arc, + client: C, worker: Arc, sync_oracle: SO, on_exit: OnExit, @@ -142,7 +141,7 @@ pub fn start_slot_worker( ) -> Result, consensus_common::Error> where B: Block, - C: ChainHead, + C: SelectChain + Clone, W: SlotWorker, SO: SyncOracle + Send + Clone, SC: SlotCompatible, @@ -173,7 +172,7 @@ where } let slot_num = slot_info.number; - let chain_head = match client.best_block_header() { + let chain_head = match client.best_chain() { Ok(x) => x, Err(e) => { warn!(target: "slots", "Unable to author block in slot {}. \ diff --git a/core/finality-grandpa/src/environment.rs b/core/finality-grandpa/src/environment.rs index 8f5db93cae967..cec64b254ccfe 100644 --- a/core/finality-grandpa/src/environment.rs +++ b/core/finality-grandpa/src/environment.rs @@ -44,6 +44,8 @@ use crate::{ PrimaryPropose, SignedMessage, NewAuthoritySet, VoterCommand, }; +use consensus_common::SelectChain; + use crate::authorities::SharedAuthoritySet; use crate::consensus_changes::SharedConsensusChanges; use crate::justification::GrandpaJustification; @@ -262,8 +264,9 @@ impl SharedVoterSetState { } /// The environment we run GRANDPA in. -pub(crate) struct Environment, RA> { +pub(crate) struct Environment, RA, SC> { pub(crate) inner: Arc>, + pub(crate) select_chain: SC, pub(crate) voters: Arc>, pub(crate) config: Config, pub(crate) authority_set: SharedAuthoritySet>, @@ -273,7 +276,7 @@ pub(crate) struct Environment, RA> { pub(crate) voter_set_state: SharedVoterSetState, } -impl, RA> Environment { +impl, RA, SC> Environment { /// Updates the voter set state using the given closure. The write lock is /// held during evaluation of the closure and the environment's voter set /// state is set to its result if successful. @@ -289,12 +292,16 @@ impl, RA> Environment } } -impl, B, E, N, RA> grandpa::Chain> for Environment where +impl, B, E, N, RA, SC> + grandpa::Chain> +for Environment +where Block: 'static, B: Backend + 'static, E: CallExecutor + 'static, N: Network + 'static, N::In: 'static, + SC: SelectChain + 'static, NumberFor: BlockNumberOps, { fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result, GrandpaError> { @@ -317,7 +324,7 @@ impl, B, E, N, RA> grandpa::Chain { let base_header = self.inner.header(&BlockId::Hash(block)).ok()? .expect("Header known to exist after `best_containing` call; qed"); @@ -376,6 +383,7 @@ impl, B, E, N, RA> grandpa::Chain, E, RA>( client: &Client, base: Block::Hash, @@ -411,13 +419,17 @@ pub(crate) fn ancestry, E, RA>( Ok(tree_route.retracted().iter().skip(1).map(|e| e.hash).collect()) } -impl, N, RA> voter::Environment> for Environment where +impl, N, RA, SC> + voter::Environment> +for Environment +where Block: 'static, B: Backend + 'static, E: CallExecutor + 'static + Send + Sync, N: Network + 'static + Send, N::In: 'static + Send, RA: 'static + Send + Sync, + SC: SelectChain + 'static, NumberFor: BlockNumberOps, { type Timer = Box + Send>; diff --git a/core/finality-grandpa/src/import.rs b/core/finality-grandpa/src/import.rs index c3bd99247aacf..542617fbcf0b7 100644 --- a/core/finality-grandpa/src/import.rs +++ b/core/finality-grandpa/src/import.rs @@ -28,6 +28,7 @@ use client::runtime_api::ApiExt; use consensus_common::{ BlockImport, Error as ConsensusError, ErrorKind as ConsensusErrorKind, ImportBlock, ImportResult, JustificationImport, well_known_cache_keys, + SelectChain, }; use fg_primitives::GrandpaApi; use runtime_primitives::Justification; @@ -53,16 +54,17 @@ use crate::justification::GrandpaJustification; /// /// When using GRANDPA, the block import worker should be using this block import /// object. -pub struct GrandpaBlockImport, RA, PRA> { +pub struct GrandpaBlockImport, RA, PRA, SC> { inner: Arc>, + select_chain: SC, authority_set: SharedAuthoritySet>, send_voter_commands: mpsc::UnboundedSender>>, consensus_changes: SharedConsensusChanges>, api: Arc, } -impl, RA, PRA> JustificationImport - for GrandpaBlockImport where +impl, RA, PRA, SC> JustificationImport + for GrandpaBlockImport where NumberFor: grandpa::BlockNumberOps, B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, @@ -70,6 +72,7 @@ impl, RA, PRA> JustificationImport RA: Send + Sync, PRA: ProvideRuntimeApi, PRA::Api: GrandpaApi, + SC: SelectChain, { type Error = ConsensusError; @@ -86,7 +89,7 @@ impl, RA, PRA> JustificationImport pending_change.effective_number() > chain_info.finalized_number && pending_change.effective_number() <= chain_info.best_number { - let effective_block_hash = self.inner.best_containing( + let effective_block_hash = self.select_chain.finality_target( pending_change.canon_hash, Some(pending_change.effective_number()), ); @@ -155,7 +158,9 @@ impl<'a, Block: 'a + BlockT> Drop for PendingSetChanges<'a, Block> { } } -impl, RA, PRA> GrandpaBlockImport where +impl, RA, PRA, SC> + GrandpaBlockImport +where NumberFor: grandpa::BlockNumberOps, B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, @@ -371,8 +376,8 @@ impl, RA, PRA> GrandpaBlockImport, RA, PRA> BlockImport - for GrandpaBlockImport where +impl, RA, PRA, SC> BlockImport + for GrandpaBlockImport where NumberFor: grandpa::BlockNumberOps, B: Backend + 'static, E: CallExecutor + 'static + Clone + Send + Sync, @@ -504,16 +509,20 @@ impl, RA, PRA> BlockImport } } -impl, RA, PRA> GrandpaBlockImport { +impl, RA, PRA, SC> + GrandpaBlockImport +{ pub(crate) fn new( inner: Arc>, + select_chain: SC, authority_set: SharedAuthoritySet>, send_voter_commands: mpsc::UnboundedSender>>, consensus_changes: SharedConsensusChanges>, api: Arc, - ) -> GrandpaBlockImport { + ) -> GrandpaBlockImport { GrandpaBlockImport { inner, + select_chain, authority_set, send_voter_commands, consensus_changes, @@ -522,12 +531,13 @@ impl, RA, PRA> GrandpaBlockImport, RA, PRA> GrandpaBlockImport - where - NumberFor: grandpa::BlockNumberOps, - B: Backend + 'static, - E: CallExecutor + 'static + Clone + Send + Sync, - RA: Send + Sync, +impl, RA, PRA, SC> + GrandpaBlockImport +where + NumberFor: grandpa::BlockNumberOps, + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, { /// Import a block justification and finalize the block. diff --git a/core/finality-grandpa/src/lib.rs b/core/finality-grandpa/src/lib.rs index 1a20e19833ac0..bd7bc5f9e3223 100644 --- a/core/finality-grandpa/src/lib.rs +++ b/core/finality-grandpa/src/lib.rs @@ -52,6 +52,8 @@ //! or prune any signaled changes based on whether the signaling block is //! included in the newly-finalized chain. #![forbid(warnings)] +#![allow(deprecated)] // FIXME #2532: remove once the refactor is done https://github.com/paritytech/substrate/issues/2532 + use futures::prelude::*; use log::{debug, info, warn}; use futures::sync::mpsc; @@ -67,6 +69,7 @@ use runtime_primitives::traits::{ use fg_primitives::GrandpaApi; use inherents::InherentDataProviders; use runtime_primitives::generic::BlockId; +use consensus_common::SelectChain; use substrate_primitives::{ed25519, H256, Pair, Blake2Hasher}; use substrate_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG, CONSENSUS_WARN}; use serde_json; @@ -285,24 +288,30 @@ impl fmt::Display for CommandOrError { } } -pub struct LinkHalf, RA> { +pub struct LinkHalf, RA, SC> { client: Arc>, + select_chain: SC, persistent_data: PersistentData, voter_commands_rx: mpsc::UnboundedReceiver>>, } /// Make block importer and link half necessary to tie the background voter /// to it. -pub fn block_import, RA, PRA>( +pub fn block_import, RA, PRA, SC>( client: Arc>, - api: Arc -) -> Result<(GrandpaBlockImport, LinkHalf), ClientError> - where - B: Backend + 'static, - E: CallExecutor + 'static + Clone + Send + Sync, - RA: Send + Sync, - PRA: ProvideRuntimeApi, - PRA::Api: GrandpaApi, + api: Arc, + select_chain: SC +) -> Result<( + GrandpaBlockImport, + LinkHalf + ), ClientError> +where + B: Backend + 'static, + E: CallExecutor + 'static + Clone + Send + Sync, + RA: Send + Sync, + PRA: ProvideRuntimeApi, + PRA::Api: GrandpaApi, + SC: SelectChain, { use runtime_primitives::traits::Zero; @@ -328,6 +337,7 @@ pub fn block_import, RA, PRA>( Ok(( GrandpaBlockImport::new( client.clone(), + select_chain.clone(), persistent_data.authority_set.clone(), voter_commands_tx, persistent_data.consensus_changes.clone(), @@ -335,6 +345,7 @@ pub fn block_import, RA, PRA>( ), LinkHalf { client, + select_chain, persistent_data, voter_commands_rx, }, @@ -435,11 +446,11 @@ fn register_finality_tracker_inherent_data_provider, N, RA, X> { +pub struct GrandpaParams<'a, B, E, Block: BlockT, N, RA, SC, X> { /// Configuration for the GRANDPA service. pub config: Config, /// A link to the block import worker. - pub link: LinkHalf, + pub link: LinkHalf, /// The Network instance. pub network: N, /// The inherent data providers. @@ -452,14 +463,15 @@ pub struct GrandpaParams<'a, B, E, Block: BlockT, N, RA, X> { /// Run a GRANDPA voter as a task. Provide configuration and a link to a /// block import worker that has already been instantiated with `block_import`. -pub fn run_grandpa_voter, N, RA, X>( - grandpa_params: GrandpaParams, +pub fn run_grandpa_voter, N, RA, SC, X>( + grandpa_params: GrandpaParams, ) -> ::client::error::Result + Send + 'static> where Block::Hash: Ord, B: Backend + 'static, E: CallExecutor + Send + Sync + 'static, N: Network + Send + Sync + 'static, N::In: Send + 'static, + SC: SelectChain + 'static, NumberFor: BlockNumberOps, DigestFor: Encode, RA: Send + Sync + 'static, @@ -480,6 +492,7 @@ pub fn run_grandpa_voter, N, RA, X>( let LinkHalf { client, + select_chain, persistent_data, voter_commands_rx, } = link; @@ -513,6 +526,7 @@ pub fn run_grandpa_voter, N, RA, X>( let initial_environment = Arc::new(Environment { inner: client.clone(), config: config.clone(), + select_chain: select_chain.clone(), voters: Arc::new(voters), network: network.clone(), set_id: authority_set.set_id(), @@ -599,6 +613,7 @@ pub fn run_grandpa_voter, N, RA, X>( let client = client.clone(); let config = config.clone(); let network = network.clone(); + let select_chain = select_chain.clone(); let authority_set = authority_set.clone(); let consensus_changes = consensus_changes.clone(); @@ -636,6 +651,7 @@ pub fn run_grandpa_voter, N, RA, X>( let env = Arc::new(Environment { inner: client, + select_chain, config, voters: Arc::new(new.authorities.into_iter().collect()), set_id: new.set_id, @@ -704,14 +720,15 @@ pub fn run_grandpa_voter, N, RA, X>( } #[deprecated(since = "1.1", note = "Please switch to run_grandpa_voter.")] -pub fn run_grandpa, N, RA, X>( - grandpa_params: GrandpaParams, +pub fn run_grandpa, N, RA, SC, X>( + grandpa_params: GrandpaParams, ) -> ::client::error::Result + Send + 'static> where Block::Hash: Ord, B: Backend + 'static, E: CallExecutor + Send + Sync + 'static, N: Network + Send + Sync + 'static, N::In: Send + 'static, + SC: SelectChain + 'static, NumberFor: BlockNumberOps, DigestFor: Encode, RA: Send + Sync + 'static, diff --git a/core/finality-grandpa/src/observer.rs b/core/finality-grandpa/src/observer.rs index 1e81e084e70aa..f6d657e1ff1f8 100644 --- a/core/finality-grandpa/src/observer.rs +++ b/core/finality-grandpa/src/observer.rs @@ -24,6 +24,7 @@ use grandpa::{ }; use log::{debug, info, warn}; +use consensus_common::SelectChain; use client::{CallExecutor, Client, backend::Backend}; use runtime_primitives::traits::{NumberFor, Block as BlockT}; use substrate_primitives::{ed25519::Public as AuthorityId, H256, Blake2Hasher}; @@ -143,9 +144,9 @@ fn grandpa_observer, RA, S>( /// listening for and validating GRANDPA commits instead of following the full /// protocol. Provide configuration and a link to a block import worker that has /// already been instantiated with `block_import`. -pub fn run_grandpa_observer, N, RA>( +pub fn run_grandpa_observer, N, RA, SC>( config: Config, - link: LinkHalf, + link: LinkHalf, network: N, on_exit: impl Future + Clone + Send + 'static, ) -> ::client::error::Result + Send + 'static> where @@ -153,11 +154,13 @@ pub fn run_grandpa_observer, N, RA>( E: CallExecutor + Send + Sync + 'static, N: Network + Send + Sync + 'static, N::In: Send + 'static, + SC: SelectChain + 'static, NumberFor: BlockNumberOps, RA: Send + Sync + 'static, { let LinkHalf { client, + select_chain: _, persistent_data, voter_commands_rx, } = link; diff --git a/core/finality-grandpa/src/service_integration.rs b/core/finality-grandpa/src/service_integration.rs index 3eee1dd9408d4..168e64183782e 100644 --- a/core/finality-grandpa/src/service_integration.rs +++ b/core/finality-grandpa/src/service_integration.rs @@ -30,11 +30,13 @@ pub type BlockImportForService = crate::GrandpaBlockImport< ::Block, ::RuntimeApi >, + ::SelectChain >; pub type LinkHalfForService = crate::LinkHalf< FullBackend, FullExecutor, ::Block, - ::RuntimeApi + ::RuntimeApi, + ::SelectChain >; diff --git a/core/finality-grandpa/src/tests.rs b/core/finality-grandpa/src/tests.rs index a631ec0c07ab3..52b23bfcb79f2 100644 --- a/core/finality-grandpa/src/tests.rs +++ b/core/finality-grandpa/src/tests.rs @@ -28,6 +28,7 @@ use client::{ BlockchainEvents, error::Result, blockchain::Backend as BlockchainBackend, runtime_api::{Core, RuntimeVersion, ApiExt}, + LongestChain, }; use test_client::{self, runtime::BlockNumber}; use consensus_common::{BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult}; @@ -50,6 +51,7 @@ type PeerData = test_client::Executor, Block, test_client::runtime::RuntimeApi, + LongestChain > > >; @@ -106,9 +108,15 @@ impl TestNetFactory for GrandpaTestNet { fn make_block_import(&self, client: Arc) -> (SharedBlockImport, Option>, PeerData) { + + let select_chain = LongestChain::new( + client.backend().clone(), + client.import_lock().clone() + ); let (import, link) = block_import( client, - Arc::new(self.test_config.clone()) + Arc::new(self.test_config.clone()), + select_chain, ).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))) @@ -679,6 +687,7 @@ fn transition_3_voters_twice_1_full_observer() { link, ) }; + finality_notifications.push( client.finality_notification_stream() .take_while(|n| Ok(n.header.number() < &30)) diff --git a/core/service/src/chain_ops.rs b/core/service/src/chain_ops.rs index 36cbee9039395..9f39e8088e7f8 100644 --- a/core/service/src/chain_ops.rs +++ b/core/service/src/chain_ops.rs @@ -127,7 +127,8 @@ pub fn import_blocks( { let client = new_client::(&config)?; // FIXME #1134 this shouldn't need a mutable config. - let queue = components::FullComponents::::build_import_queue(&mut config, client.clone())?; + let select_chain = components::FullComponents::::build_select_chain(&mut config, client.clone())?; + let queue = components::FullComponents::::build_import_queue(&mut config, client.clone(), select_chain)?; let (wait_send, wait_recv) = std::sync::mpsc::channel(); let wait_link = WaitLink::new(wait_send); diff --git a/core/service/src/components.rs b/core/service/src/components.rs index db2aae4c46d5a..7e76a8f201a9b 100644 --- a/core/service/src/components.rs +++ b/core/service/src/components.rs @@ -16,14 +16,14 @@ //! Substrate service components. -use std::{sync::Arc, net::SocketAddr, marker::PhantomData, ops::Deref, ops::DerefMut}; +use std::{sync::Arc, net::SocketAddr, ops::Deref, ops::DerefMut}; use serde::{Serialize, de::DeserializeOwned}; use tokio::runtime::TaskExecutor; use crate::chain_spec::ChainSpec; use client_db; use client::{self, Client, runtime_api}; use crate::{error, Service, maybe_start_server}; -use consensus_common::import_queue::ImportQueue; +use consensus_common::{import_queue::ImportQueue, SelectChain}; use network::{self, OnDemand}; use substrate_executor::{NativeExecutor, NativeExecutionDispatch}; use transaction_pool::txpool::{self, Options as TransactionPoolOptions, Pool as TransactionPool}; @@ -304,9 +304,11 @@ pub trait ServiceFactory: 'static + Sized { /// Extended light service type. type LightService: ServiceTrait>; /// ImportQueue for full client - type FullImportQueue: consensus_common::import_queue::ImportQueue + 'static; + type FullImportQueue: ImportQueue + 'static; /// ImportQueue for light clients - type LightImportQueue: consensus_common::import_queue::ImportQueue + 'static; + type LightImportQueue: ImportQueue + 'static; + /// The Fork Choice Strategy for the chain + type SelectChain: SelectChain + 'static; //TODO: replace these with a constructor trait. that TransactionPool implements. (#1242) /// Extrinsic pool constructor for the full client. @@ -320,6 +322,12 @@ pub trait ServiceFactory: 'static + Sized { fn build_network_protocol(config: &FactoryFullConfiguration) -> Result; + /// Build the Fork Choice algorithm for full client + fn build_select_chain( + config: &mut FactoryFullConfiguration, + client: Arc>, + ) -> Result; + /// Build full service. fn new_full(config: FactoryFullConfiguration, executor: TaskExecutor) -> Result; @@ -330,7 +338,8 @@ pub trait ServiceFactory: 'static + Sized { /// ImportQueue for a full client fn build_full_import_queue( config: &mut FactoryFullConfiguration, - _client: Arc> + _client: Arc>, + _select_chain: Self::SelectChain ) -> Result { if let Some(name) = config.chain_spec.consensus_engine() { match name { @@ -378,6 +387,8 @@ pub trait Components: Sized + 'static { >; /// Our Import Queue type ImportQueue: ImportQueue> + 'static; + /// The Fork Choice Strategy for the chain + type SelectChain: SelectChain>; /// Create client. fn build_client( @@ -398,13 +409,20 @@ pub trait Components: Sized + 'static { /// instance of import queue for clients fn build_import_queue( config: &mut FactoryFullConfiguration, - client: Arc> + client: Arc>, + select_chain: Self::SelectChain, ) -> Result; + + /// Build fork choice selector + fn build_select_chain( + config: &mut FactoryFullConfiguration, + client: Arc> + ) -> Result; + } /// A struct that implement `Components` for the full client. pub struct FullComponents { - _factory: PhantomData, service: Service>, } @@ -416,7 +434,6 @@ impl FullComponents { ) -> Result { Ok( Self { - _factory: Default::default(), service: Service::new(config, task_executor)?, } ) @@ -445,6 +462,7 @@ impl Components for FullComponents { type ImportQueue = Factory::FullImportQueue; type RuntimeApi = Factory::RuntimeApi; type RuntimeServices = Factory::FullService; + type SelectChain = Factory::SelectChain; fn build_client( config: &FactoryFullConfiguration, @@ -469,23 +487,32 @@ impl Components for FullComponents { )?), None)) } - fn build_transaction_pool(config: TransactionPoolOptions, client: Arc>) - -> Result, error::Error> - { + fn build_transaction_pool( + config: TransactionPoolOptions, + client: Arc> + ) -> Result, error::Error> { Factory::build_full_transaction_pool(config, client) } fn build_import_queue( config: &mut FactoryFullConfiguration, - client: Arc> + client: Arc>, + select_chain: Self::SelectChain, ) -> Result { - Factory::build_full_import_queue(config, client) + Factory::build_full_import_queue(config, client, select_chain) + } + + fn build_select_chain( + config: &mut FactoryFullConfiguration, + client: Arc> + ) -> Result { + Self::Factory::build_select_chain(config, client) } + } /// A struct that implement `Components` for the light client. pub struct LightComponents { - _factory: PhantomData, service: Service>, } @@ -497,7 +524,6 @@ impl LightComponents { ) -> Result { Ok( Self { - _factory: Default::default(), service: Service::new(config, task_executor)?, } ) @@ -520,6 +546,7 @@ impl Components for LightComponents { type ImportQueue = ::LightImportQueue; type RuntimeApi = Factory::RuntimeApi; type RuntimeServices = Factory::LightService; + type SelectChain = Factory::SelectChain; fn build_client( config: &FactoryFullConfiguration, @@ -554,16 +581,27 @@ impl Components for LightComponents { fn build_import_queue( config: &mut FactoryFullConfiguration, - client: Arc> + client: Arc>, + _select_chain: Self::SelectChain, ) -> Result { Factory::build_light_import_queue(config, client) } + + /// Build fork choice selector + fn build_select_chain( + _config: &mut FactoryFullConfiguration, + _client: Arc> + ) -> Result { + Err("Fork choice doesn't happen on light clients.".into()) + } + } #[cfg(test)] mod tests { use super::*; use consensus_common::BlockOrigin; + use client::LongestChain; use substrate_test_client::{self, TestClient, AccountKeyring, runtime::Transfer}; #[test] @@ -576,8 +614,11 @@ mod tests { from: AccountKeyring::Alice.into(), to: Default::default(), }.into_signed_tx(); + let best = LongestChain::new(client.backend().clone(), client.import_lock()) + .best_chain().unwrap(); + // store the transaction in the pool - pool.submit_one(&BlockId::hash(client.best_block_header().unwrap().hash()), transaction.clone()).unwrap(); + pool.submit_one(&BlockId::hash(best.hash()), transaction.clone()).unwrap(); // import the block let mut builder = client.new_block().unwrap(); diff --git a/core/service/src/lib.rs b/core/service/src/lib.rs index afd2b52e57f2e..aed14568cb682 100644 --- a/core/service/src/lib.rs +++ b/core/service/src/lib.rs @@ -42,6 +42,7 @@ use primitives::Pair; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Header, As}; use substrate_executor::NativeExecutor; +use consensus_common::SelectChain; use tel::{telemetry, SUBSTRATE_INFO}; pub use self::error::{ErrorKind, Error}; @@ -73,6 +74,7 @@ const DEFAULT_PROTOCOL_ID: &str = "sup"; /// Substrate service. pub struct Service { client: Arc>, + select_chain: ::SelectChain, network: Option>>, transaction_pool: Arc>, inherents_pool: Arc>>, @@ -150,8 +152,13 @@ impl Service { }; let (client, on_demand) = Components::build_client(&config, executor)?; - let import_queue = Box::new(Components::build_import_queue(&mut config, client.clone())?); - let best_header = client.best_block_header()?; + let select_chain = Components::build_select_chain(&mut config, client.clone())?; + let import_queue = Box::new(Components::build_import_queue( + &mut config, + client.clone(), + select_chain.clone() + )?); + let best_header = select_chain.best_chain()?; let version = config.full_version(); info!("Best block: #{}", best_header.number()); @@ -357,6 +364,7 @@ impl Service { Ok(Service { client, network: Some(network), + select_chain, transaction_pool, inherents_pool, signal: Some(signal), @@ -395,6 +403,11 @@ impl Service where Components: components::Components { self.client.clone() } + /// Get clone of select chain. + pub fn select_chain(&self) -> ::SelectChain { + self.select_chain.clone() + } + /// Get shared network instance. pub fn network(&self) -> Arc> { self.network.as_ref().expect("self.network always Some").clone() @@ -578,6 +591,8 @@ macro_rules! construct_service_factory { { $( $full_import_queue_init:tt )* }, LightImportQueue = $light_import_queue:ty { $( $light_import_queue_init:tt )* }, + SelectChain = $select_chain:ty + { $( $select_chain_init:tt )* }, } ) => { $( #[$attr] )* @@ -597,6 +612,7 @@ macro_rules! construct_service_factory { type LightService = $light_service; type FullImportQueue = $full_import_queue; type LightImportQueue = $light_import_queue; + type SelectChain = $select_chain; fn build_full_transaction_pool( config: $crate::TransactionPoolOptions, @@ -620,11 +636,19 @@ macro_rules! construct_service_factory { ( $( $protocol_init )* ) (config) } + fn build_select_chain( + config: &mut $crate::FactoryFullConfiguration, + client: Arc<$crate::FullClient> + ) -> $crate::Result { + ( $( $select_chain_init )* ) (config, client) + } + fn build_full_import_queue( config: &mut $crate::FactoryFullConfiguration, client: $crate::Arc<$crate::FullClient>, + select_chain: Self::SelectChain ) -> $crate::Result { - ( $( $full_import_queue_init )* ) (config, client) + ( $( $full_import_queue_init )* ) (config, client, select_chain) } fn build_light_import_queue( diff --git a/core/sr-api-macros/tests/runtime_calls.rs b/core/sr-api-macros/tests/runtime_calls.rs index df1be11a74fb8..fb3cad3238e6f 100644 --- a/core/sr-api-macros/tests/runtime_calls.rs +++ b/core/sr-api-macros/tests/runtime_calls.rs @@ -27,6 +27,8 @@ use state_machine::{ execution_proof_check_on_trie_backend, }; +use client::LongestChain; +use consensus_common::SelectChain; use codec::Encode; fn calling_function_with_strat(strat: ExecutionStrategy) { @@ -156,7 +158,8 @@ fn record_proof_works() { let client = test_client::new_with_execution_strategy(ExecutionStrategy::Both); let block_id = BlockId::Number(client.info().unwrap().chain.best_number); - let storage_root = client.best_block_header().unwrap().state_root().clone(); + let storage_root = LongestChain::new(client.backend().clone(), client.import_lock()) + .best_chain().unwrap().state_root().clone(); let transaction = Transfer { amount: 1000, diff --git a/node-template/src/service.rs b/node-template/src/service.rs index 239f02f33da3f..2a4980688f8a7 100644 --- a/node-template/src/service.rs +++ b/node-template/src/service.rs @@ -13,7 +13,7 @@ use substrate_service::{ }; use basic_authorship::ProposerFactory; use consensus::{import_queue, start_aura, AuraImportQueue, SlotDuration, NothingExtra}; -use substrate_client as client; +use substrate_client::{self as client, LongestChain}; use primitives::{ed25519::Pair, Pair as PairT}; use inherents::InherentDataProviders; use network::construct_simple_protocol; @@ -69,6 +69,7 @@ construct_service_factory! { SlotDuration::get_or_compute(&*client)?, key.clone(), client.clone(), + service.select_chain(), client, proposer, service.network(), @@ -86,7 +87,7 @@ construct_service_factory! { FullImportQueue = AuraImportQueue< Self::Block, > - { |config: &mut FactoryFullConfiguration , client: Arc>| { + { |config: &mut FactoryFullConfiguration , client: Arc>, _select_chain: Self::SelectChain| { import_queue::<_, _, _, Pair>( SlotDuration::get_or_compute(&*client)?, client.clone(), @@ -111,5 +112,13 @@ construct_service_factory! { ).map_err(Into::into) } }, + SelectChain = LongestChain, Self::Block> + { |config: &FactoryFullConfiguration, client: Arc>| { + Ok(LongestChain::new( + client.backend().clone(), + client.import_lock() + )) + } + }, } } diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 96a531a4ef7f1..47bdb24122309 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -21,8 +21,10 @@ use std::sync::Arc; use std::time::Duration; -use client; -use consensus::{import_queue, start_aura, AuraImportQueue, SlotDuration, NothingExtra}; +use client::{self, LongestChain}; +use consensus::{import_queue, start_aura, AuraImportQueue, + SlotDuration, NothingExtra +}; use grandpa; use node_executor; use primitives::{Pair as PairT, ed25519}; @@ -94,6 +96,7 @@ construct_service_factory! { SlotDuration::get_or_compute(&*client)?, key.clone(), client, + service.select_chain(), block_import.clone(), proposer, service.network(), @@ -152,11 +155,11 @@ construct_service_factory! { LightService = LightComponents { |config, executor| >::new(config, executor) }, FullImportQueue = AuraImportQueue - { |config: &mut FactoryFullConfiguration , client: Arc>| { + { |config: &mut FactoryFullConfiguration , client: Arc>, select_chain: Self::SelectChain| { let slot_duration = SlotDuration::get_or_compute(&*client)?; let (block_import, link_half) = - grandpa::block_import::<_, _, _, RuntimeApi, FullClient>( - client.clone(), client.clone() + grandpa::block_import::<_, _, _, RuntimeApi, FullClient, _>( + client.clone(), client.clone(), select_chain )?; let block_import = Arc::new(block_import); let justification_import = block_import.clone(); @@ -184,6 +187,14 @@ construct_service_factory! { ).map_err(Into::into) } }, + SelectChain = LongestChain, Self::Block> + { |config: &FactoryFullConfiguration, client: Arc>| { + Ok(LongestChain::new( + client.backend().clone(), + client.import_lock() + )) + } + }, } } diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index b73cc8e99c757..faaa72430266a 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -58,7 +58,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node"), impl_name: create_runtime_str!("substrate-node"), authoring_version: 10, - spec_version: 73, + spec_version: 74, impl_version: 74, apis: RUNTIME_API_VERSIONS, };