Removal of light client from substrate (#9684)

* Removal of light client from substrate

* add missing import

* These tests relate to there being light and non light clients.

* removing lightnodes from test

* cargo fmt

* not needed

* LightDataChecker not needed any longer

* cargo fmt

* Update client/service/test/src/lib.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update client/service/test/src/lib.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* cargo fmt

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Squirrel
2021-10-30 14:38:27 +02:00
committed by GitHub
parent 0fd676594a
commit 744bc44de2
22 changed files with 83 additions and 3141 deletions
-1
View File
@@ -8329,7 +8329,6 @@ dependencies = [
"sc-executor",
"sc-informant",
"sc-keystore",
"sc-light",
"sc-network",
"sc-offchain",
"sc-rpc",
@@ -4,7 +4,7 @@ use crate::{
service,
};
use node_template_runtime::Block;
use sc_cli::{ChainSpec, Role, RuntimeVersion, SubstrateCli};
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
use sc_service::PartialComponents;
impl SubstrateCli for Cli {
@@ -111,11 +111,7 @@ pub fn run() -> sc_cli::Result<()> {
None => {
let runner = cli.create_runner(&cli.run)?;
runner.run_node_until_exit(|config| async move {
match config.role {
Role::Light => service::new_light(config),
_ => service::new_full(config),
}
.map_err(sc_cli::Error::Service)
service::new_full(config).map_err(sc_cli::Error::Service)
})
},
}
+1 -142
View File
@@ -1,7 +1,7 @@
//! Service and ServiceFactory implementation. Specialized wrapper over substrate service.
use node_template_runtime::{self, opaque::Block, RuntimeApi};
use sc_client_api::{ExecutorProvider, RemoteBackend};
use sc_client_api::ExecutorProvider;
use sc_consensus_aura::{ImportQueueParams, SlotProportion, StartAuraParams};
pub use sc_executor::NativeElseWasmExecutor;
use sc_finality_grandpa::SharedVoterState;
@@ -336,144 +336,3 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
network_starter.start_network();
Ok(task_manager)
}
/// Builds a new service for a light client.
pub fn new_light(mut config: Configuration) -> Result<TaskManager, ServiceError> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
let worker = TelemetryWorker::new(16)?;
let telemetry = worker.handle().new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let executor = NativeElseWasmExecutor::<ExecutorDispatch>::new(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
);
let (client, backend, keystore_container, mut task_manager, on_demand) =
sc_service::new_light_parts::<Block, RuntimeApi, _>(
&config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let mut telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
telemetry
});
config.network.extra_sets.push(sc_finality_grandpa::grandpa_peers_set_config());
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = Arc::new(sc_transaction_pool::BasicPool::new_light(
config.transaction_pool.clone(),
config.prometheus_registry(),
task_manager.spawn_essential_handle(),
client.clone(),
on_demand.clone(),
));
let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import(
client.clone(),
&(client.clone() as Arc<_>),
select_chain.clone(),
telemetry.as_ref().map(|x| x.handle()),
)?;
let slot_duration = sc_consensus_aura::slot_duration(&*client)?.slot_duration();
let import_queue =
sc_consensus_aura::import_queue::<AuraPair, _, _, _, _, _, _>(ImportQueueParams {
block_import: grandpa_block_import.clone(),
justification_import: Some(Box::new(grandpa_block_import.clone())),
client: client.clone(),
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_duration(
*timestamp,
slot_duration,
);
Ok((timestamp, slot))
},
spawner: &task_manager.spawn_essential_handle(),
can_author_with: sp_consensus::NeverCanAuthor,
registry: config.prometheus_registry(),
check_for_equivocation: Default::default(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
})?;
let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
Vec::default(),
));
let (network, system_rpc_tx, network_starter) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
on_demand: Some(on_demand.clone()),
block_announce_validator_builder: None,
warp_sync: Some(warp_sync),
})?;
if config.offchain_worker.enabled {
sc_service::build_offchain_workers(
&config,
task_manager.spawn_handle(),
client.clone(),
network.clone(),
);
}
let enable_grandpa = !config.disable_grandpa;
if enable_grandpa {
let name = config.network.node_name.clone();
let config = sc_finality_grandpa::Config {
gossip_duration: std::time::Duration::from_millis(333),
justification_period: 512,
name: Some(name),
observer_enabled: false,
keystore: None,
local_role: config.role.clone(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
};
task_manager.spawn_handle().spawn_blocking(
"grandpa-observer",
sc_finality_grandpa::run_grandpa_observer(config, grandpa_link, network.clone())?,
);
}
sc_service::spawn_tasks(sc_service::SpawnTasksParams {
remote_blockchain: Some(backend.remote_blockchain()),
transaction_pool,
task_manager: &mut task_manager,
on_demand: Some(on_demand),
rpc_extensions_builder: Box::new(|_, _| Ok(())),
config,
client,
keystore: keystore_container.sync_keystore(),
backend,
network,
system_rpc_tx,
telemetry: telemetry.as_mut(),
})?;
network_starter.start_network();
Ok(task_manager)
}
+11 -23
View File
@@ -418,7 +418,7 @@ pub fn local_testnet_config() -> ChainSpec {
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::service::{new_full_base, new_light_base, NewFullBase};
use crate::service::{new_full_base, NewFullBase};
use sc_service_test;
use sp_runtime::BuildStorage;
@@ -466,28 +466,16 @@ pub(crate) mod tests {
fn test_connectivity() {
sp_tracing::try_init_simple();
sc_service_test::connectivity(
integration_test_config_with_two_authorities(),
|config| {
let NewFullBase { task_manager, client, network, transaction_pool, .. } =
new_full_base(config, |_, _| ())?;
Ok(sc_service_test::TestNetComponents::new(
task_manager,
client,
network,
transaction_pool,
))
},
|config| {
let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?;
Ok(sc_service_test::TestNetComponents::new(
keep_alive,
client,
network,
transaction_pool,
))
},
);
sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| {
let NewFullBase { task_manager, client, network, transaction_pool, .. } =
new_full_base(config, |_, _| ())?;
Ok(sc_service_test::TestNetComponents::new(
task_manager,
client,
network,
transaction_pool,
))
});
}
#[test]
+2 -6
View File
@@ -19,7 +19,7 @@
use crate::{chain_spec, service, service::new_partial, Cli, Subcommand};
use node_executor::ExecutorDispatch;
use node_runtime::{Block, RuntimeApi};
use sc_cli::{ChainSpec, Result, Role, RuntimeVersion, SubstrateCli};
use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli};
use sc_service::PartialComponents;
impl SubstrateCli for Cli {
@@ -77,11 +77,7 @@ pub fn run() -> Result<()> {
None => {
let runner = cli.create_runner(&cli.run)?;
runner.run_node_until_exit(|config| async move {
match config.role {
Role::Light => service::new_light(config),
_ => service::new_full(config),
}
.map_err(sc_cli::Error::Service)
service::new_full(config).map_err(sc_cli::Error::Service)
})
},
Some(Subcommand::Inspect(cmd)) => {
+4 -200
View File
@@ -26,11 +26,11 @@ use futures::prelude::*;
use node_executor::ExecutorDispatch;
use node_primitives::Block;
use node_runtime::RuntimeApi;
use sc_client_api::{BlockBackend, ExecutorProvider, RemoteBackend};
use sc_client_api::{BlockBackend, ExecutorProvider};
use sc_consensus_babe::{self, SlotProportion};
use sc_executor::NativeElseWasmExecutor;
use sc_network::{Event, NetworkService};
use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager};
use sc_service::{config::Configuration, error::Error as ServiceError, TaskManager};
use sc_telemetry::{Telemetry, TelemetryWorker};
use sp_api::ProvideRuntimeApi;
use sp_core::crypto::Pair;
@@ -44,8 +44,7 @@ type FullBackend = sc_service::TFullBackend<Block>;
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
type FullGrandpaBlockImport =
grandpa::GrandpaBlockImport<FullBackend, Block, FullClient, FullSelectChain>;
type LightClient =
sc_service::TLightClient<Block, RuntimeApi, NativeElseWasmExecutor<ExecutorDispatch>>;
/// The transaction pool type defintion.
pub type TransactionPool = sc_transaction_pool::FullPool<Block, FullClient>;
@@ -516,186 +515,9 @@ pub fn new_full(config: Configuration) -> Result<TaskManager, ServiceError> {
new_full_base(config, |_, _| ()).map(|NewFullBase { task_manager, .. }| task_manager)
}
/// Creates a light service from the configuration.
pub fn new_light_base(
mut config: Configuration,
) -> Result<
(
TaskManager,
RpcHandlers,
Arc<LightClient>,
Arc<NetworkService<Block, <Block as BlockT>::Hash>>,
Arc<
sc_transaction_pool::LightPool<Block, LightClient, sc_network::config::OnDemand<Block>>,
>,
),
ServiceError,
> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(|endpoints| -> Result<_, sc_telemetry::Error> {
let worker = TelemetryWorker::new(16)?;
let telemetry = worker.handle().new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let executor = NativeElseWasmExecutor::<ExecutorDispatch>::new(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
);
let (client, backend, keystore_container, mut task_manager, on_demand) =
sc_service::new_light_parts::<Block, RuntimeApi, _>(
&config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let mut telemetry = telemetry.map(|(worker, telemetry)| {
task_manager.spawn_handle().spawn("telemetry", worker.run());
telemetry
});
config.network.extra_sets.push(grandpa::grandpa_peers_set_config());
let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = Arc::new(sc_transaction_pool::BasicPool::new_light(
config.transaction_pool.clone(),
config.prometheus_registry(),
task_manager.spawn_essential_handle(),
client.clone(),
on_demand.clone(),
));
let (grandpa_block_import, grandpa_link) = grandpa::block_import(
client.clone(),
&(client.clone() as Arc<_>),
select_chain.clone(),
telemetry.as_ref().map(|x| x.handle()),
)?;
let justification_import = grandpa_block_import.clone();
let (babe_block_import, babe_link) = sc_consensus_babe::block_import(
sc_consensus_babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
)?;
let slot_duration = babe_link.config().slot_duration();
let import_queue = sc_consensus_babe::import_queue(
babe_link,
babe_block_import,
Some(Box::new(justification_import)),
client.clone(),
select_chain,
move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_duration(
*timestamp,
slot_duration,
);
let uncles =
sp_authorship::InherentDataProvider::<<Block as BlockT>::Header>::check_inherents();
Ok((timestamp, slot, uncles))
},
&task_manager.spawn_essential_handle(),
config.prometheus_registry(),
sp_consensus::NeverCanAuthor,
telemetry.as_ref().map(|x| x.handle()),
)?;
let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
grandpa_link.shared_authority_set().clone(),
Vec::default(),
));
let (network, system_rpc_tx, network_starter) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
on_demand: Some(on_demand.clone()),
block_announce_validator_builder: None,
warp_sync: Some(warp_sync),
})?;
let enable_grandpa = !config.disable_grandpa;
if enable_grandpa {
let name = config.network.node_name.clone();
let config = grandpa::Config {
gossip_duration: std::time::Duration::from_millis(333),
justification_period: 512,
name: Some(name),
observer_enabled: false,
keystore: None,
local_role: config.role.clone(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
};
task_manager.spawn_handle().spawn_blocking(
"grandpa-observer",
grandpa::run_grandpa_observer(config, grandpa_link, network.clone())?,
);
}
if config.offchain_worker.enabled {
sc_service::build_offchain_workers(
&config,
task_manager.spawn_handle(),
client.clone(),
network.clone(),
);
}
let light_deps = node_rpc::LightDeps {
remote_blockchain: backend.remote_blockchain(),
fetcher: on_demand.clone(),
client: client.clone(),
pool: transaction_pool.clone(),
};
let rpc_extensions = node_rpc::create_light(light_deps);
let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
on_demand: Some(on_demand),
remote_blockchain: Some(backend.remote_blockchain()),
rpc_extensions_builder: Box::new(sc_service::NoopRpcExtensionBuilder(rpc_extensions)),
client: client.clone(),
transaction_pool: transaction_pool.clone(),
keystore: keystore_container.sync_keystore(),
config,
backend,
system_rpc_tx,
network: network.clone(),
task_manager: &mut task_manager,
telemetry: telemetry.as_mut(),
})?;
network_starter.start_network();
Ok((task_manager, rpc_handlers, client, network, transaction_pool))
}
/// Builds a new service for a light client.
pub fn new_light(config: Configuration) -> Result<TaskManager, ServiceError> {
new_light_base(config).map(|(task_manager, _, _, _, _)| task_manager)
}
#[cfg(test)]
mod tests {
use crate::service::{new_full_base, new_light_base, NewFullBase};
use crate::service::{new_full_base, NewFullBase};
use codec::Encode;
use node_primitives::{Block, DigestItem, Signature};
use node_runtime::{
@@ -771,15 +593,6 @@ mod tests {
);
Ok((node, setup_handles.unwrap()))
},
|config| {
let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?;
Ok(sc_service_test::TestNetComponents::new(
keep_alive,
client,
network,
transaction_pool,
))
},
|service, &mut (ref mut block_import, ref babe_link)| {
let parent_id = BlockId::number(service.client().chain_info().best_number);
let parent_header = service.client().header(&parent_id).unwrap().unwrap();
@@ -946,15 +759,6 @@ mod tests {
transaction_pool,
))
},
|config| {
let (keep_alive, _, client, network, transaction_pool) = new_light_base(config)?;
Ok(sc_service_test::TestNetComponents::new(
keep_alive,
client,
network,
transaction_pool,
))
},
vec!["//Alice".into(), "//Bob".into()],
)
}
@@ -1,116 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use sc_client_db::{
light::LightStorage, DatabaseSettings, DatabaseSource, KeepBlocks, PruningMode,
TransactionStorageMode,
};
use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper};
use tempfile::tempdir;
pub mod common;
#[tokio::test]
#[cfg(unix)]
async fn database_role_subdir_migration() {
type Block = RawBlock<ExtrinsicWrapper<u64>>;
let base_path = tempdir().expect("could not create a temp dir");
let path = base_path.path().join("chains/dev/db");
// create a dummy database dir
{
let _old_db = LightStorage::<Block>::new(DatabaseSettings {
state_cache_size: 0,
state_cache_child_ratio: None,
state_pruning: PruningMode::ArchiveAll,
source: DatabaseSource::RocksDb { path: path.to_path_buf(), cache_size: 128 },
keep_blocks: KeepBlocks::All,
transaction_storage: TransactionStorageMode::BlockBody,
})
.unwrap();
}
assert!(path.join("db_version").exists());
assert!(!path.join("light").exists());
// start a light client
common::run_node_for_a_while(
base_path.path(),
&[
"--dev",
"--light",
"--port",
"30335",
"--rpc-port",
"44444",
"--ws-port",
"44445",
"--no-prometheus",
],
)
.await;
// check if the database dir had been migrated
assert!(!path.join("db_version").exists());
assert!(path.join("light/db_version").exists());
}
#[test]
#[cfg(unix)]
fn database_role_subdir_migration_fail_on_different_role() {
type Block = RawBlock<ExtrinsicWrapper<u64>>;
let base_path = tempdir().expect("could not create a temp dir");
let path = base_path.path().join("chains/dev/db");
// create a database with the old layout
{
let _old_db = LightStorage::<Block>::new(DatabaseSettings {
state_cache_size: 0,
state_cache_child_ratio: None,
state_pruning: PruningMode::ArchiveAll,
source: DatabaseSource::RocksDb { path: path.to_path_buf(), cache_size: 128 },
keep_blocks: KeepBlocks::All,
transaction_storage: TransactionStorageMode::BlockBody,
})
.unwrap();
}
assert!(path.join("db_version").exists());
assert!(!path.join("light/db_version").exists());
// start a client with a different role (full), it should fail and not change any files on disk
common::run_node_assert_fail(
&base_path.path(),
&[
"--dev",
"--port",
"30334",
"--rpc-port",
"44446",
"--ws-port",
"44447",
"--no-prometheus",
],
);
// check if the files are unchanged
assert!(path.join("db_version").exists());
assert!(!path.join("light/db_version").exists());
assert!(!path.join("full/db_version").exists());
}
+1 -1
View File
@@ -27,7 +27,7 @@ use sp_runtime::{
Justifications,
};
use crate::fetcher::RemoteHeaderRequest;
use sc_client_api::light::RemoteHeaderRequest;
pub use sc_client_api::{
backend::{AuxStore, NewBlockState, ProvideChtRoots},
blockchain::{
-366
View File
@@ -1,366 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Light client data fetcher. Fetches requested data from remote full nodes.
use std::{
collections::{BTreeMap, HashMap},
marker::PhantomData,
sync::Arc,
};
use codec::{Decode, Encode};
use hash_db::{HashDB, Hasher, EMPTY_PREFIX};
use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_core::{
convert_hash,
storage::{ChildInfo, ChildType},
traits::{CodeExecutor, SpawnNamed},
};
use sp_runtime::traits::{
AtLeast32Bit, Block as BlockT, CheckedConversion, Hash, HashFor, Header as HeaderT, NumberFor,
};
pub use sp_state_machine::StorageProof;
use sp_state_machine::{
key_changes_proof_check_with_db, read_child_proof_check, read_proof_check,
ChangesTrieAnchorBlockId, ChangesTrieConfigurationRange, ChangesTrieRootsStorage,
InMemoryChangesTrieStorage, TrieBackend,
};
use crate::{blockchain::Blockchain, call_executor::check_execution_proof};
pub use sc_client_api::{
cht,
light::{
ChangesProof, FetchChecker, Fetcher, RemoteBodyRequest, RemoteCallRequest,
RemoteChangesRequest, RemoteHeaderRequest, RemoteReadChildRequest, RemoteReadRequest,
Storage as BlockchainStorage,
},
};
/// Remote data checker.
pub struct LightDataChecker<E, B: BlockT, S: BlockchainStorage<B>> {
blockchain: Arc<Blockchain<S>>,
executor: E,
spawn_handle: Box<dyn SpawnNamed>,
_marker: PhantomData<B>,
}
impl<E, B: BlockT, S: BlockchainStorage<B>> LightDataChecker<E, B, S> {
/// Create new light data checker.
pub fn new(
blockchain: Arc<Blockchain<S>>,
executor: E,
spawn_handle: Box<dyn SpawnNamed>,
) -> Self {
Self { blockchain, executor, spawn_handle, _marker: PhantomData }
}
/// Check remote changes query proof assuming that CHT-s are of given size.
pub fn check_changes_proof_with_cht_size(
&self,
request: &RemoteChangesRequest<B::Header>,
remote_proof: ChangesProof<B::Header>,
cht_size: NumberFor<B>,
) -> ClientResult<Vec<(NumberFor<B>, u32)>> {
// since we need roots of all changes tries for the range begin..max
// => remote node can't use max block greater that one that we have passed
if remote_proof.max_block > request.max_block.0 ||
remote_proof.max_block < request.last_block.0
{
return Err(ClientError::ChangesTrieAccessFailed(format!(
"Invalid max_block used by the remote node: {}. Local: {}..{}..{}",
remote_proof.max_block,
request.first_block.0,
request.last_block.0,
request.max_block.0,
))
.into())
}
// check if remote node has responded with extra changes trie roots proofs
// all changes tries roots must be in range [request.first_block.0; request.tries_roots.0)
let is_extra_first_root = remote_proof
.roots
.keys()
.next()
.map(|first_root| {
*first_root < request.first_block.0 || *first_root >= request.tries_roots.0
})
.unwrap_or(false);
let is_extra_last_root = remote_proof
.roots
.keys()
.next_back()
.map(|last_root| *last_root >= request.tries_roots.0)
.unwrap_or(false);
if is_extra_first_root || is_extra_last_root {
return Err(ClientError::ChangesTrieAccessFailed(format!(
"Extra changes tries roots proofs provided by the remote node: [{:?}..{:?}]. Expected in range: [{}; {})",
remote_proof.roots.keys().next(), remote_proof.roots.keys().next_back(),
request.first_block.0, request.tries_roots.0,
)).into());
}
// if request has been composed when some required headers were already pruned
// => remote node has sent us CHT-based proof of required changes tries roots
// => check that this proof is correct before proceeding with changes proof
let remote_max_block = remote_proof.max_block;
let remote_roots = remote_proof.roots;
let remote_roots_proof = remote_proof.roots_proof;
let remote_proof = remote_proof.proof;
if !remote_roots.is_empty() {
self.check_changes_tries_proof(cht_size, &remote_roots, remote_roots_proof)?;
}
// and now check the key changes proof + get the changes
let mut result = Vec::new();
let proof_storage = InMemoryChangesTrieStorage::with_proof(remote_proof);
for config_range in &request.changes_trie_configs {
let result_range = key_changes_proof_check_with_db::<HashFor<B>, _>(
ChangesTrieConfigurationRange {
config: config_range
.config
.as_ref()
.ok_or(ClientError::ChangesTriesNotSupported)?,
zero: config_range.zero.0,
end: config_range.end.map(|(n, _)| n),
},
&RootsStorage {
roots: (request.tries_roots.0, &request.tries_roots.2),
prev_roots: &remote_roots,
},
&proof_storage,
request.first_block.0,
&ChangesTrieAnchorBlockId {
hash: convert_hash(&request.last_block.1),
number: request.last_block.0,
},
remote_max_block,
request.storage_key.as_ref(),
&request.key,
)
.map_err(|err| ClientError::ChangesTrieAccessFailed(err))?;
result.extend(result_range);
}
Ok(result)
}
/// Check CHT-based proof for changes tries roots.
pub fn check_changes_tries_proof(
&self,
cht_size: NumberFor<B>,
remote_roots: &BTreeMap<NumberFor<B>, B::Hash>,
remote_roots_proof: StorageProof,
) -> ClientResult<()> {
// all the checks are sharing the same storage
let storage = remote_roots_proof.into_memory_db();
// remote_roots.keys() are sorted => we can use this to group changes tries roots
// that are belongs to the same CHT
let blocks = remote_roots.keys().cloned();
cht::for_each_cht_group::<B::Header, _, _, _>(
cht_size,
blocks,
|mut storage, _, cht_blocks| {
// get local changes trie CHT root for given CHT
// it should be there, because it is never pruned AND request has been composed
// when required header has been pruned (=> replaced with CHT)
let first_block = cht_blocks
.first()
.cloned()
.expect("for_each_cht_group never calls callback with empty groups");
let local_cht_root = self
.blockchain
.storage()
.changes_trie_cht_root(cht_size, first_block)?
.ok_or(ClientError::InvalidCHTProof)?;
// check changes trie root for every block within CHT range
for block in cht_blocks {
// check if the proofs storage contains the root
// normally this happens in when the proving backend is created, but since
// we share the storage for multiple checks, do it here
if !storage.contains(&local_cht_root, EMPTY_PREFIX) {
return Err(ClientError::InvalidCHTProof.into())
}
// check proof for single changes trie root
let proving_backend = TrieBackend::new(storage, local_cht_root);
let remote_changes_trie_root = remote_roots[&block];
cht::check_proof_on_proving_backend::<B::Header, HashFor<B>>(
local_cht_root,
block,
remote_changes_trie_root,
&proving_backend,
)?;
// and return the storage to use in following checks
storage = proving_backend.into_storage();
}
Ok(storage)
},
storage,
)
}
}
impl<E, Block, S> FetchChecker<Block> for LightDataChecker<E, Block, S>
where
Block: BlockT,
E: CodeExecutor + Clone + 'static,
S: BlockchainStorage<Block>,
{
fn check_header_proof(
&self,
request: &RemoteHeaderRequest<Block::Header>,
remote_header: Option<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<Block::Header> {
let remote_header =
remote_header.ok_or_else(|| ClientError::from(ClientError::InvalidCHTProof))?;
let remote_header_hash = remote_header.hash();
cht::check_proof::<Block::Header, HashFor<Block>>(
request.cht_root,
request.block,
remote_header_hash,
remote_proof,
)
.map(|_| remote_header)
}
fn check_read_proof(
&self,
request: &RemoteReadRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
read_proof_check::<HashFor<Block>, _>(
convert_hash(request.header.state_root()),
remote_proof,
request.keys.iter(),
)
.map_err(|e| ClientError::from(e))
}
fn check_read_child_proof(
&self,
request: &RemoteReadChildRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<HashMap<Vec<u8>, Option<Vec<u8>>>> {
let child_info = match ChildType::from_prefixed_key(&request.storage_key) {
Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key),
None => return Err(ClientError::InvalidChildType),
};
read_child_proof_check::<HashFor<Block>, _>(
convert_hash(request.header.state_root()),
remote_proof,
&child_info,
request.keys.iter(),
)
.map_err(|e| ClientError::from(e))
}
fn check_execution_proof(
&self,
request: &RemoteCallRequest<Block::Header>,
remote_proof: StorageProof,
) -> ClientResult<Vec<u8>> {
check_execution_proof::<_, _, HashFor<Block>>(
&self.executor,
self.spawn_handle.clone(),
request,
remote_proof,
)
}
fn check_changes_proof(
&self,
request: &RemoteChangesRequest<Block::Header>,
remote_proof: ChangesProof<Block::Header>,
) -> ClientResult<Vec<(NumberFor<Block>, u32)>> {
self.check_changes_proof_with_cht_size(request, remote_proof, cht::size())
}
fn check_body_proof(
&self,
request: &RemoteBodyRequest<Block::Header>,
body: Vec<Block::Extrinsic>,
) -> ClientResult<Vec<Block::Extrinsic>> {
// TODO: #2621
let extrinsics_root =
HashFor::<Block>::ordered_trie_root(body.iter().map(Encode::encode).collect());
if *request.header.extrinsics_root() == extrinsics_root {
Ok(body)
} else {
Err(ClientError::ExtrinsicRootInvalid {
received: request.header.extrinsics_root().to_string(),
expected: extrinsics_root.to_string(),
})
}
}
}
/// A view of BTreeMap<Number, Hash> as a changes trie roots storage.
struct RootsStorage<'a, Number: AtLeast32Bit, Hash: 'a> {
roots: (Number, &'a [Hash]),
prev_roots: &'a BTreeMap<Number, Hash>,
}
impl<'a, H, Number, Hash> ChangesTrieRootsStorage<H, Number> for RootsStorage<'a, Number, Hash>
where
H: Hasher,
Number: std::fmt::Display
+ std::hash::Hash
+ Clone
+ AtLeast32Bit
+ Encode
+ Decode
+ Send
+ Sync
+ 'static,
Hash: 'a + Send + Sync + Clone + AsRef<[u8]>,
{
fn build_anchor(
&self,
_hash: H::Out,
) -> Result<sp_state_machine::ChangesTrieAnchorBlockId<H::Out, Number>, String> {
Err("build_anchor is only called when building block".into())
}
fn root(
&self,
_anchor: &ChangesTrieAnchorBlockId<H::Out, Number>,
block: Number,
) -> Result<Option<H::Out>, String> {
// we can't ask for roots from parallel forks here => ignore anchor
let root = if block < self.roots.0 {
self.prev_roots.get(&Number::unique_saturated_from(block)).cloned()
} else {
let index: Option<usize> =
block.checked_sub(&self.roots.0).and_then(|index| index.checked_into());
index.and_then(|index| self.roots.1.get(index as usize).cloned())
};
Ok(root.map(|root| {
let mut hasher_root: H::Out = Default::default();
hasher_root.as_mut().copy_from_slice(root.as_ref());
hasher_root
}))
}
}
+1 -19
View File
@@ -18,36 +18,18 @@
//! Light client components.
use sp_core::traits::{CodeExecutor, SpawnNamed};
use sp_runtime::traits::{Block as BlockT, HashFor};
use std::sync::Arc;
pub mod backend;
pub mod blockchain;
pub mod call_executor;
pub mod fetcher;
pub use backend::*;
pub use blockchain::*;
pub use call_executor::*;
pub use fetcher::*;
/// Create an instance of fetch data checker.
pub fn new_fetch_checker<E, B: BlockT, S: BlockchainStorage<B>>(
blockchain: Arc<Blockchain<S>>,
executor: E,
spawn_handle: Box<dyn SpawnNamed>,
) -> LightDataChecker<E, B, S>
where
E: CodeExecutor,
{
LightDataChecker::new(blockchain, executor, spawn_handle)
}
/// Create an instance of light client blockchain backend.
pub fn new_light_blockchain<B: BlockT, S: BlockchainStorage<B>>(storage: S) -> Arc<Blockchain<S>> {
Arc::new(Blockchain::new(storage))
}
use sc_client_api::light::Storage as BlockchainStorage;
/// Create an instance of light client backend.
pub fn new_light_backend<B, S>(blockchain: Arc<Blockchain<S>>) -> Arc<Backend<S, HashFor<B>>>
-41
View File
@@ -19,7 +19,6 @@
//! Substrate state API.
mod state_full;
mod state_light;
#[cfg(test)]
mod tests;
@@ -29,7 +28,6 @@ use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, Subscripti
use rpc::Result as RpcResult;
use std::sync::Arc;
use sc_client_api::light::{Fetcher, RemoteBlockchain};
use sc_rpc_api::{state::ReadProof, DenyUnsafe};
use sp_core::{
storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey},
@@ -217,45 +215,6 @@ where
(State { backend, deny_unsafe }, ChildState { backend: child_backend })
}
/// Create new state API that works on light node.
pub fn new_light<BE, Block: BlockT, Client, F: Fetcher<Block>>(
client: Arc<Client>,
subscriptions: SubscriptionManager,
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
fetcher: Arc<F>,
deny_unsafe: DenyUnsafe,
) -> (State<Block, Client>, ChildState<Block, Client>)
where
Block: BlockT + 'static,
Block::Hash: Unpin,
BE: Backend<Block> + 'static,
Client: ExecutorProvider<Block>
+ StorageProvider<Block, BE>
+ HeaderMetadata<Block, Error = sp_blockchain::Error>
+ ProvideRuntimeApi<Block>
+ HeaderBackend<Block>
+ BlockchainEvents<Block>
+ Send
+ Sync
+ 'static,
F: Send + Sync + 'static,
{
let child_backend = Box::new(self::state_light::LightState::new(
client.clone(),
subscriptions.clone(),
remote_blockchain.clone(),
fetcher.clone(),
));
let backend = Box::new(self::state_light::LightState::new(
client,
subscriptions,
remote_blockchain,
fetcher,
));
(State { backend, deny_unsafe }, ChildState { backend: child_backend })
}
/// State API with subscriptions support.
pub struct State<Block, Client> {
backend: Box<dyn StateBackend<Block, Client>>,
@@ -1,873 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! State API backend for light nodes.
use codec::Decode;
use futures::{
channel::oneshot::{channel, Sender},
future::{ready, Either},
Future, FutureExt, SinkExt, Stream, StreamExt as _, TryFutureExt, TryStreamExt as _,
};
use hash_db::Hasher;
use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId};
use log::warn;
use parking_lot::Mutex;
use rpc::Result as RpcResult;
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
sync::Arc,
};
use sc_client_api::{
light::{
future_header, Fetcher, RemoteBlockchain, RemoteCallRequest, RemoteReadChildRequest,
RemoteReadRequest,
},
BlockchainEvents,
};
use sc_rpc_api::state::ReadProof;
use sp_blockchain::{Error as ClientError, HeaderBackend};
use sp_core::{
storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey},
Bytes, OpaqueMetadata,
};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, HashFor},
};
use sp_version::RuntimeVersion;
use super::{
client_err,
error::{Error, FutureResult},
ChildStateBackend, StateBackend,
};
/// Storage data map of storage keys => (optional) storage value.
type StorageMap = HashMap<StorageKey, Option<StorageData>>;
/// State API backend for light nodes.
#[derive(Clone)]
pub struct LightState<Block: BlockT, F: Fetcher<Block>, Client> {
client: Arc<Client>,
subscriptions: SubscriptionManager,
version_subscriptions: SimpleSubscriptions<Block::Hash, RuntimeVersion>,
storage_subscriptions: Arc<Mutex<StorageSubscriptions<Block>>>,
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
fetcher: Arc<F>,
}
/// Shared requests container.
trait SharedRequests<Hash, V>: Clone + Send + Sync {
/// Tries to listen for already issued request, or issues request.
///
/// Returns true if requests has been issued.
fn listen_request(&self, block: Hash, sender: Sender<Result<V, ()>>) -> bool;
/// Returns (and forgets) all listeners for given request.
fn on_response_received(&self, block: Hash) -> Vec<Sender<Result<V, ()>>>;
}
/// Storage subscriptions data.
struct StorageSubscriptions<Block: BlockT> {
/// Active storage requests.
active_requests: HashMap<Block::Hash, Vec<Sender<Result<StorageMap, ()>>>>,
/// Map of subscription => keys that this subscription watch for.
keys_by_subscription: HashMap<SubscriptionId, HashSet<StorageKey>>,
/// Map of key => set of subscriptions that watch this key.
subscriptions_by_key: HashMap<StorageKey, HashSet<SubscriptionId>>,
}
impl<Block: BlockT> SharedRequests<Block::Hash, StorageMap>
for Arc<Mutex<StorageSubscriptions<Block>>>
{
fn listen_request(&self, block: Block::Hash, sender: Sender<Result<StorageMap, ()>>) -> bool {
let mut subscriptions = self.lock();
let active_requests_at = subscriptions.active_requests.entry(block).or_default();
active_requests_at.push(sender);
active_requests_at.len() == 1
}
fn on_response_received(&self, block: Block::Hash) -> Vec<Sender<Result<StorageMap, ()>>> {
self.lock().active_requests.remove(&block).unwrap_or_default()
}
}
/// Simple, maybe shared, subscription data that shares per block requests.
type SimpleSubscriptions<Hash, V> = Arc<Mutex<HashMap<Hash, Vec<Sender<Result<V, ()>>>>>>;
impl<Hash, V> SharedRequests<Hash, V> for SimpleSubscriptions<Hash, V>
where
Hash: Send + Eq + std::hash::Hash,
V: Send,
{
fn listen_request(&self, block: Hash, sender: Sender<Result<V, ()>>) -> bool {
let mut subscriptions = self.lock();
let active_requests_at = subscriptions.entry(block).or_default();
active_requests_at.push(sender);
active_requests_at.len() == 1
}
fn on_response_received(&self, block: Hash) -> Vec<Sender<Result<V, ()>>> {
self.lock().remove(&block).unwrap_or_default()
}
}
impl<Block: BlockT, F: Fetcher<Block> + 'static, Client> LightState<Block, F, Client>
where
Block: BlockT,
Client: HeaderBackend<Block> + Send + Sync + 'static,
{
/// Create new state API backend for light nodes.
pub fn new(
client: Arc<Client>,
subscriptions: SubscriptionManager,
remote_blockchain: Arc<dyn RemoteBlockchain<Block>>,
fetcher: Arc<F>,
) -> Self {
Self {
client,
subscriptions,
version_subscriptions: Arc::new(Mutex::new(HashMap::new())),
storage_subscriptions: Arc::new(Mutex::new(StorageSubscriptions {
active_requests: HashMap::new(),
keys_by_subscription: HashMap::new(),
subscriptions_by_key: HashMap::new(),
})),
remote_blockchain,
fetcher,
}
}
/// Returns given block hash or best block hash if None is passed.
fn block_or_best(&self, hash: Option<Block::Hash>) -> Block::Hash {
hash.unwrap_or_else(|| self.client.info().best_hash)
}
}
impl<Block, F, Client> StateBackend<Block, Client> for LightState<Block, F, Client>
where
Block: BlockT,
Block::Hash: Unpin,
Client: BlockchainEvents<Block> + HeaderBackend<Block> + Send + Sync + 'static,
F: Fetcher<Block> + 'static,
{
fn call(
&self,
block: Option<Block::Hash>,
method: String,
call_data: Bytes,
) -> FutureResult<Bytes> {
call(
&*self.remote_blockchain,
self.fetcher.clone(),
self.block_or_best(block),
method,
call_data,
)
.boxed()
}
fn storage_keys(
&self,
_block: Option<Block::Hash>,
_prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage_pairs(
&self,
_block: Option<Block::Hash>,
_prefix: StorageKey,
) -> FutureResult<Vec<(StorageKey, StorageData)>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage_keys_paged(
&self,
_block: Option<Block::Hash>,
_prefix: Option<StorageKey>,
_count: u32,
_start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage_size(&self, _: Option<Block::Hash>, _: StorageKey) -> FutureResult<Option<u64>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<StorageData>> {
storage(
&*self.remote_blockchain,
self.fetcher.clone(),
self.block_or_best(block),
vec![key.0.clone()],
)
.map_ok(move |mut values| {
values
.remove(&key)
.expect("successful request has entries for all requested keys; qed")
})
.boxed()
}
fn storage_hash(
&self,
block: Option<Block::Hash>,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>> {
let res = StateBackend::storage(self, block, key);
async move { res.await.map(|r| r.map(|s| HashFor::<Block>::hash(&s.0))) }.boxed()
}
fn metadata(&self, block: Option<Block::Hash>) -> FutureResult<Bytes> {
self.call(block, "Metadata_metadata".into(), Bytes(Vec::new()))
.and_then(|metadata| async move {
OpaqueMetadata::decode(&mut &metadata.0[..])
.map(Into::into)
.map_err(|decode_err| {
client_err(ClientError::CallResultDecode(
"Unable to decode metadata",
decode_err,
))
})
})
.boxed()
}
fn runtime_version(&self, block: Option<Block::Hash>) -> FutureResult<RuntimeVersion> {
runtime_version(&*self.remote_blockchain, self.fetcher.clone(), self.block_or_best(block))
.boxed()
}
fn query_storage(
&self,
_from: Block::Hash,
_to: Option<Block::Hash>,
_keys: Vec<StorageKey>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn query_storage_at(
&self,
_keys: Vec<StorageKey>,
_at: Option<Block::Hash>,
) -> FutureResult<Vec<StorageChangeSet<Block::Hash>>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn read_proof(
&self,
_block: Option<Block::Hash>,
_keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn subscribe_storage(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<StorageChangeSet<Block::Hash>>,
keys: Option<Vec<StorageKey>>,
) {
let keys = match keys {
Some(keys) if !keys.is_empty() => keys,
_ => {
warn!("Cannot subscribe to all keys on light client. Subscription rejected.");
return
},
};
let keys = keys.iter().cloned().collect::<HashSet<_>>();
let keys_to_check = keys.iter().map(|k| k.0.clone()).collect::<HashSet<_>>();
let subscription_id = self.subscriptions.add(subscriber, move |sink| {
let fetcher = self.fetcher.clone();
let remote_blockchain = self.remote_blockchain.clone();
let storage_subscriptions = self.storage_subscriptions.clone();
let initial_block = self.block_or_best(None);
let initial_keys = keys_to_check.iter().cloned().collect::<Vec<_>>();
let changes_stream = subscription_stream::<Block, _, _, _, _, _, _, _, _>(
storage_subscriptions.clone(),
self.client.import_notification_stream().map(|notification| notification.hash),
display_error(
storage(&*remote_blockchain, fetcher.clone(), initial_block, initial_keys)
.map(move |r| r.map(|r| (initial_block, r))),
),
move |block| {
// there'll be single request per block for all active subscriptions
// with all subscribed keys
let keys = storage_subscriptions
.lock()
.subscriptions_by_key
.keys()
.map(|k| k.0.clone())
.collect();
storage(&*remote_blockchain, fetcher.clone(), block, keys)
},
move |block, old_value, new_value| {
// let's only select keys which are valid for this subscription
let new_value = new_value
.iter()
.filter(|(k, _)| keys_to_check.contains(&k.0))
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<HashMap<_, _>>();
let value_differs = old_value
.as_ref()
.map(|old_value| **old_value != new_value)
.unwrap_or(true);
value_differs.then(|| StorageChangeSet {
block,
changes: new_value.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
})
},
);
changes_stream
.map_ok(Ok)
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
// we ignore the resulting Stream (if the first stream is over we are unsubscribed)
.map(|_| ())
});
// remember keys associated with this subscription
let mut storage_subscriptions = self.storage_subscriptions.lock();
storage_subscriptions
.keys_by_subscription
.insert(subscription_id.clone(), keys.clone());
for key in keys {
storage_subscriptions
.subscriptions_by_key
.entry(key)
.or_default()
.insert(subscription_id.clone());
}
}
fn unsubscribe_storage(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
if !self.subscriptions.cancel(id.clone()) {
return Ok(false)
}
// forget subscription keys
let mut storage_subscriptions = self.storage_subscriptions.lock();
let keys = storage_subscriptions.keys_by_subscription.remove(&id);
for key in keys.into_iter().flat_map(|keys| keys.into_iter()) {
match storage_subscriptions.subscriptions_by_key.entry(key) {
Entry::Vacant(_) => unreachable!(
"every key from keys_by_subscription has\
corresponding entry in subscriptions_by_key; qed"
),
Entry::Occupied(mut entry) => {
entry.get_mut().remove(&id);
if entry.get().is_empty() {
entry.remove();
}
},
}
}
Ok(true)
}
fn subscribe_runtime_version(
&self,
_meta: crate::Metadata,
subscriber: Subscriber<RuntimeVersion>,
) {
self.subscriptions.add(subscriber, move |sink| {
let fetcher = self.fetcher.clone();
let remote_blockchain = self.remote_blockchain.clone();
let version_subscriptions = self.version_subscriptions.clone();
let initial_block = self.block_or_best(None);
let versions_stream = subscription_stream::<Block, _, _, _, _, _, _, _, _>(
version_subscriptions,
self.client.import_notification_stream().map(|notification| notification.hash),
display_error(
runtime_version(&*remote_blockchain, fetcher.clone(), initial_block)
.map(move |r| r.map(|r| (initial_block, r))),
),
move |block| runtime_version(&*remote_blockchain, fetcher.clone(), block),
|_, old_version, new_version| {
let version_differs = old_version
.as_ref()
.map(|old_version| *old_version != new_version)
.unwrap_or(true);
version_differs.then(|| new_version.clone())
},
);
versions_stream
.map_ok(Ok)
.forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e)))
// we ignore the resulting Stream (if the first stream is over we are unsubscribed)
.map(|_| ())
});
}
fn unsubscribe_runtime_version(
&self,
_meta: Option<crate::Metadata>,
id: SubscriptionId,
) -> RpcResult<bool> {
Ok(self.subscriptions.cancel(id))
}
fn trace_block(
&self,
_block: Block::Hash,
_targets: Option<String>,
_storage_keys: Option<String>,
_methods: Option<String>,
) -> FutureResult<sp_rpc::tracing::TraceBlockResponse> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
}
impl<Block, F, Client> ChildStateBackend<Block, Client> for LightState<Block, F, Client>
where
Block: BlockT,
Client: BlockchainEvents<Block> + HeaderBackend<Block> + Send + Sync + 'static,
F: Fetcher<Block> + 'static,
{
fn read_child_proof(
&self,
_block: Option<Block::Hash>,
_storage_key: PrefixedStorageKey,
_keys: Vec<StorageKey>,
) -> FutureResult<ReadProof<Block::Hash>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage_keys(
&self,
_block: Option<Block::Hash>,
_storage_key: PrefixedStorageKey,
_prefix: StorageKey,
) -> FutureResult<Vec<StorageKey>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage_keys_paged(
&self,
_block: Option<Block::Hash>,
_storage_key: PrefixedStorageKey,
_prefix: Option<StorageKey>,
_count: u32,
_start_key: Option<StorageKey>,
) -> FutureResult<Vec<StorageKey>> {
async move { Err(client_err(ClientError::NotAvailableOnLightClient)) }.boxed()
}
fn storage(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<StorageData>> {
let block = self.block_or_best(block);
let fetcher = self.fetcher.clone();
let child_storage =
resolve_header(&*self.remote_blockchain, &*self.fetcher, block).then(move |result| {
match result {
Ok(header) => Either::Left(
fetcher
.remote_read_child(RemoteReadChildRequest {
block,
header,
storage_key,
keys: vec![key.0.clone()],
retry_count: Default::default(),
})
.then(move |result| {
ready(
result
.map(|mut data| {
data.remove(&key.0)
.expect(
"successful result has entry for all keys; qed",
)
.map(StorageData)
})
.map_err(client_err),
)
}),
),
Err(error) => Either::Right(ready(Err(error))),
}
});
child_storage.boxed()
}
fn storage_entries(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
keys: Vec<StorageKey>,
) -> FutureResult<Vec<Option<StorageData>>> {
let block = self.block_or_best(block);
let fetcher = self.fetcher.clone();
let keys = keys.iter().map(|k| k.0.clone()).collect::<Vec<_>>();
let child_storage =
resolve_header(&*self.remote_blockchain, &*self.fetcher, block).then(move |result| {
match result {
Ok(header) => Either::Left(
fetcher
.remote_read_child(RemoteReadChildRequest {
block,
header,
storage_key,
keys: keys.clone(),
retry_count: Default::default(),
})
.then(move |result| {
ready(
result
.map(|data| {
data.iter()
.filter_map(|(k, d)| {
keys.contains(k).then(|| {
d.as_ref().map(|v| StorageData(v.to_vec()))
})
})
.collect::<Vec<_>>()
})
.map_err(client_err),
)
}),
),
Err(error) => Either::Right(ready(Err(error))),
}
});
child_storage.boxed()
}
fn storage_hash(
&self,
block: Option<Block::Hash>,
storage_key: PrefixedStorageKey,
key: StorageKey,
) -> FutureResult<Option<Block::Hash>> {
let child_storage = ChildStateBackend::storage(self, block, storage_key, key);
async move { child_storage.await.map(|r| r.map(|s| HashFor::<Block>::hash(&s.0))) }.boxed()
}
}
/// Resolve header by hash.
fn resolve_header<Block: BlockT, F: Fetcher<Block>>(
remote_blockchain: &dyn RemoteBlockchain<Block>,
fetcher: &F,
block: Block::Hash,
) -> impl std::future::Future<Output = Result<Block::Header, Error>> {
let maybe_header = future_header(remote_blockchain, fetcher, BlockId::Hash(block));
maybe_header.then(move |result| {
ready(
result
.and_then(|maybe_header| {
maybe_header.ok_or_else(|| ClientError::UnknownBlock(format!("{}", block)))
})
.map_err(client_err),
)
})
}
/// Call runtime method at given block
fn call<Block: BlockT, F: Fetcher<Block>>(
remote_blockchain: &dyn RemoteBlockchain<Block>,
fetcher: Arc<F>,
block: Block::Hash,
method: String,
call_data: Bytes,
) -> impl std::future::Future<Output = Result<Bytes, Error>> {
resolve_header(remote_blockchain, &*fetcher, block).then(move |result| match result {
Ok(header) => Either::Left(
fetcher
.remote_call(RemoteCallRequest {
block,
header,
method,
call_data: call_data.0,
retry_count: Default::default(),
})
.then(|result| ready(result.map(Bytes).map_err(client_err))),
),
Err(error) => Either::Right(ready(Err(error))),
})
}
/// Get runtime version at given block.
fn runtime_version<Block: BlockT, F: Fetcher<Block>>(
remote_blockchain: &dyn RemoteBlockchain<Block>,
fetcher: Arc<F>,
block: Block::Hash,
) -> impl std::future::Future<Output = Result<RuntimeVersion, Error>> {
call(remote_blockchain, fetcher, block, "Core_version".into(), Bytes(Vec::new())).then(
|version| {
ready(version.and_then(|version| {
Decode::decode(&mut &version.0[..])
.map_err(|e| client_err(ClientError::VersionInvalid(e.to_string())))
}))
},
)
}
/// Get storage value at given key at given block.
fn storage<Block: BlockT, F: Fetcher<Block>>(
remote_blockchain: &dyn RemoteBlockchain<Block>,
fetcher: Arc<F>,
block: Block::Hash,
keys: Vec<Vec<u8>>,
) -> impl std::future::Future<Output = Result<HashMap<StorageKey, Option<StorageData>>, Error>> {
resolve_header(remote_blockchain, &*fetcher, block).then(move |result| match result {
Ok(header) => Either::Left(
fetcher
.remote_read(RemoteReadRequest {
block,
header,
keys,
retry_count: Default::default(),
})
.then(|result| {
ready(
result
.map(|result| {
result
.into_iter()
.map(|(key, value)| (StorageKey(key), value.map(StorageData)))
.collect()
})
.map_err(client_err),
)
}),
),
Err(error) => Either::Right(ready(Err(error))),
})
}
/// Returns subscription stream that issues request on every imported block and
/// if value has changed from previous block, emits (stream) item.
fn subscription_stream<
Block,
Requests,
FutureBlocksStream,
V,
N,
InitialRequestFuture,
IssueRequest,
IssueRequestFuture,
CompareValues,
>(
shared_requests: Requests,
future_blocks_stream: FutureBlocksStream,
initial_request: InitialRequestFuture,
issue_request: IssueRequest,
compare_values: CompareValues,
) -> impl Stream<Item = std::result::Result<N, ()>>
where
Block: BlockT,
Requests: 'static + SharedRequests<Block::Hash, V>,
FutureBlocksStream: Stream<Item = Block::Hash>,
V: Send + 'static + Clone,
InitialRequestFuture: Future<Output = Result<(Block::Hash, V), ()>> + Send + 'static,
IssueRequest: 'static + Fn(Block::Hash) -> IssueRequestFuture,
IssueRequestFuture: Future<Output = Result<V, Error>> + Send + 'static,
CompareValues: Fn(Block::Hash, Option<&V>, &V) -> Option<N>,
{
// we need to send initial value first, then we'll only be sending if value has changed
let previous_value = Arc::new(Mutex::new(None));
// prepare 'stream' of initial values
let initial_value_stream = initial_request.into_stream();
// prepare stream of future values
//
// we do not want to stop stream if single request fails
// (the warning should have been already issued by the request issuer)
let future_values_stream = future_blocks_stream
.then(move |block| {
maybe_share_remote_request::<Block, _, _, _, _>(
shared_requests.clone(),
block,
&issue_request,
)
.map(move |r| r.map(|v| (block, v)))
})
.filter(|r| ready(r.is_ok()));
// now let's return changed values for selected blocks
initial_value_stream
.chain(future_values_stream)
.try_filter_map(move |(block, new_value)| {
let mut previous_value = previous_value.lock();
let res = compare_values(block, previous_value.as_ref(), &new_value).map(
|notification_value| {
*previous_value = Some(new_value);
notification_value
},
);
async move { Ok(res) }
})
.map_err(|_| ())
}
/// Request some data from remote node, probably reusing response from already
/// (in-progress) existing request.
fn maybe_share_remote_request<Block: BlockT, Requests, V, IssueRequest, IssueRequestFuture>(
shared_requests: Requests,
block: Block::Hash,
issue_request: &IssueRequest,
) -> impl std::future::Future<Output = Result<V, ()>>
where
V: Clone,
Requests: SharedRequests<Block::Hash, V>,
IssueRequest: Fn(Block::Hash) -> IssueRequestFuture,
IssueRequestFuture: std::future::Future<Output = Result<V, Error>>,
{
let (sender, receiver) = channel();
let need_issue_request = shared_requests.listen_request(block, sender);
// if that isn't the first request - just listen for existing request' response
if !need_issue_request {
return Either::Right(receiver.then(|r| ready(r.unwrap_or(Err(())))))
}
// that is the first request - issue remote request + notify all listeners on
// completion
Either::Left(display_error(issue_request(block)).then(move |remote_result| {
let listeners = shared_requests.on_response_received(block);
// skip first element, because this future is the first element
for receiver in listeners.into_iter().skip(1) {
if let Err(_) = receiver.send(remote_result.clone()) {
// we don't care if receiver has been dropped already
}
}
ready(remote_result)
}))
}
/// Convert successful future result into Ok(result) and error into Err(()),
/// displaying warning.
fn display_error<F, T>(future: F) -> impl std::future::Future<Output = Result<T, ()>>
where
F: std::future::Future<Output = Result<T, Error>>,
{
future.then(|result| {
ready(result.or_else(|err| {
warn!("Remote request for subscription data has failed with: {:?}", err);
Err(())
}))
})
}
#[cfg(test)]
mod tests {
use super::*;
use futures::{executor, stream};
use sp_core::H256;
use substrate_test_runtime_client::runtime::Block;
#[test]
fn subscription_stream_works() {
let stream = subscription_stream::<Block, _, _, _, _, _, _, _, _>(
SimpleSubscriptions::default(),
stream::iter(vec![H256::from([2; 32]), H256::from([3; 32])]),
ready(Ok((H256::from([1; 32]), 100))),
|block| match block[0] {
2 => ready(Ok(100)),
3 => ready(Ok(200)),
_ => unreachable!("should not issue additional requests"),
},
|_, old_value, new_value| match old_value == Some(new_value) {
true => None,
false => Some(new_value.clone()),
},
);
assert_eq!(executor::block_on(stream.collect::<Vec<_>>()), vec![Ok(100), Ok(200)]);
}
#[test]
fn subscription_stream_ignores_failed_requests() {
let stream = subscription_stream::<Block, _, _, _, _, _, _, _, _>(
SimpleSubscriptions::default(),
stream::iter(vec![H256::from([2; 32]), H256::from([3; 32])]),
ready(Ok((H256::from([1; 32]), 100))),
|block| match block[0] {
2 => ready(Err(client_err(ClientError::NotAvailableOnLightClient))),
3 => ready(Ok(200)),
_ => unreachable!("should not issue additional requests"),
},
|_, old_value, new_value| match old_value == Some(new_value) {
true => None,
false => Some(new_value.clone()),
},
);
assert_eq!(executor::block_on(stream.collect::<Vec<_>>()), vec![Ok(100), Ok(200)]);
}
#[test]
fn maybe_share_remote_request_shares_request() {
type UnreachableFuture = futures::future::Ready<Result<u32, Error>>;
let shared_requests = SimpleSubscriptions::default();
// let's 'issue' requests for B1
shared_requests.lock().insert(H256::from([1; 32]), vec![channel().0]);
// make sure that no additional requests are issued when we're asking for B1
let _ = maybe_share_remote_request::<Block, _, _, _, UnreachableFuture>(
shared_requests.clone(),
H256::from([1; 32]),
&|_| unreachable!("no duplicate requests issued"),
);
// make sure that additional requests is issued when we're asking for B2
let request_issued = Arc::new(Mutex::new(false));
let _ = maybe_share_remote_request::<Block, _, _, _, UnreachableFuture>(
shared_requests.clone(),
H256::from([2; 32]),
&|_| {
*request_issued.lock() = true;
ready(Ok(Default::default()))
},
);
assert!(*request_issued.lock());
}
}
-1
View File
@@ -53,7 +53,6 @@ sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" }
sp-storage = { version = "4.0.0-dev", path = "../../primitives/storage" }
sc-network = { version = "0.10.0-dev", path = "../network" }
sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" }
sc-light = { version = "4.0.0-dev", path = "../light" }
sc-client-api = { version = "4.0.0-dev", path = "../api" }
sp-api = { version = "4.0.0-dev", path = "../../primitives/api" }
sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" }
+15 -121
View File
@@ -18,7 +18,7 @@
use crate::{
build_network_future,
client::{light, Client, ClientConfig},
client::{Client, ClientConfig},
config::{Configuration, KeystoreConfig, PrometheusConfig, TransactionStorageMode},
error::Error,
metrics::MetricsService,
@@ -58,7 +58,7 @@ use sp_core::traits::{CodeExecutor, SpawnNamed};
use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
generic::BlockId,
traits::{Block as BlockT, BlockIdTo, HashFor, Zero},
traits::{Block as BlockT, BlockIdTo, Zero},
BuildStorage,
};
use std::{str::FromStr, sync::Arc, time::SystemTime};
@@ -137,47 +137,9 @@ pub type TFullBackend<TBl> = sc_client_db::Backend<TBl>;
pub type TFullCallExecutor<TBl, TExec> =
crate::client::LocalCallExecutor<TBl, sc_client_db::Backend<TBl>, TExec>;
/// Light client type.
pub type TLightClient<TBl, TRtApi, TExec> =
TLightClientWithBackend<TBl, TRtApi, TExec, TLightBackend<TBl>>;
/// Light client backend type.
pub type TLightBackend<TBl> =
sc_light::Backend<sc_client_db::light::LightStorage<TBl>, HashFor<TBl>>;
/// Light call executor type.
pub type TLightCallExecutor<TBl, TExec> = sc_light::GenesisCallExecutor<
sc_light::Backend<sc_client_db::light::LightStorage<TBl>, HashFor<TBl>>,
crate::client::LocalCallExecutor<
TBl,
sc_light::Backend<sc_client_db::light::LightStorage<TBl>, HashFor<TBl>>,
TExec,
>,
>;
type TFullParts<TBl, TRtApi, TExec> =
(TFullClient<TBl, TRtApi, TExec>, Arc<TFullBackend<TBl>>, KeystoreContainer, TaskManager);
type TLightParts<TBl, TRtApi, TExec> = (
Arc<TLightClient<TBl, TRtApi, TExec>>,
Arc<TLightBackend<TBl>>,
KeystoreContainer,
TaskManager,
Arc<OnDemand<TBl>>,
);
/// Light client backend type with a specific hash type.
pub type TLightBackendWithHash<TBl, THash> =
sc_light::Backend<sc_client_db::light::LightStorage<TBl>, THash>;
/// Light client type with a specific backend.
pub type TLightClientWithBackend<TBl, TRtApi, TExec, TBackend> = Client<
TBackend,
sc_light::GenesisCallExecutor<TBackend, crate::client::LocalCallExecutor<TBl, TBackend, TExec>>,
TBl,
TRtApi,
>;
trait AsCryptoStoreRef {
fn keystore_ref(&self) -> Arc<dyn CryptoStore>;
fn sync_keystore_ref(&self) -> Arc<dyn SyncCryptoStore>;
@@ -359,53 +321,6 @@ where
Ok((client, backend, keystore_container, task_manager))
}
/// Create the initial parts of a light node.
pub fn new_light_parts<TBl, TRtApi, TExec>(
config: &Configuration,
telemetry: Option<TelemetryHandle>,
executor: TExec,
) -> Result<TLightParts<TBl, TRtApi, TExec>, Error>
where
TBl: BlockT,
TExec: CodeExecutor + RuntimeVersionOf + Clone,
{
let keystore_container = KeystoreContainer::new(&config.keystore)?;
let task_manager = {
let registry = config.prometheus_config.as_ref().map(|cfg| &cfg.registry);
TaskManager::new(config.tokio_handle.clone(), registry)?
};
let db_storage = {
let db_settings = sc_client_db::DatabaseSettings {
state_cache_size: config.state_cache_size,
state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)),
state_pruning: config.state_pruning.clone(),
source: config.database.clone(),
keep_blocks: config.keep_blocks.clone(),
transaction_storage: config.transaction_storage.clone(),
};
sc_client_db::light::LightStorage::new(db_settings)?
};
let light_blockchain = sc_light::new_light_blockchain(db_storage);
let fetch_checker = Arc::new(sc_light::new_fetch_checker::<_, TBl, _>(
light_blockchain.clone(),
executor.clone(),
Box::new(task_manager.spawn_handle()),
));
let on_demand = Arc::new(sc_network::config::OnDemand::new(fetch_checker));
let backend = sc_light::new_light_backend(light_blockchain);
let client = Arc::new(light::new_light(
backend.clone(),
config.chain_spec.as_storage_builder(),
executor,
Box::new(task_manager.spawn_handle()),
config.prometheus_config.as_ref().map(|config| config.registry.clone()),
telemetry,
)?);
Ok((client, backend, keystore_container, task_manager, on_demand))
}
/// Create an instance of default DB-backend backend.
pub fn new_db_backend<Block>(
settings: DatabaseSettings,
@@ -559,12 +474,12 @@ where
mut config,
task_manager,
client,
on_demand,
on_demand: _,
backend,
keystore,
transaction_pool,
rpc_extensions_builder,
remote_blockchain,
remote_blockchain: _,
network,
system_rpc_tx,
telemetry,
@@ -630,8 +545,6 @@ where
client.clone(),
transaction_pool.clone(),
keystore.clone(),
on_demand.clone(),
remote_blockchain.clone(),
&*rpc_extensions_builder,
backend.offchain_storage(),
system_rpc_tx.clone(),
@@ -729,8 +642,6 @@ fn gen_handler<TBl, TBackend, TExPool, TRpc, TCl>(
client: Arc<TCl>,
transaction_pool: Arc<TExPool>,
keystore: SyncCryptoStorePtr,
on_demand: Option<Arc<OnDemand<TBl>>>,
remote_blockchain: Option<Arc<dyn RemoteBlockchain<TBl>>>,
rpc_extensions_builder: &(dyn RpcExtensionBuilder<Output = TRpc> + Send),
offchain_storage: Option<<TBackend as sc_client_api::backend::Backend<TBl>>::OffchainStorage>,
system_rpc_tx: TracingUnboundedSender<sc_rpc::system::Request<TBl>>,
@@ -769,34 +680,17 @@ where
let task_executor = sc_rpc::SubscriptionTaskExecutor::new(spawn_handle);
let subscriptions = SubscriptionManager::new(Arc::new(task_executor.clone()));
let (chain, state, child_state) =
if let (Some(remote_blockchain), Some(on_demand)) = (remote_blockchain, on_demand) {
// Light clients
let chain = sc_rpc::chain::new_light(
client.clone(),
subscriptions.clone(),
remote_blockchain.clone(),
on_demand.clone(),
);
let (state, child_state) = sc_rpc::state::new_light(
client.clone(),
subscriptions.clone(),
remote_blockchain.clone(),
on_demand,
deny_unsafe,
);
(chain, state, child_state)
} else {
// Full nodes
let chain = sc_rpc::chain::new_full(client.clone(), subscriptions.clone());
let (state, child_state) = sc_rpc::state::new_full(
client.clone(),
subscriptions.clone(),
deny_unsafe,
config.rpc_max_payload,
);
(chain, state, child_state)
};
let (chain, state, child_state) = {
// Full nodes
let chain = sc_rpc::chain::new_full(client.clone(), subscriptions.clone());
let (state, child_state) = sc_rpc::state::new_full(
client.clone(),
subscriptions.clone(),
deny_unsafe,
config.rpc_max_payload,
);
(chain, state, child_state)
};
let author =
sc_rpc::author::Author::new(client, transaction_pool, subscriptions, keystore, deny_unsafe);
@@ -42,6 +42,7 @@ use sc_client_api::{
ProvideUncles,
},
execution_extensions::ExecutionExtensions,
light::ChangesProof,
notifications::{StorageEventStream, StorageNotifications},
CallExecutor, ExecutorProvider, KeyIterator, ProofProvider, UsageProvider,
};
@@ -49,7 +50,6 @@ use sc_consensus::{
BlockCheckParams, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction,
};
use sc_executor::RuntimeVersion;
use sc_light::fetcher::ChangesProof;
use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO};
use sp_api::{
ApiExt, ApiRef, CallApiAt, CallApiAtParams, ConstructRuntimeApi, Core as CoreApi,
@@ -1,82 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Light client utilities.
use std::sync::Arc;
use prometheus_endpoint::Registry;
use sc_executor::RuntimeVersionOf;
use sc_telemetry::TelemetryHandle;
use sp_blockchain::Result as ClientResult;
use sp_core::traits::{CodeExecutor, SpawnNamed};
use sp_runtime::{
traits::{Block as BlockT, HashFor},
BuildStorage,
};
use super::{
call_executor::LocalCallExecutor,
client::{Client, ClientConfig},
};
use sc_client_api::light::Storage as BlockchainStorage;
use sc_light::{Backend, GenesisCallExecutor};
/// Create an instance of light client.
pub fn new_light<B, S, RA, E>(
backend: Arc<Backend<S, HashFor<B>>>,
genesis_storage: &dyn BuildStorage,
code_executor: E,
spawn_handle: Box<dyn SpawnNamed>,
prometheus_registry: Option<Registry>,
telemetry: Option<TelemetryHandle>,
) -> ClientResult<
Client<
Backend<S, HashFor<B>>,
GenesisCallExecutor<
Backend<S, HashFor<B>>,
LocalCallExecutor<B, Backend<S, HashFor<B>>, E>,
>,
B,
RA,
>,
>
where
B: BlockT,
S: BlockchainStorage<B> + 'static,
E: CodeExecutor + RuntimeVersionOf + Clone + 'static,
{
let local_executor = LocalCallExecutor::new(
backend.clone(),
code_executor,
spawn_handle.clone(),
ClientConfig::default(),
)?;
let executor = GenesisCallExecutor::new(backend.clone(), local_executor);
Client::new(
backend,
executor,
genesis_storage,
Default::default(),
Default::default(),
Default::default(),
prometheus_registry,
telemetry,
ClientConfig::default(),
)
}
@@ -49,7 +49,6 @@ mod block_rules;
mod call_executor;
mod client;
pub mod genesis;
pub mod light;
mod wasm_override;
mod wasm_substitutes;
+3 -4
View File
@@ -49,10 +49,9 @@ use sp_runtime::{
pub use self::{
builder::{
build_network, build_offchain_workers, new_client, new_db_backend, new_full_client,
new_full_parts, new_light_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer,
NetworkStarter, NoopRpcExtensionBuilder, RpcExtensionBuilder, SpawnTasksParams,
TFullBackend, TFullCallExecutor, TFullClient, TLightBackend, TLightBackendWithHash,
TLightCallExecutor, TLightClient, TLightClientWithBackend,
new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter,
NoopRpcExtensionBuilder, RpcExtensionBuilder, SpawnTasksParams, TFullBackend,
TFullCallExecutor, TFullClient,
},
client::{ClientConfig, LocalCallExecutor},
error::Error,
@@ -21,6 +21,7 @@ use std::sync::Arc;
type TestBackend = sc_client_api::in_mem::Backend<substrate_test_runtime::Block>;
#[test]
fn test_leaves_with_complex_block_tree() {
let backend = Arc::new(TestBackend::new());
@@ -1,981 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use super::prepare_client_with_key_changes;
use parity_scale_codec::{Decode, Encode};
use parking_lot::Mutex;
use sc_block_builder::BlockBuilderProvider;
use sc_client_api::{
backend::NewBlockState,
blockchain::Info,
cht,
in_mem::{Backend as InMemBackend, Blockchain as InMemoryBlockchain},
AuxStore, Backend as ClientBackend, BlockBackend, BlockImportOperation, CallExecutor,
ChangesProof, ExecutionStrategy, FetchChecker, ProofProvider, ProvideChtRoots,
RemoteBodyRequest, RemoteCallRequest, RemoteChangesRequest, RemoteHeaderRequest,
RemoteReadChildRequest, RemoteReadRequest, Storage, StorageProof, StorageProvider,
};
use sc_executor::{NativeElseWasmExecutor, RuntimeVersion, WasmExecutionMethod};
use sc_light::{
backend::{Backend, GenesisOrUnavailableState},
blockchain::{Blockchain, BlockchainCache},
call_executor::{check_execution_proof, GenesisCallExecutor},
fetcher::LightDataChecker,
};
use sp_api::{ProofRecorder, StorageTransactionCache};
use sp_blockchain::{
well_known_cache_keys, BlockStatus, CachedHeaderMetadata, Error as ClientError, HeaderBackend,
Result as ClientResult,
};
use sp_consensus::BlockOrigin;
use sp_core::{testing::TaskExecutor, NativeOrEncoded, H256};
use sp_externalities::Extensions;
use sp_runtime::{
generic::BlockId,
traits::{BlakeTwo256, Block as _, Header as HeaderT, NumberFor},
Digest, Justifications,
};
use sp_state_machine::{ExecutionManager, OverlayedChanges};
use std::{cell::RefCell, collections::HashMap, panic::UnwindSafe, sync::Arc};
use substrate_test_runtime_client::{
runtime::{self, Block, Extrinsic, Hash, Header},
AccountKeyring, ClientBlockImportExt, TestClient,
};
use sp_core::{
blake2_256,
storage::{well_known_keys, ChildInfo, StorageKey},
ChangesTrieConfiguration,
};
use sp_state_machine::Backend as _;
pub type DummyBlockchain = Blockchain<DummyStorage>;
pub struct DummyStorage {
pub changes_tries_cht_roots: HashMap<u64, Hash>,
pub aux_store: Mutex<HashMap<Vec<u8>, Vec<u8>>>,
}
impl DummyStorage {
pub fn new() -> Self {
DummyStorage {
changes_tries_cht_roots: HashMap::new(),
aux_store: Mutex::new(HashMap::new()),
}
}
}
impl sp_blockchain::HeaderBackend<Block> for DummyStorage {
fn header(&self, _id: BlockId<Block>) -> ClientResult<Option<Header>> {
Err(ClientError::Backend("Test error".into()))
}
fn info(&self) -> Info<Block> {
panic!("Test error")
}
fn status(&self, _id: BlockId<Block>) -> ClientResult<BlockStatus> {
Err(ClientError::Backend("Test error".into()))
}
fn number(&self, hash: Hash) -> ClientResult<Option<NumberFor<Block>>> {
if hash == Default::default() {
Ok(Some(Default::default()))
} else {
Err(ClientError::Backend("Test error".into()))
}
}
fn hash(&self, number: u64) -> ClientResult<Option<Hash>> {
if number == 0 {
Ok(Some(Default::default()))
} else {
Err(ClientError::Backend("Test error".into()))
}
}
}
impl sp_blockchain::HeaderMetadata<Block> for DummyStorage {
type Error = ClientError;
fn header_metadata(&self, hash: Hash) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
self.header(BlockId::hash(hash))?
.map(|header| CachedHeaderMetadata::from(&header))
.ok_or(ClientError::UnknownBlock("header not found".to_owned()))
}
fn insert_header_metadata(&self, _hash: Hash, _metadata: CachedHeaderMetadata<Block>) {}
fn remove_header_metadata(&self, _hash: Hash) {}
}
impl AuxStore for DummyStorage {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
_delete: D,
) -> ClientResult<()> {
for (k, v) in insert.into_iter() {
self.aux_store.lock().insert(k.to_vec(), v.to_vec());
}
Ok(())
}
fn get_aux(&self, key: &[u8]) -> ClientResult<Option<Vec<u8>>> {
Ok(self.aux_store.lock().get(key).cloned())
}
}
impl Storage<Block> for DummyStorage {
fn import_header(
&self,
_header: Header,
_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
_state: NewBlockState,
_aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
) -> ClientResult<()> {
Ok(())
}
fn set_head(&self, _block: BlockId<Block>) -> ClientResult<()> {
Err(ClientError::Backend("Test error".into()))
}
fn finalize_header(&self, _block: BlockId<Block>) -> ClientResult<()> {
Err(ClientError::Backend("Test error".into()))
}
fn last_finalized(&self) -> ClientResult<Hash> {
Err(ClientError::Backend("Test error".into()))
}
fn cache(&self) -> Option<Arc<dyn BlockchainCache<Block>>> {
None
}
fn usage_info(&self) -> Option<sc_client_api::UsageInfo> {
None
}
}
impl ProvideChtRoots<Block> for DummyStorage {
fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult<Option<Hash>> {
Err(ClientError::Backend("Test error".into()))
}
fn changes_trie_cht_root(&self, cht_size: u64, block: u64) -> ClientResult<Option<Hash>> {
cht::block_to_cht_number(cht_size, block)
.and_then(|cht_num| self.changes_tries_cht_roots.get(&cht_num))
.cloned()
.ok_or_else(|| {
ClientError::Backend(format!("Test error: CHT for block #{} not found", block))
.into()
})
.map(Some)
}
}
struct DummyCallExecutor;
impl CallExecutor<Block> for DummyCallExecutor {
type Error = ClientError;
type Backend = substrate_test_runtime_client::Backend;
fn call(
&self,
_id: &BlockId<Block>,
_method: &str,
_call_data: &[u8],
_strategy: ExecutionStrategy,
_extensions: Option<Extensions>,
) -> Result<Vec<u8>, ClientError> {
Ok(vec![42])
}
fn contextual_call<
EM: Fn(
Result<NativeOrEncoded<R>, Self::Error>,
Result<NativeOrEncoded<R>, Self::Error>,
) -> Result<NativeOrEncoded<R>, Self::Error>,
R: Encode + Decode + PartialEq,
NC: FnOnce() -> Result<R, sp_api::ApiError> + UnwindSafe,
>(
&self,
_at: &BlockId<Block>,
_method: &str,
_call_data: &[u8],
_changes: &RefCell<OverlayedChanges>,
_storage_transaction_cache: Option<
&RefCell<
StorageTransactionCache<
Block,
<Self::Backend as sc_client_api::backend::Backend<Block>>::State,
>,
>,
>,
_execution_manager: ExecutionManager<EM>,
_native_call: Option<NC>,
_proof_recorder: &Option<ProofRecorder<Block>>,
_extensions: Option<Extensions>,
) -> ClientResult<NativeOrEncoded<R>>
where
ExecutionManager<EM>: Clone,
{
unreachable!()
}
fn runtime_version(&self, _id: &BlockId<Block>) -> Result<RuntimeVersion, ClientError> {
unreachable!()
}
fn prove_execution(
&self,
_: &BlockId<Block>,
_: &str,
_: &[u8],
) -> Result<(Vec<u8>, StorageProof), ClientError> {
unreachable!()
}
}
fn local_executor() -> NativeElseWasmExecutor<substrate_test_runtime_client::LocalExecutorDispatch>
{
NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8)
}
#[test]
fn local_state_is_created_when_genesis_state_is_available() {
let def = Default::default();
let header0 =
substrate_test_runtime_client::runtime::Header::new(0, def, def, def, Default::default());
let backend: Backend<_, BlakeTwo256> =
Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
let mut op = backend.begin_operation().unwrap();
op.set_block_data(header0, None, None, None, NewBlockState::Final).unwrap();
op.set_genesis_state(Default::default(), true).unwrap();
backend.commit_operation(op).unwrap();
match backend.state_at(BlockId::Number(0)).unwrap() {
GenesisOrUnavailableState::Genesis(_) => (),
_ => panic!("unexpected state"),
}
}
#[test]
fn unavailable_state_is_created_when_genesis_state_is_unavailable() {
let backend: Backend<_, BlakeTwo256> =
Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
match backend.state_at(BlockId::Number(0)).unwrap() {
GenesisOrUnavailableState::Unavailable => (),
_ => panic!("unexpected state"),
}
}
#[test]
fn light_aux_store_is_updated_via_non_importing_op() {
let backend = Backend::new(Arc::new(DummyBlockchain::new(DummyStorage::new())));
let mut op = ClientBackend::<Block>::begin_operation(&backend).unwrap();
BlockImportOperation::<Block>::insert_aux(&mut op, vec![(vec![1], Some(vec![2]))]).unwrap();
ClientBackend::<Block>::commit_operation(&backend, op).unwrap();
assert_eq!(AuxStore::get_aux(&backend, &[1]).unwrap(), Some(vec![2]));
}
#[test]
fn execution_proof_is_generated_and_checked() {
fn execute(remote_client: &TestClient, at: u64, method: &'static str) -> (Vec<u8>, Vec<u8>) {
let remote_block_id = BlockId::Number(at);
let remote_header = remote_client.header(&remote_block_id).unwrap().unwrap();
// 'fetch' execution proof from remote node
let (remote_result, remote_execution_proof) =
remote_client.execution_proof(&remote_block_id, method, &[]).unwrap();
// check remote execution proof locally
let local_result = check_execution_proof::<_, _, BlakeTwo256>(
&local_executor(),
Box::new(TaskExecutor::new()),
&RemoteCallRequest {
block: substrate_test_runtime_client::runtime::Hash::default(),
header: remote_header,
method: method.into(),
call_data: vec![],
retry_count: None,
},
remote_execution_proof,
)
.unwrap();
(remote_result, local_result)
}
fn execute_with_proof_failure(remote_client: &TestClient, at: u64) {
let remote_block_id = BlockId::Number(at);
let remote_header = remote_client.header(&remote_block_id).unwrap().unwrap();
// 'fetch' execution proof from remote node
let (_, remote_execution_proof) = remote_client
.execution_proof(
&remote_block_id,
"Core_initialize_block",
&Header::new(
at,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.encode(),
)
.unwrap();
// check remote execution proof locally
let execution_result = check_execution_proof::<_, _, BlakeTwo256>(
&local_executor(),
Box::new(TaskExecutor::new()),
&RemoteCallRequest {
block: substrate_test_runtime_client::runtime::Hash::default(),
header: remote_header.clone(),
method: "Core_initialize_block".into(),
call_data: Header::new(
at + 1,
Default::default(),
Default::default(),
remote_header.hash(),
remote_header.digest().clone(), // this makes next header wrong
)
.encode(),
retry_count: None,
},
remote_execution_proof,
);
match execution_result {
Err(sp_blockchain::Error::Execution(_)) => (),
_ => panic!("Unexpected execution result: {:?}", execution_result),
}
}
// prepare remote client
let mut remote_client = substrate_test_runtime_client::new();
for i in 1u32..3u32 {
let mut digest = Digest::default();
digest.push(sp_runtime::generic::DigestItem::Other::<H256>(i.to_le_bytes().to_vec()));
futures::executor::block_on(remote_client.import_justified(
BlockOrigin::Own,
remote_client.new_block(digest).unwrap().build().unwrap().block,
Justifications::from((*b"TEST", Default::default())),
))
.unwrap();
}
// check method that doesn't requires environment
let (remote, local) = execute(&remote_client, 0, "Core_version");
assert_eq!(remote, local);
let (remote, local) = execute(&remote_client, 2, "Core_version");
assert_eq!(remote, local);
// check that proof check doesn't panic even if proof is incorrect AND no panic handler is set
execute_with_proof_failure(&remote_client, 2);
// check that proof check doesn't panic even if proof is incorrect AND panic handler is set
sp_panic_handler::set("TEST", "1.2.3");
execute_with_proof_failure(&remote_client, 2);
}
#[test]
fn code_is_executed_at_genesis_only() {
let backend = Arc::new(InMemBackend::<Block>::new());
let def = H256::default();
let header0 =
substrate_test_runtime_client::runtime::Header::new(0, def, def, def, Default::default());
let hash0 = header0.hash();
let header1 =
substrate_test_runtime_client::runtime::Header::new(1, def, def, hash0, Default::default());
let hash1 = header1.hash();
backend
.blockchain()
.insert(hash0, header0, None, None, NewBlockState::Final)
.unwrap();
backend
.blockchain()
.insert(hash1, header1, None, None, NewBlockState::Final)
.unwrap();
let genesis_executor = GenesisCallExecutor::new(backend, DummyCallExecutor);
assert_eq!(
genesis_executor
.call(&BlockId::Number(0), "test_method", &[], ExecutionStrategy::NativeElseWasm, None,)
.unwrap(),
vec![42],
);
let call_on_unavailable = genesis_executor.call(
&BlockId::Number(1),
"test_method",
&[],
ExecutionStrategy::NativeElseWasm,
None,
);
match call_on_unavailable {
Err(ClientError::NotAvailableOnLightClient) => (),
_ => unreachable!("unexpected result: {:?}", call_on_unavailable),
}
}
type TestChecker = LightDataChecker<
NativeElseWasmExecutor<substrate_test_runtime_client::LocalExecutorDispatch>,
Block,
DummyStorage,
>;
fn prepare_for_read_proof_check() -> (TestChecker, Header, StorageProof, u32) {
// prepare remote client
let remote_client = substrate_test_runtime_client::new();
let remote_block_id = BlockId::Number(0);
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
remote_block_header.state_root = remote_client
.state_at(&remote_block_id)
.unwrap()
.storage_root(::std::iter::empty())
.0
.into();
// 'fetch' read proof from remote node
let heap_pages = remote_client
.storage(&remote_block_id, &StorageKey(well_known_keys::HEAP_PAGES.to_vec()))
.unwrap()
.and_then(|v| Decode::decode(&mut &v.0[..]).ok())
.unwrap();
let remote_read_proof = remote_client
.read_proof(&remote_block_id, &mut std::iter::once(well_known_keys::HEAP_PAGES))
.unwrap();
// check remote read proof locally
let local_storage = InMemoryBlockchain::<Block>::new();
local_storage
.insert(remote_block_hash, remote_block_header.clone(), None, None, NewBlockState::Final)
.unwrap();
let local_checker = LightDataChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
(local_checker, remote_block_header, remote_read_proof, heap_pages)
}
fn prepare_for_read_child_proof_check() -> (TestChecker, Header, StorageProof, Vec<u8>) {
use substrate_test_runtime_client::{DefaultTestClientBuilderExt, TestClientBuilderExt};
let child_info = ChildInfo::new_default(b"child1");
let child_info = &child_info;
// prepare remote client
let remote_client = substrate_test_runtime_client::TestClientBuilder::new()
.add_extra_child_storage(child_info, b"key1".to_vec(), b"value1".to_vec())
.build();
let remote_block_id = BlockId::Number(0);
let remote_block_hash = remote_client.block_hash(0).unwrap().unwrap();
let mut remote_block_header = remote_client.header(&remote_block_id).unwrap().unwrap();
remote_block_header.state_root = remote_client
.state_at(&remote_block_id)
.unwrap()
.storage_root(::std::iter::empty())
.0
.into();
// 'fetch' child read proof from remote node
let child_value = remote_client
.child_storage(&remote_block_id, child_info, &StorageKey(b"key1".to_vec()))
.unwrap()
.unwrap()
.0;
assert_eq!(b"value1"[..], child_value[..]);
let remote_read_proof = remote_client
.read_child_proof(&remote_block_id, child_info, &mut std::iter::once("key1".as_bytes()))
.unwrap();
// check locally
let local_storage = InMemoryBlockchain::<Block>::new();
local_storage
.insert(remote_block_hash, remote_block_header.clone(), None, None, NewBlockState::Final)
.unwrap();
let local_checker = LightDataChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
(local_checker, remote_block_header, remote_read_proof, child_value)
}
fn prepare_for_header_proof_check(insert_cht: bool) -> (TestChecker, Hash, Header, StorageProof) {
// prepare remote client
let mut remote_client = substrate_test_runtime_client::new();
let mut local_headers_hashes = Vec::new();
for i in 0..4 {
let block = remote_client.new_block(Default::default()).unwrap().build().unwrap().block;
futures::executor::block_on(remote_client.import(BlockOrigin::Own, block)).unwrap();
local_headers_hashes.push(
remote_client
.block_hash(i + 1)
.map_err(|_| ClientError::Backend("TestError".into())),
);
}
// 'fetch' header proof from remote node
let remote_block_id = BlockId::Number(1);
let (remote_block_header, remote_header_proof) =
remote_client.header_proof_with_cht_size(&remote_block_id, 4).unwrap();
// check remote read proof locally
let local_storage = InMemoryBlockchain::<Block>::new();
let local_cht_root =
cht::compute_root::<Header, BlakeTwo256, _>(4, 0, local_headers_hashes).unwrap();
if insert_cht {
local_storage.insert_cht_root(1, local_cht_root);
}
let local_checker = LightDataChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
(local_checker, local_cht_root, remote_block_header, remote_header_proof)
}
fn header_with_computed_extrinsics_root(extrinsics: Vec<Extrinsic>) -> Header {
use sp_trie::{trie_types::Layout, TrieConfiguration};
let iter = extrinsics.iter().map(Encode::encode);
let extrinsics_root = Layout::<BlakeTwo256>::ordered_trie_root(iter);
// only care about `extrinsics_root`
Header::new(0, extrinsics_root, H256::zero(), H256::zero(), Default::default())
}
#[test]
fn storage_read_proof_is_generated_and_checked() {
let (local_checker, remote_block_header, remote_read_proof, heap_pages) =
prepare_for_read_proof_check();
assert_eq!(
(&local_checker as &dyn FetchChecker<Block>)
.check_read_proof(
&RemoteReadRequest::<Header> {
block: remote_block_header.hash(),
header: remote_block_header,
keys: vec![well_known_keys::HEAP_PAGES.to_vec()],
retry_count: None,
},
remote_read_proof
)
.unwrap()
.remove(well_known_keys::HEAP_PAGES)
.unwrap()
.unwrap()[0],
heap_pages as u8
);
}
#[test]
fn storage_child_read_proof_is_generated_and_checked() {
let child_info = ChildInfo::new_default(&b"child1"[..]);
let (local_checker, remote_block_header, remote_read_proof, result) =
prepare_for_read_child_proof_check();
assert_eq!(
(&local_checker as &dyn FetchChecker<Block>)
.check_read_child_proof(
&RemoteReadChildRequest::<Header> {
block: remote_block_header.hash(),
header: remote_block_header,
storage_key: child_info.prefixed_storage_key(),
keys: vec![b"key1".to_vec()],
retry_count: None,
},
remote_read_proof
)
.unwrap()
.remove(b"key1".as_ref())
.unwrap()
.unwrap(),
result
);
}
#[test]
fn header_proof_is_generated_and_checked() {
let (local_checker, local_cht_root, remote_block_header, remote_header_proof) =
prepare_for_header_proof_check(true);
assert_eq!(
(&local_checker as &dyn FetchChecker<Block>)
.check_header_proof(
&RemoteHeaderRequest::<Header> {
cht_root: local_cht_root,
block: 1,
retry_count: None,
},
Some(remote_block_header.clone()),
remote_header_proof
)
.unwrap(),
remote_block_header
);
}
#[test]
fn check_header_proof_fails_if_cht_root_is_invalid() {
let (local_checker, _, mut remote_block_header, remote_header_proof) =
prepare_for_header_proof_check(true);
remote_block_header.number = 100;
assert!((&local_checker as &dyn FetchChecker<Block>)
.check_header_proof(
&RemoteHeaderRequest::<Header> {
cht_root: Default::default(),
block: 1,
retry_count: None,
},
Some(remote_block_header.clone()),
remote_header_proof
)
.is_err());
}
#[test]
fn check_header_proof_fails_if_invalid_header_provided() {
let (local_checker, local_cht_root, mut remote_block_header, remote_header_proof) =
prepare_for_header_proof_check(true);
remote_block_header.number = 100;
assert!((&local_checker as &dyn FetchChecker<Block>)
.check_header_proof(
&RemoteHeaderRequest::<Header> {
cht_root: local_cht_root,
block: 1,
retry_count: None,
},
Some(remote_block_header.clone()),
remote_header_proof
)
.is_err());
}
#[test]
fn changes_proof_is_generated_and_checked_when_headers_are_not_pruned() {
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
let local_checker = &local_checker as &dyn FetchChecker<Block>;
let max = remote_client.chain_info().best_number;
let max_hash = remote_client.chain_info().best_hash;
for (index, (begin, end, key, expected_result)) in test_cases.into_iter().enumerate() {
let begin_hash = remote_client.block_hash(begin).unwrap().unwrap();
let end_hash = remote_client.block_hash(end).unwrap().unwrap();
// 'fetch' changes proof from remote node
let key = StorageKey(key);
let remote_proof = remote_client
.key_changes_proof(begin_hash, end_hash, begin_hash, max_hash, None, &key)
.unwrap();
// check proof on local client
let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec();
let config = ChangesTrieConfiguration::new(4, 2);
let request = RemoteChangesRequest::<Header> {
changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange {
zero: (0, Default::default()),
end: None,
config: Some(config),
}],
first_block: (begin, begin_hash),
last_block: (end, end_hash),
max_block: (max, max_hash),
tries_roots: (begin, begin_hash, local_roots_range),
key: key.0,
storage_key: None,
retry_count: None,
};
let local_result = local_checker
.check_changes_proof(
&request,
ChangesProof {
max_block: remote_proof.max_block,
proof: remote_proof.proof,
roots: remote_proof.roots,
roots_proof: remote_proof.roots_proof,
},
)
.unwrap();
// ..and ensure that result is the same as on remote node
if local_result != expected_result {
panic!(
"Failed test {}: local = {:?}, expected = {:?}",
index, local_result, expected_result,
);
}
}
}
#[test]
fn changes_proof_is_generated_and_checked_when_headers_are_pruned() {
// we're testing this test case here:
// (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]),
let (remote_client, remote_roots, _) = prepare_client_with_key_changes();
let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec();
let dave = StorageKey(dave);
// 'fetch' changes proof from remote node:
// we're fetching changes for range b1..b4
// we do not know changes trie roots before b3 (i.e. we only know b3+b4)
// but we have changes trie CHT root for b1...b4
let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap();
let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap();
let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap();
let remote_proof = remote_client
.key_changes_proof_with_cht_size(b1, b4, b3, b4, None, &dave, 4)
.unwrap();
// prepare local checker, having a root of changes trie CHT#0
let local_cht_root = cht::compute_root::<Header, BlakeTwo256, _>(
4,
0,
remote_roots.iter().cloned().map(|ct| Ok(Some(ct))),
)
.unwrap();
let mut local_storage = DummyStorage::new();
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(local_storage)),
local_executor(),
Box::new(TaskExecutor::new()),
);
// check proof on local client
let config = ChangesTrieConfiguration::new(4, 2);
let request = RemoteChangesRequest::<Header> {
changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange {
zero: (0, Default::default()),
end: None,
config: Some(config),
}],
first_block: (1, b1),
last_block: (4, b4),
max_block: (4, b4),
tries_roots: (3, b3, vec![remote_roots[2].clone(), remote_roots[3].clone()]),
storage_key: None,
key: dave.0,
retry_count: None,
};
let local_result = local_checker
.check_changes_proof_with_cht_size(
&request,
ChangesProof {
max_block: remote_proof.max_block,
proof: remote_proof.proof,
roots: remote_proof.roots,
roots_proof: remote_proof.roots_proof,
},
4,
)
.unwrap();
assert_eq!(local_result, vec![(4, 0), (1, 1), (1, 0)]);
}
#[test]
fn check_changes_proof_fails_if_proof_is_wrong() {
let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes();
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
let local_checker = &local_checker as &dyn FetchChecker<Block>;
let max = remote_client.chain_info().best_number;
let max_hash = remote_client.chain_info().best_hash;
let (begin, end, key, _) = test_cases[0].clone();
let begin_hash = remote_client.block_hash(begin).unwrap().unwrap();
let end_hash = remote_client.block_hash(end).unwrap().unwrap();
// 'fetch' changes proof from remote node
let key = StorageKey(key);
let remote_proof = remote_client
.key_changes_proof(begin_hash, end_hash, begin_hash, max_hash, None, &key)
.unwrap();
let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec();
let config = ChangesTrieConfiguration::new(4, 2);
let request = RemoteChangesRequest::<Header> {
changes_trie_configs: vec![sp_core::ChangesTrieConfigurationRange {
zero: (0, Default::default()),
end: None,
config: Some(config),
}],
first_block: (begin, begin_hash),
last_block: (end, end_hash),
max_block: (max, max_hash),
tries_roots: (begin, begin_hash, local_roots_range.clone()),
storage_key: None,
key: key.0,
retry_count: None,
};
// check proof on local client using max from the future
assert!(local_checker
.check_changes_proof(
&request,
ChangesProof {
max_block: remote_proof.max_block + 1,
proof: remote_proof.proof.clone(),
roots: remote_proof.roots.clone(),
roots_proof: remote_proof.roots_proof.clone(),
}
)
.is_err());
// check proof on local client using broken proof
assert!(local_checker
.check_changes_proof(
&request,
ChangesProof {
max_block: remote_proof.max_block,
proof: local_roots_range.clone().into_iter().map(|v| v.as_ref().to_vec()).collect(),
roots: remote_proof.roots,
roots_proof: remote_proof.roots_proof,
}
)
.is_err());
// extra roots proofs are provided
assert!(local_checker
.check_changes_proof(
&request,
ChangesProof {
max_block: remote_proof.max_block,
proof: remote_proof.proof.clone(),
roots: vec![(begin - 1, Default::default())].into_iter().collect(),
roots_proof: StorageProof::empty(),
}
)
.is_err());
assert!(local_checker
.check_changes_proof(
&request,
ChangesProof {
max_block: remote_proof.max_block,
proof: remote_proof.proof.clone(),
roots: vec![(end + 1, Default::default())].into_iter().collect(),
roots_proof: StorageProof::empty(),
}
)
.is_err());
}
#[test]
fn check_changes_tries_proof_fails_if_proof_is_wrong() {
// we're testing this test case here:
// (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]),
let (remote_client, remote_roots, _) = prepare_client_with_key_changes();
let local_cht_root = cht::compute_root::<Header, BlakeTwo256, _>(
4,
0,
remote_roots.iter().cloned().map(|ct| Ok(Some(ct))),
)
.unwrap();
let dave = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Dave.into())).to_vec();
let dave = StorageKey(dave);
// 'fetch' changes proof from remote node:
// we're fetching changes for range b1..b4
// we do not know changes trie roots before b3 (i.e. we only know b3+b4)
// but we have changes trie CHT root for b1...b4
let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap();
let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap();
let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap();
let remote_proof = remote_client
.key_changes_proof_with_cht_size(b1, b4, b3, b4, None, &dave, 4)
.unwrap();
// fails when changes trie CHT is missing from the local db
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
assert!(local_checker
.check_changes_tries_proof(4, &remote_proof.roots, remote_proof.roots_proof.clone())
.is_err());
// fails when proof is broken
let mut local_storage = DummyStorage::new();
local_storage.changes_tries_cht_roots.insert(0, local_cht_root);
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(local_storage)),
local_executor(),
Box::new(TaskExecutor::new()),
);
let result =
local_checker.check_changes_tries_proof(4, &remote_proof.roots, StorageProof::empty());
assert!(result.is_err());
}
#[test]
fn check_body_proof_faulty() {
let header =
header_with_computed_extrinsics_root(vec![Extrinsic::IncludeData(vec![1, 2, 3, 4])]);
let block = Block::new(header.clone(), Vec::new());
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
let body_request = RemoteBodyRequest { header: header.clone(), retry_count: None };
assert!(
local_checker.check_body_proof(&body_request, block.extrinsics).is_err(),
"vec![1, 2, 3, 4] != vec![]"
);
}
#[test]
fn check_body_proof_of_same_data_should_succeed() {
let extrinsics = vec![Extrinsic::IncludeData(vec![1, 2, 3, 4, 5, 6, 7, 8, 255])];
let header = header_with_computed_extrinsics_root(extrinsics.clone());
let block = Block::new(header.clone(), extrinsics);
let local_checker = TestChecker::new(
Arc::new(DummyBlockchain::new(DummyStorage::new())),
local_executor(),
Box::new(TaskExecutor::new()),
);
let body_request = RemoteBodyRequest { header: header.clone(), retry_count: None };
assert!(local_checker.check_body_proof(&body_request, block.extrinsics).is_ok());
}
@@ -57,9 +57,6 @@ use substrate_test_runtime_client::{
Sr25519Keyring, TestClientBuilder, TestClientBuilderExt,
};
mod db;
mod light;
const TEST_ENGINE_ID: ConsensusEngineId = *b"TEST";
pub struct ExecutorDispatch;
+41 -153
View File
@@ -33,8 +33,9 @@ use sc_service::{
SpawnTaskHandle, TaskManager, TransactionStorageMode,
};
use sc_transaction_pool_api::TransactionPool;
use sp_api::BlockId;
use sp_blockchain::HeaderBackend;
use sp_runtime::{generic::BlockId, traits::Block as BlockT};
use sp_runtime::traits::Block as BlockT;
use std::{iter, net::Ipv4Addr, pin::Pin, sync::Arc, task::Context, time::Duration};
use tempfile::TempDir;
use tokio::{runtime::Runtime, time};
@@ -45,22 +46,20 @@ mod client;
/// Maximum duration of single wait call.
const MAX_WAIT_TIME: Duration = Duration::from_secs(60 * 3);
struct TestNet<G, E, F, L, U> {
struct TestNet<G, E, F, U> {
runtime: Runtime,
authority_nodes: Vec<(usize, F, U, Multiaddr)>,
full_nodes: Vec<(usize, F, U, Multiaddr)>,
light_nodes: Vec<(usize, L, Multiaddr)>,
chain_spec: GenericChainSpec<G, E>,
base_port: u16,
nodes: usize,
}
impl<G, E, F, L, U> Drop for TestNet<G, E, F, L, U> {
impl<G, E, F, U> Drop for TestNet<G, E, F, U> {
fn drop(&mut self) {
// Drop the nodes before dropping the runtime, as the runtime otherwise waits for all
// futures to be ended and we run into a dead lock.
self.full_nodes.drain(..);
self.light_nodes.drain(..);
self.authority_nodes.drain(..);
}
}
@@ -156,39 +155,26 @@ where
}
}
impl<G, E, F, L, U> TestNet<G, E, F, L, U>
impl<G, E, F, U> TestNet<G, E, F, U>
where
F: Clone + Send + 'static,
L: Clone + Send + 'static,
U: Clone + Send + 'static,
{
pub fn run_until_all_full<FP, LP>(&mut self, full_predicate: FP, light_predicate: LP)
pub fn run_until_all_full<FP>(&mut self, full_predicate: FP)
where
FP: Send + Fn(usize, &F) -> bool + 'static,
LP: Send + Fn(usize, &L) -> bool + 'static,
{
let full_nodes = self.full_nodes.clone();
let light_nodes = self.light_nodes.clone();
let future = async move {
let mut interval = time::interval(Duration::from_millis(100));
loop {
interval.tick().await;
let full_ready = full_nodes
if full_nodes
.iter()
.all(|&(ref id, ref service, _, _)| full_predicate(*id, service));
if !full_ready {
continue
}
let light_ready = light_nodes
.iter()
.all(|&(ref id, ref service, _)| light_predicate(*id, service));
if light_ready {
return
.all(|&(ref id, ref service, _, _)| full_predicate(*id, service))
{
break
}
}
};
@@ -278,10 +264,9 @@ fn node_config<
}
}
impl<G, E, F, L, U> TestNet<G, E, F, L, U>
impl<G, E, F, U> TestNet<G, E, F, U>
where
F: TestNetNode,
L: TestNetNode,
E: ChainSpecExtension + Clone + 'static + Send + Sync,
G: RuntimeGenesis + 'static,
{
@@ -289,10 +274,9 @@ where
temp: &TempDir,
spec: GenericChainSpec<G, E>,
full: impl Iterator<Item = impl FnOnce(Configuration) -> Result<(F, U), Error>>,
light: impl Iterator<Item = impl FnOnce(Configuration) -> Result<L, Error>>,
authorities: impl Iterator<Item = (String, impl FnOnce(Configuration) -> Result<(F, U), Error>)>,
base_port: u16,
) -> TestNet<G, E, F, L, U> {
) -> TestNet<G, E, F, U> {
sp_tracing::try_init_simple();
fdlimit::raise_fd_limit();
let runtime = Runtime::new().expect("Error creating tokio runtime");
@@ -300,12 +284,11 @@ where
runtime,
authority_nodes: Default::default(),
full_nodes: Default::default(),
light_nodes: Default::default(),
chain_spec: spec,
base_port,
nodes: 0,
};
net.insert_nodes(temp, full, light, authorities);
net.insert_nodes(temp, full, authorities);
net
}
@@ -313,7 +296,6 @@ where
&mut self,
temp: &TempDir,
full: impl Iterator<Item = impl FnOnce(Configuration) -> Result<(F, U), Error>>,
light: impl Iterator<Item = impl FnOnce(Configuration) -> Result<L, Error>>,
authorities: impl Iterator<Item = (String, impl FnOnce(Configuration) -> Result<(F, U), Error>)>,
) {
let handle = self.runtime.handle().clone();
@@ -358,26 +340,6 @@ where
self.full_nodes.push((self.nodes, service, user_data, addr));
self.nodes += 1;
}
for light in light {
let node_config = node_config(
self.nodes,
&self.chain_spec,
Role::Light,
handle.clone(),
None,
self.base_port,
&temp,
);
let addr = node_config.network.listen_addresses.iter().next().unwrap().clone();
let service = light(node_config).expect("Error creating test node service");
handle.spawn(service.clone().map_err(|_| ()));
let addr = addr
.with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into()));
self.light_nodes.push((self.nodes, service, addr));
self.nodes += 1;
}
}
}
@@ -388,23 +350,16 @@ fn tempdir_with_prefix(prefix: &str) -> TempDir {
.expect("Error creating test dir")
}
pub fn connectivity<G, E, Fb, F, Lb, L>(
spec: GenericChainSpec<G, E>,
full_builder: Fb,
light_builder: Lb,
) where
pub fn connectivity<G, E, Fb, F>(spec: GenericChainSpec<G, E>, full_builder: Fb)
where
E: ChainSpecExtension + Clone + 'static + Send + Sync,
G: RuntimeGenesis + 'static,
Fb: Fn(Configuration) -> Result<F, Error>,
F: TestNetNode,
Lb: Fn(Configuration) -> Result<L, Error>,
L: TestNetNode,
{
const NUM_FULL_NODES: usize = 5;
const NUM_LIGHT_NODES: usize = 5;
let expected_full_connections = NUM_FULL_NODES - 1 + NUM_LIGHT_NODES;
let expected_light_connections = NUM_FULL_NODES;
let expected_full_connections = NUM_FULL_NODES - 1;
{
let temp = tempdir_with_prefix("substrate-connectivity-test");
@@ -413,7 +368,6 @@ pub fn connectivity<G, E, Fb, F, Lb, L>(
&temp,
spec.clone(),
(0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))),
(0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)),
// Note: this iterator is empty but we can't just use `iter::empty()`, otherwise
// the type of the closure cannot be inferred.
(0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })),
@@ -427,25 +381,12 @@ pub fn connectivity<G, E, Fb, F, Lb, L>(
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
for (_, service, _) in network.light_nodes.iter() {
service
.network()
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
network.run_until_all_full(
move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} full connections...", connected, expected_full_connections);
connected == expected_full_connections
},
move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} light connections...", connected, expected_light_connections);
connected == expected_light_connections
},
);
network.run_until_all_full(move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} full connections...", connected, expected_full_connections);
connected == expected_full_connections
});
};
temp.close().expect("Error removing temp dir");
@@ -457,7 +398,6 @@ pub fn connectivity<G, E, Fb, F, Lb, L>(
&temp,
spec,
(0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))),
(0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)),
// Note: this iterator is empty but we can't just use `iter::empty()`, otherwise
// the type of the closure cannot be inferred.
(0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })),
@@ -465,8 +405,7 @@ pub fn connectivity<G, E, Fb, F, Lb, L>(
);
info!("Checking linked topology");
let mut address = network.full_nodes[0].3.clone();
let max_nodes = std::cmp::max(NUM_FULL_NODES, NUM_LIGHT_NODES);
for i in 0..max_nodes {
for i in 0..NUM_FULL_NODES {
if i != 0 {
if let Some((_, service, _, node_id)) = network.full_nodes.get(i) {
service
@@ -476,44 +415,26 @@ pub fn connectivity<G, E, Fb, F, Lb, L>(
address = node_id.clone();
}
}
if let Some((_, service, node_id)) = network.light_nodes.get(i) {
service
.network()
.add_reserved_peer(address.to_string())
.expect("Error adding reserved peer");
address = node_id.clone();
}
}
network.run_until_all_full(
move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} full connections...", connected, expected_full_connections);
connected == expected_full_connections
},
move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} light connections...", connected, expected_light_connections);
connected == expected_light_connections
},
);
network.run_until_all_full(move |_index, service| {
let connected = service.network().num_connected();
debug!("Got {}/{} full connections...", connected, expected_full_connections);
connected == expected_full_connections
});
}
temp.close().expect("Error removing temp dir");
}
}
pub fn sync<G, E, Fb, F, Lb, L, B, ExF, U>(
pub fn sync<G, E, Fb, F, B, ExF, U>(
spec: GenericChainSpec<G, E>,
full_builder: Fb,
light_builder: Lb,
mut make_block_and_import: B,
mut extrinsic_factory: ExF,
) where
Fb: Fn(Configuration) -> Result<(F, U), Error>,
F: TestNetNode,
Lb: Fn(Configuration) -> Result<L, Error>,
L: TestNetNode,
B: FnMut(&F, &mut U),
ExF: FnMut(&F, &U) -> <F::Block as BlockT>::Extrinsic,
U: Clone + Send + 'static,
@@ -521,15 +442,12 @@ pub fn sync<G, E, Fb, F, Lb, L, B, ExF, U>(
G: RuntimeGenesis + 'static,
{
const NUM_FULL_NODES: usize = 10;
// FIXME: BABE light client support is currently not working.
const NUM_LIGHT_NODES: usize = 10;
const NUM_BLOCKS: usize = 512;
let temp = tempdir_with_prefix("substrate-sync-test");
let mut network = TestNet::new(
&temp,
spec,
(0..NUM_FULL_NODES).map(|_| |cfg| full_builder(cfg)),
(0..NUM_LIGHT_NODES).map(|_| |cfg| light_builder(cfg)),
// Note: this iterator is empty but we can't just use `iter::empty()`, otherwise
// the type of the closure cannot be inferred.
(0..0).map(|_| (String::new(), { |cfg| full_builder(cfg) })),
@@ -560,16 +478,10 @@ pub fn sync<G, E, Fb, F, Lb, L, B, ExF, U>(
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
for (_, service, _) in network.light_nodes.iter() {
service
.network()
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
network.run_until_all_full(
|_index, service| service.client().info().best_number == (NUM_BLOCKS as u32).into(),
|_index, service| service.client().info().best_number == (NUM_BLOCKS as u32).into(),
);
network.run_until_all_full(|_index, service| {
service.client().info().best_number == (NUM_BLOCKS as u32).into()
});
info!("Checking extrinsic propagation");
let first_service = network.full_nodes[0].1.clone();
@@ -585,34 +497,26 @@ pub fn sync<G, E, Fb, F, Lb, L, B, ExF, U>(
))
.expect("failed to submit extrinsic");
network.run_until_all_full(
|_index, service| service.transaction_pool().ready().count() == 1,
|_index, _service| true,
);
network.run_until_all_full(|_index, service| service.transaction_pool().ready().count() == 1);
}
pub fn consensus<G, E, Fb, F, Lb, L>(
pub fn consensus<G, E, Fb, F>(
spec: GenericChainSpec<G, E>,
full_builder: Fb,
light_builder: Lb,
authorities: impl IntoIterator<Item = String>,
) where
Fb: Fn(Configuration) -> Result<F, Error>,
F: TestNetNode,
Lb: Fn(Configuration) -> Result<L, Error>,
L: TestNetNode,
E: ChainSpecExtension + Clone + 'static + Send + Sync,
G: RuntimeGenesis + 'static,
{
const NUM_FULL_NODES: usize = 10;
const NUM_LIGHT_NODES: usize = 10;
const NUM_BLOCKS: usize = 10; // 10 * 2 sec block production time = ~20 seconds
let temp = tempdir_with_prefix("substrate-consensus-test");
let mut network = TestNet::new(
&temp,
spec,
(0..NUM_FULL_NODES / 2).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))),
(0..NUM_LIGHT_NODES / 2).map(|_| |cfg| light_builder(cfg)),
authorities
.into_iter()
.map(|key| (key, { |cfg| full_builder(cfg).map(|s| (s, ())) })),
@@ -627,30 +531,20 @@ pub fn consensus<G, E, Fb, F, Lb, L>(
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
for (_, service, _) in network.light_nodes.iter() {
service
.network()
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
for (_, service, _, _) in network.authority_nodes.iter().skip(1) {
service
.network()
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
network.run_until_all_full(
|_index, service| {
service.client().info().finalized_number >= (NUM_BLOCKS as u32 / 2).into()
},
|_index, service| service.client().info().best_number >= (NUM_BLOCKS as u32 / 2).into(),
);
network.run_until_all_full(|_index, service| {
service.client().info().finalized_number >= (NUM_BLOCKS as u32 / 2).into()
});
info!("Adding more peers");
network.insert_nodes(
&temp,
(0..NUM_FULL_NODES / 2).map(|_| |cfg| full_builder(cfg).map(|s| (s, ()))),
(0..NUM_LIGHT_NODES / 2).map(|_| |cfg| light_builder(cfg)),
// Note: this iterator is empty but we can't just use `iter::empty()`, otherwise
// the type of the closure cannot be inferred.
(0..0).map(|_| (String::new(), { |cfg| full_builder(cfg).map(|s| (s, ())) })),
@@ -661,14 +555,8 @@ pub fn consensus<G, E, Fb, F, Lb, L>(
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
for (_, service, _) in network.light_nodes.iter() {
service
.network()
.add_reserved_peer(first_address.to_string())
.expect("Error adding reserved peer");
}
network.run_until_all_full(
|_index, service| service.client().info().finalized_number >= (NUM_BLOCKS as u32).into(),
|_index, service| service.client().info().best_number >= (NUM_BLOCKS as u32).into(),
);
network.run_until_all_full(|_index, service| {
service.client().info().finalized_number >= (NUM_BLOCKS as u32).into()
});
}