mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
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:
Generated
-1
@@ -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,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)
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)) => {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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::{
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -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>>>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user