diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index efc5ca4ee8ca0..47fec977f5e82 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -536,3 +536,21 @@ pub fn changes_tries_state_at_block<'a, Block: BlockT>( None => Ok(None), } } + +/// Provide CHT roots. These are stored on a light client and generated dynamically on a full +/// client. +pub trait ProvideChtRoots { + /// Get headers CHT root for given block. Returns None if the block is not a part of any CHT. + fn header_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result>; + + /// Get changes trie CHT root for given block. Returns None if the block is not a part of any CHT. + fn changes_trie_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result>; +} diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 13ff7a01f9193..ded030fb8046f 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -35,7 +35,7 @@ use sp_state_machine::{ use sp_blockchain::{CachedHeaderMetadata, HeaderMetadata}; use crate::{ - backend::{self, NewBlockState}, + backend::{self, NewBlockState, ProvideChtRoots}, blockchain::{ self, BlockStatus, HeaderBackend, well_known_cache_keys::Id as CacheKeyId }, @@ -456,7 +456,7 @@ impl light::Storage for Blockchain } } -impl light::ChtRootStorage for Blockchain { +impl ProvideChtRoots for Blockchain { fn header_cht_root( &self, _cht_size: NumberFor, diff --git a/client/api/src/light.rs b/client/api/src/light.rs index f9aa002841caa..144851dac0075 100644 --- a/client/api/src/light.rs +++ b/client/api/src/light.rs @@ -32,7 +32,7 @@ use sp_blockchain::{ HeaderMetadata, well_known_cache_keys, HeaderBackend, Cache as BlockchainCache, Error as ClientError, Result as ClientResult, }; -use crate::{backend::{AuxStore, NewBlockState}, UsageInfo}; +use crate::{backend::{AuxStore, NewBlockState}, UsageInfo, ProvideChtRoots}; /// Remote call request. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -233,7 +233,7 @@ pub trait FetchChecker: Send + Sync { /// Light client blockchain storage. pub trait Storage: AuxStore + HeaderBackend - + HeaderMetadata + ChtRootStorage + + HeaderMetadata + ProvideChtRoots { /// Store new header. Should refuse to revert any finalized blocks. /// @@ -263,23 +263,6 @@ pub trait Storage: AuxStore + HeaderBackend fn usage_info(&self) -> Option; } -/// Light client CHT root storage. -pub trait ChtRootStorage { - /// Get headers CHT root for given block. Returns None if the block is not pruned (not a part of any CHT). - fn header_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> ClientResult>; - - /// Get changes trie CHT root for given block. Returns None if the block is not pruned (not a part of any CHT). - fn changes_trie_cht_root( - &self, - cht_size: NumberFor, - block: NumberFor, - ) -> ClientResult>; -} - /// Remote header. #[derive(Debug)] pub enum LocalOrRemote { diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index d854c80bf3535..bd438f4dd71b2 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -54,8 +54,8 @@ use std::collections::{HashMap, HashSet}; use sc_client_api::{ UsageInfo, MemoryInfo, IoInfo, MemorySize, - backend::{NewBlockState, PrunableStateChangesTrieStorage}, - leaves::{LeafSet, FinalizationDisplaced}, + backend::{NewBlockState, PrunableStateChangesTrieStorage, ProvideChtRoots}, + leaves::{LeafSet, FinalizationDisplaced}, cht, }; use sp_blockchain::{ Result as ClientResult, Error as ClientError, @@ -70,7 +70,7 @@ use sp_core::ChangesTrieConfiguration; use sp_core::offchain::storage::{OffchainOverlayedChange, OffchainOverlayedChanges}; use sp_core::storage::{well_known_keys, ChildInfo}; use sp_arithmetic::traits::Saturating; -use sp_runtime::{generic::BlockId, Justification, Storage}; +use sp_runtime::{generic::{DigestItem, BlockId}, Justification, Storage}; use sp_runtime::traits::{ Block as BlockT, Header as HeaderT, NumberFor, Zero, One, SaturatedConversion, HashFor, }; @@ -405,6 +405,14 @@ impl BlockchainDb { meta.finalized_hash = hash; } } + + // Get block changes trie root, if available. + fn changes_trie_root(&self, block: BlockId) -> ClientResult> { + self.header(block) + .map(|header| header.and_then(|header| + header.digest().log(DigestItem::as_changes_trie_root) + .cloned())) + } } impl sc_client_api::blockchain::HeaderBackend for BlockchainDb { @@ -525,6 +533,58 @@ impl HeaderMetadata for BlockchainDb { } } +impl ProvideChtRoots for BlockchainDb { + fn header_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result> { + let cht_number = match cht::block_to_cht_number(cht_size, block) { + Some(number) => number, + None => return Ok(None), + }; + + let cht_start: NumberFor = cht::start_number(cht::size(), cht_number); + + let mut current_num = cht_start; + let cht_range = ::std::iter::from_fn(|| { + let old_current_num = current_num; + current_num = current_num + One::one(); + Some(old_current_num) + }); + + cht::compute_root::, _>( + cht::size(), cht_number, cht_range.map(|num| self.hash(num)) + ).map(Some) + } + + fn changes_trie_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result> { + let cht_number = match cht::block_to_cht_number(cht_size, block) { + Some(number) => number, + None => return Ok(None), + }; + + let cht_start: NumberFor = cht::start_number(cht::size(), cht_number); + + let mut current_num = cht_start; + let cht_range = ::std::iter::from_fn(|| { + let old_current_num = current_num; + current_num = current_num + One::one(); + Some(old_current_num) + }); + + cht::compute_root::, _>( + cht::size(), + cht_number, + cht_range.map(|num| self.changes_trie_root(BlockId::Number(num))), + ).map(Some) + } +} + /// Database transaction pub struct BlockImportOperation { old_state: SyncingCachingState, Block>, @@ -2329,4 +2389,29 @@ pub(crate) mod tests { backend.commit_operation(op).unwrap_err(); } } + + #[test] + fn header_cht_root_works() { + use sc_client_api::ProvideChtRoots; + + let backend = Backend::::new_test(10, 10); + + // insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created + let mut prev_hash = insert_header(&backend, 0, Default::default(), None, Default::default()); + let cht_size: u64 = cht::size(); + for i in 1..1 + cht_size + cht_size + 1 { + prev_hash = insert_header(&backend, i, prev_hash, None, Default::default()); + } + + let blockchain = backend.blockchain(); + + let cht_root_1 = blockchain.header_cht_root(cht_size, cht::start_number(cht_size, 0)) + .unwrap().unwrap(); + let cht_root_2 = blockchain.header_cht_root(cht_size, cht::start_number(cht_size, 0) + cht_size / 2) + .unwrap().unwrap(); + let cht_root_3 = blockchain.header_cht_root(cht_size, cht::end_number(cht_size, 0)) + .unwrap().unwrap(); + assert_eq!(cht_root_1, cht_root_2); + assert_eq!(cht_root_2, cht_root_3); + } } diff --git a/client/db/src/light.rs b/client/db/src/light.rs index adf9a98d35e2a..acfb6217ce9e0 100644 --- a/client/db/src/light.rs +++ b/client/db/src/light.rs @@ -23,11 +23,11 @@ use std::convert::TryInto; use parking_lot::RwLock; use sc_client_api::{ - cht, backend::{AuxStore, NewBlockState}, UsageInfo, + cht, backend::{AuxStore, NewBlockState, ProvideChtRoots}, UsageInfo, blockchain::{ BlockStatus, Cache as BlockchainCache, Info as BlockchainInfo, }, - Storage, ChtRootStorage, + Storage, }; use sp_blockchain::{ CachedHeaderMetadata, HeaderMetadata, HeaderMetadataCache, @@ -596,7 +596,7 @@ impl Storage for LightStorage } } -impl ChtRootStorage for LightStorage +impl ProvideChtRoots for LightStorage where Block: BlockT, { fn header_cht_root( diff --git a/client/light/src/blockchain.rs b/client/light/src/blockchain.rs index 9d557db887d29..3b5753f2849d5 100644 --- a/client/light/src/blockchain.rs +++ b/client/light/src/blockchain.rs @@ -29,7 +29,7 @@ use sp_blockchain::{ }; pub use sc_client_api::{ backend::{ - AuxStore, NewBlockState + AuxStore, NewBlockState, ProvideChtRoots, }, blockchain::{ Backend as BlockchainBackend, BlockStatus, Cache as BlockchainCache, @@ -173,3 +173,21 @@ impl RemoteBlockchain for Blockchain })) } } + +impl, Block: BlockT> ProvideChtRoots for Blockchain { + fn header_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result> { + self.storage().header_cht_root(cht_size, block) + } + + fn changes_trie_cht_root( + &self, + cht_size: NumberFor, + block: NumberFor, + ) -> sp_blockchain::Result> { + self.storage().changes_trie_cht_root(cht_size, block) + } +} diff --git a/client/service/src/chain_ops/build_spec.rs b/client/service/src/chain_ops/build_spec.rs index c84c1c754adac..40d591d81f02b 100644 --- a/client/service/src/chain_ops/build_spec.rs +++ b/client/service/src/chain_ops/build_spec.rs @@ -16,31 +16,9 @@ use sp_runtime::traits::{Block as BlockT, NumberFor, Saturating, One}; use sp_blockchain::HeaderBackend; -use crate::{TFullBackend, TLightBackend}; use std::sync::Arc; use sp_runtime::generic::BlockId; - -/// An error for if this function is being called on a full node. -pub const CHT_ROOT_ERROR: &str = - "Backend doesn't store CHT roots. Make sure you're calling this on a light client."; - -/// Something that might allow access to a `ChtRootStorage`. -pub trait MaybeChtRootStorageProvider { - /// Potentially get a reference to a `ChtRootStorage`. - fn cht_root_storage(&self) -> Option<&dyn sc_client_api::light::ChtRootStorage>; -} - -impl MaybeChtRootStorageProvider for TFullBackend { - fn cht_root_storage(&self) -> Option<&dyn sc_client_api::light::ChtRootStorage> { - None - } -} - -impl MaybeChtRootStorageProvider for TLightBackend { - fn cht_root_storage(&self) -> Option<&dyn sc_client_api::light::ChtRootStorage> { - Some(self.blockchain().storage()) - } -} +use sc_client_api::ProvideChtRoots; /// Build a `LightSyncState` from the CHT roots stored in a backend. pub fn build_light_sync_state( @@ -50,9 +28,10 @@ pub fn build_light_sync_state( where TBl: BlockT, TCl: HeaderBackend, - TBackend: MaybeChtRootStorageProvider, + TBackend: sc_client_api::Backend, + >::Blockchain: ProvideChtRoots, { - let storage = backend.cht_root_storage().ok_or(CHT_ROOT_ERROR)?; + let cht_root_provider = backend.blockchain(); let finalized_hash = client.info().finalized_hash; let finalized_number = client.info().finalized_number; @@ -67,7 +46,7 @@ pub fn build_light_sync_state( let mut number = NumberFor::::one(); while number <= finalized_number.saturating_sub(cht_size_x_2) { - match storage.header_cht_root(cht::size(), number)? { + match cht_root_provider.header_cht_root(cht::size(), number)? { Some(cht_root) => chts.push(cht_root), None => log::error!("No CHT found for block {}", number), } diff --git a/client/service/test/src/client/light.rs b/client/service/test/src/client/light.rs index 515d7d1532677..f38aef008e11c 100644 --- a/client/service/test/src/client/light.rs +++ b/client/service/test/src/client/light.rs @@ -42,7 +42,7 @@ use sc_executor::{NativeExecutor, WasmExecutionMethod, RuntimeVersion, NativeVer use sp_core::{H256, NativeOrEncoded, testing::TaskExecutor}; use sc_client_api::{ blockchain::Info, backend::NewBlockState, Backend as ClientBackend, ProofProvider, - in_mem::{Backend as InMemBackend, Blockchain as InMemoryBlockchain}, ChtRootStorage, + in_mem::{Backend as InMemBackend, Blockchain as InMemoryBlockchain}, ProvideChtRoots, AuxStore, Storage, CallExecutor, cht, ExecutionStrategy, StorageProof, BlockImportOperation, RemoteCallRequest, StorageProvider, ChangesProof, RemoteBodyRequest, RemoteReadRequest, RemoteChangesRequest, FetchChecker, RemoteReadChildRequest, RemoteHeaderRequest, BlockBackend, @@ -173,7 +173,7 @@ impl Storage for DummyStorage { } } -impl ChtRootStorage for DummyStorage { +impl ProvideChtRoots for DummyStorage { fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult> { Err(ClientError::Backend("Test error".into())) }