From 79f5e0a2a82e7b65addbd0b9005154c97ee7f265 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 2 May 2018 13:46:58 +0300 Subject: [PATCH 1/9] light client structure + remote call requests --- Cargo.lock | 18 +- demo/cli/src/lib.rs | 105 ++--- polkadot/api/src/lib.rs | 52 +-- polkadot/api/src/light.rs | 89 +++++ polkadot/cli/src/cli.yml | 4 + polkadot/cli/src/informant.rs | 10 +- polkadot/cli/src/lib.rs | 41 +- polkadot/consensus/src/service.rs | 8 +- polkadot/service/Cargo.toml | 1 + polkadot/service/src/lib.rs | 109 +++-- substrate/client/db/src/lib.rs | 15 +- substrate/client/src/backend.rs | 6 +- substrate/client/src/block_builder.rs | 20 +- substrate/client/src/call_executor.rs | 191 +++++++++ substrate/client/src/client.rs | 116 +++--- substrate/client/src/error.rs | 12 + substrate/client/src/in_mem.rs | 30 +- substrate/client/src/lib.rs | 10 +- substrate/client/src/light.rs | 187 +++++++++ substrate/network/Cargo.toml | 1 + substrate/network/src/chain.rs | 11 +- substrate/network/src/lib.rs | 3 + substrate/network/src/message.rs | 28 ++ substrate/network/src/on_demand.rs | 374 ++++++++++++++++++ substrate/network/src/protocol.rs | 41 +- substrate/network/src/service.rs | 19 +- substrate/network/src/sync.rs | 21 +- substrate/network/src/test/mod.rs | 2 +- substrate/rpc/src/chain/mod.rs | 2 +- substrate/rpc/src/chain/tests.rs | 6 +- substrate/rpc/src/state/mod.rs | 6 +- substrate/test-client/src/client_ext.rs | 24 +- substrate/test-client/src/lib.rs | 2 +- substrate/test-runtime/src/lib.rs | 1 + substrate/test-runtime/src/system.rs | 11 + substrate/test-runtime/wasm/genesis.wasm | Bin 32296 -> 17273 bytes .../substrate_test_runtime.compact.wasm | Bin 16186 -> 17273 bytes .../release/substrate_test_runtime.wasm | Bin 17932 -> 19217 bytes 38 files changed, 1335 insertions(+), 241 deletions(-) create mode 100644 polkadot/api/src/light.rs create mode 100644 substrate/client/src/call_executor.rs create mode 100644 substrate/client/src/light.rs create mode 100644 substrate/network/src/on_demand.rs diff --git a/Cargo.lock b/Cargo.lock index 976b1ea5ee5d2..998109c9ce5f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,11 @@ name = "libc" version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "linked-hash-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "local-encoding" version = "0.2.0" @@ -1415,6 +1420,7 @@ dependencies = [ "substrate-network 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", + "substrate-state-machine 0.1.0", "tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1939,7 +1945,7 @@ dependencies = [ "substrate-state-machine 0.1.0", "triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1974,6 +1980,7 @@ dependencies = [ "ethcore-network 1.12.0 (git+https://github.com/paritytech/parity.git)", "ethcore-network-devp2p 1.12.0 (git+https://github.com/paritytech/parity.git)", "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", + "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2008,7 +2015,7 @@ dependencies = [ "substrate-serializer 0.1.0", "twox-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "uint 0.1.2 (git+https://github.com/rphmeier/primitives.git?branch=compile-for-wasm)", - "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2154,7 +2161,7 @@ dependencies = [ "substrate-primitives 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-std 0.1.0", - "wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2622,7 +2629,7 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2804,6 +2811,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1ceb20f39ff7ae42f3ff9795f3986b1daad821caaa1e1732a0944103a5a1a66" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" @@ -2921,7 +2929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wabt 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e902997144209c90311321b90dd658d964dd8e58b23a5919e66a1d068a0050e5" "checksum wabt-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fc67b1d96cd7839be6996edf94be66351d83f614e9cc7c6edc33accd9f5e6529" -"checksum wasmi 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba06def0c95a653122299e68a44f2f227eeac2d1f707df61f33abbaf6dd55992" +"checksum wasmi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d19da510b59247935ad5f598357b3cc739912666d75d3d28318026478d95bbdb" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index 0330192b5816a..306f26e8885c5 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -82,56 +82,61 @@ pub fn run(args: I) -> error::Result<()> where // Create client let executor = demo_executor::Executor::new(); - let mut storage = Default::default(); - let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"]; - - let genesis_config = GenesisConfig { - consensus: Some(ConsensusConfig { - code: vec![], // TODO - authorities: vec![god_key.clone()], - }), - system: None, -// block_time: 5, // 5 second block time. - session: Some(SessionConfig { - validators: vec![god_key.clone()], - session_length: 720, // that's 1 hour per session. - }), - staking: Some(StakingConfig { - current_era: 0, - intentions: vec![], - transaction_fee: 100, - balances: vec![(god_key.clone(), 1u64 << 63)].into_iter().collect(), - validator_count: 12, - sessions_per_era: 24, // 24 hours per era. - bonding_duration: 90, // 90 days per bond. - }), - democracy: Some(DemocracyConfig { - launch_period: 120 * 24 * 14, // 2 weeks per public referendum - voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum - minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum - }), - council: Some(CouncilConfig { - active_council: vec![], - candidacy_bond: 1000, // 1000 to become a council candidate - voter_bond: 100, // 100 down to vote for a candidate - present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation. - carry_count: 24, // carry over the 24 runners-up to the next council election - presentation_duration: 120 * 24, // one day for presenting winners. - approval_voting_period: 7 * 120 * 24, // one week period between possible council elections. - term_duration: 180 * 120 * 24, // 180 day term duration for the council. - desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit. - inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped. - - cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal. - voting_period: 7 * 120 * 24, // 7 day voting period for council members. - }), - }; - let prepare_genesis = || { - storage = genesis_config.build_externalities(); - let block = genesis::construct_genesis_block(&storage); - (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) - }; - let client = Arc::new(client::new_in_mem(executor, prepare_genesis)?); + + struct GenesisBuilder; + + impl client::GenesisBuilder for GenesisBuilder { + fn build(self) -> (primitives::Header, Vec<(Vec, Vec)>) { + let god_key = hex!["3d866ec8a9190c8343c2fc593d21d8a6d0c5c4763aaab2349de3a6111d64d124"]; + let genesis_config = GenesisConfig { + consensus: Some(ConsensusConfig { + code: vec![], // TODO + authorities: vec![god_key.clone()], + }), + system: None, + // block_time: 5, // 5 second block time. + session: Some(SessionConfig { + validators: vec![god_key.clone()], + session_length: 720, // that's 1 hour per session. + }), + staking: Some(StakingConfig { + current_era: 0, + intentions: vec![], + transaction_fee: 100, + balances: vec![(god_key.clone(), 1u64 << 63)].into_iter().collect(), + validator_count: 12, + sessions_per_era: 24, // 24 hours per era. + bonding_duration: 90, // 90 days per bond. + }), + democracy: Some(DemocracyConfig { + launch_period: 120 * 24 * 14, // 2 weeks per public referendum + voting_period: 120 * 24 * 28, // 4 weeks to discuss & vote on an active referendum + minimum_deposit: 1000, // 1000 as the minimum deposit for a referendum + }), + council: Some(CouncilConfig { + active_council: vec![], + candidacy_bond: 1000, // 1000 to become a council candidate + voter_bond: 100, // 100 down to vote for a candidate + present_slash_per_voter: 1, // slash by 1 per voter for an invalid presentation. + carry_count: 24, // carry over the 24 runners-up to the next council election + presentation_duration: 120 * 24, // one day for presenting winners. + approval_voting_period: 7 * 120 * 24, // one week period between possible council elections. + term_duration: 180 * 120 * 24, // 180 day term duration for the council. + desired_seats: 0, // start with no council: we'll raise this once the stake has been dispersed a bit. + inactive_grace_period: 1, // one addition vote should go by before an inactive voter can be reaped. + + cooloff_period: 90 * 120 * 24, // 90 day cooling off period if council member vetoes a proposal. + voting_period: 7 * 120 * 24, // 7 day voting period for council members. + }), + }; + + let storage = genesis_config.build_externalities(); + let block = genesis::construct_genesis_block(&storage); + (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) + } + } + + let client = Arc::new(client::new_in_mem(executor, GenesisBuilder)?); let mut core = ::tokio_core::reactor::Core::new().expect("Unable to spawn event loop."); let _rpc_servers = { diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 666893585ddd8..c9e1efeced36e 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -34,8 +34,10 @@ extern crate error_chain; #[cfg(test)] extern crate substrate_keyring as keyring; +pub mod light; + use client::backend::Backend; -use client::Client; +use client::{Client, LocalCallExecutor}; use polkadot_executor::Executor as LocalDispatch; use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; use state_machine::OverlayedChanges; @@ -178,7 +180,7 @@ macro_rules! with_runtime { }} } -impl PolkadotApi for Client> +impl PolkadotApi for Client>> where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> { type CheckedBlockId = CheckedId; @@ -356,8 +358,10 @@ mod tests { use super::*; use keyring::Keyring; use codec::Slicable; + use client::{self, LocalCallExecutor}; use client::in_mem::Backend as InMemory; use substrate_executor::NativeExecutionDispatch; + use substrate_primitives::Header; use runtime::{GenesisConfig, ConsensusConfig, SessionConfig, BuildExternalities}; fn validators() -> Vec { @@ -367,30 +371,34 @@ mod tests { ] } - fn client() -> Client> { - let genesis_config = GenesisConfig { - consensus: Some(ConsensusConfig { - code: LocalDispatch::native_equivalent().to_vec(), - authorities: validators(), - }), - system: None, - session: Some(SessionConfig { - validators: validators(), - session_length: 100, - }), - council: Some(Default::default()), - democracy: Some(Default::default()), - parachains: Some(Default::default()), - staking: Some(Default::default()), - }; - ::client::new_in_mem( - LocalDispatch::new(), - || { + fn client() -> Client>> { + struct GenesisBuilder; + + impl client::GenesisBuilder for GenesisBuilder { + fn build(self) -> (Header, Vec<(Vec, Vec)>) { + let genesis_config = GenesisConfig { + consensus: Some(ConsensusConfig { + code: LocalDispatch::native_equivalent().to_vec(), + authorities: validators(), + }), + system: None, + session: Some(SessionConfig { + validators: validators(), + session_length: 100, + }), + council: Some(Default::default()), + democracy: Some(Default::default()), + parachains: Some(Default::default()), + staking: Some(Default::default()), + }; + let storage = genesis_config.build_externalities(); let block = ::client::genesis::construct_genesis_block(&storage); (substrate_primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) } - ).unwrap() + } + + ::client::new_in_mem(LocalDispatch::new(), GenesisBuilder).unwrap() } #[test] diff --git a/polkadot/api/src/light.rs b/polkadot/api/src/light.rs new file mode 100644 index 0000000000000..5d1195bbc83a4 --- /dev/null +++ b/polkadot/api/src/light.rs @@ -0,0 +1,89 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Strongly typed API for light Polkadot client. + +use std::sync::Arc; +use client::backend::Backend; +use client::{Client, CallExecutor}; +use codec::Slicable; +use state_machine; +use primitives::{AccountId, BlockId, Hash, Index, SessionKey, Timestamp}; +use primitives::parachain::DutyRoster; +use runtime::{Block, UncheckedExtrinsic}; +use {PolkadotApi, BlockBuilder, CheckedBlockId, CheckedId, Result, ErrorKind}; + +/// Remote polkadot API implementation. +pub struct RemotePolkadotApi(pub Arc>); + +/// Block builder for light client. +pub struct LightBlockBuilder; + +impl PolkadotApi for RemotePolkadotApi + where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ + type CheckedBlockId = CheckedId; + type BlockBuilder = LightBlockBuilder; + + fn check_id(&self, id: BlockId) -> Result { + Ok(CheckedId(id)) + } + + fn session_keys(&self, at: &CheckedId) -> Result> { + self.0.executor().call(at.block_id(), "authorities", &[]) + .and_then(|r| Vec::::decode(&mut &r.return_data[..]) + .ok_or("error decoding session keys".into())) + .map_err(Into::into) + } + + fn validators(&self, _at: &CheckedId) -> Result> { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn random_seed(&self, _at: &Self::CheckedBlockId) -> Result { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn duty_roster(&self, _at: &CheckedId) -> Result { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn timestamp(&self, _at: &CheckedId) -> Result { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn evaluate_block(&self, _at: &CheckedId, _block: Block) -> Result<()> { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn index(&self, _at: &CheckedId, _account: AccountId) -> Result { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn build_block(&self, _parent: &CheckedId, _timestamp: Timestamp) -> Result { + Err(ErrorKind::UnknownRuntime.into()) + } +} + +impl BlockBuilder for LightBlockBuilder { + fn push_extrinsic(&mut self, _extrinsic: UncheckedExtrinsic) -> Result<()> { + Err(ErrorKind::UnknownRuntime.into()) + } + + fn bake(self) -> Block { + unimplemented!() + } +} diff --git a/polkadot/cli/src/cli.yml b/polkadot/cli/src/cli.yml index c839ba94f7238..81ea904959b09 100644 --- a/polkadot/cli/src/cli.yml +++ b/polkadot/cli/src/cli.yml @@ -37,6 +37,10 @@ args: long: validator help: Enable validator mode takes_value: false + - light: + long: light + help: Run in light client mode + takes_value: false - port: long: port value_name: PORT diff --git a/polkadot/cli/src/informant.rs b/polkadot/cli/src/informant.rs index 70c49f9c448f4..54affa839ee61 100644 --- a/polkadot/cli/src/informant.rs +++ b/polkadot/cli/src/informant.rs @@ -23,12 +23,18 @@ use tokio_core::reactor; use network::{SyncState, SyncProvider}; use runtime_support::Hashable; use primitives::block::HeaderHash; -use client::BlockchainEvents; +use state_machine; +use client::{self, BlockchainEvents}; const TIMER_INTERVAL_MS: u64 = 5000; /// Spawn informant on the event loop -pub fn start(service: &Service, handle: reactor::Handle) { +pub fn start(service: &Service, handle: reactor::Handle) + where + B: client::backend::Backend + Send + Sync + 'static, + E: client::CallExecutor + Send + Sync + 'static, + client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ let interval = reactor::Interval::new_at(Instant::now(), Duration::from_millis(TIMER_INTERVAL_MS), &handle) .expect("Error creating informant timer"); diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs index 604c665910230..a30d8ee18f554 100644 --- a/polkadot/cli/src/lib.rs +++ b/polkadot/cli/src/lib.rs @@ -107,16 +107,7 @@ pub fn run(args: I) -> error::Result<()> where I: IntoIterator, T: Into + Clone, { - let mut core = reactor::Core::new().expect("tokio::Core could not be created"); - let exit = { - // can't use signal directly here because CtrlC takes only `Fn`. - let (exit_send, exit) = mpsc::channel(1); - ctrlc::CtrlC::set_handler(move || { - exit_send.clone().send(()).wait().expect("Error sending exit notification"); - }); - - exit - }; + let core = reactor::Core::new().expect("tokio::Core could not be created"); let yaml = load_yaml!("./cli.yml"); let matches = match clap::App::from_yaml(yaml).version(crate_version!()).get_matches_from_safe(args) { @@ -157,6 +148,10 @@ pub fn run(args: I) -> error::Result<()> where info!("Starting validator."); role = service::Role::VALIDATOR; } + else if matches.is_present("light") { + info!("Starting light."); + role = service::Role::LIGHT; + } match matches.value_of("chain") { Some("dev") => config.chain_spec = ChainSpec::Development, @@ -195,13 +190,33 @@ pub fn run(args: I) -> error::Result<()> where config.keys = matches.values_of("key").unwrap_or_default().map(str::to_owned).collect(); - let service = service::Service::new(config)?; + match role == service::Role::LIGHT { + true => run_until_exit(core, service::new_light(config)?, &matches), + false => run_until_exit(core, service::new_full(config)?, &matches), + } +} + +fn run_until_exit(mut core: reactor::Core, service: service::Service, matches: &clap::ArgMatches) -> error::Result<()> + where + B: client::backend::Backend + Send + Sync + 'static, + E: client::CallExecutor + Send + Sync + 'static, + client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ + let exit = { + // can't use signal directly here because CtrlC takes only `Fn`. + let (exit_send, exit) = mpsc::channel(1); + ctrlc::CtrlC::set_handler(move || { + exit_send.clone().send(()).wait().expect("Error sending exit notification"); + }); + + exit + }; informant::start(&service, core.handle()); let _rpc_servers = { - let http_address = parse_address("127.0.0.1:9933", "rpc-port", &matches)?; - let ws_address = parse_address("127.0.0.1:9944", "ws-port", &matches)?; + let http_address = parse_address("127.0.0.1:9933", "rpc-port", matches)?; + let ws_address = parse_address("127.0.0.1:9944", "ws-port", matches)?; let handler = || { let chain = rpc::apis::chain::Chain::new(service.client(), core.remote()); diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index 97384040e9c86..b58b2e361b78f 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -220,21 +220,23 @@ pub struct Service { impl Service { /// Create and start a new instance. - pub fn new( + pub fn new( client: Arc, + api: Arc, network: Arc, transaction_pool: Arc>, key: ed25519::Pair, ) -> Service where - C: BlockchainEvents + ChainHead + bft::BlockImport + bft::Authorities + PolkadotApi + Send + Sync + 'static, + A: PolkadotApi + Send + Sync + 'static, + C: BlockchainEvents + ChainHead + bft::BlockImport + bft::Authorities + Send + Sync + 'static, { let (signal, exit) = ::exit_future::signal(); let thread = thread::spawn(move || { let mut core = reactor::Core::new().expect("tokio::Core could not be created"); let key = Arc::new(key); let factory = ProposerFactory { - client: client.clone(), + client: api.clone(), transaction_pool: transaction_pool.clone(), network: Network(network.clone()), handle: core.handle(), diff --git a/polkadot/service/Cargo.toml b/polkadot/service/Cargo.toml index 469dc2e2018db..bb9fb170bd2d4 100644 --- a/polkadot/service/Cargo.toml +++ b/polkadot/service/Cargo.toml @@ -27,3 +27,4 @@ substrate-client = { path = "../../substrate/client" } substrate-client-db = { path = "../../substrate/client/db" } substrate-codec = { path = "../../substrate/codec" } substrate-executor = { path = "../../substrate/executor" } +substrate-state-machine = { path = "../../substrate/state-machine" } diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index bcb454260e933..95b55118ee8a3 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -35,6 +35,7 @@ extern crate substrate_network as network; extern crate substrate_codec as codec; extern crate substrate_client_db as client_db; extern crate substrate_executor; +extern crate substrate_state_machine as state_machine; extern crate exit_future; extern crate tokio_core; @@ -55,7 +56,7 @@ use futures::prelude::*; use parking_lot::Mutex; use tokio_core::reactor::Core; use codec::Slicable; -use primitives::block::{Id as BlockId, Extrinsic, ExtrinsicHash, HeaderHash}; +use primitives::block::{Id as BlockId, Extrinsic, ExtrinsicHash, HeaderHash, Header}; use primitives::{AuthorityId, hashing}; use transaction_pool::TransactionPool; use substrate_executor::NativeExecutor; @@ -64,31 +65,39 @@ use keystore::Store as Keystore; use polkadot_api::PolkadotApi; use polkadot_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig, StakingConfig, BuildExternalities}; -use client::{genesis, BlockchainEvents}; +use client::backend::Backend; +use client::{genesis, Client, BlockchainEvents, CallExecutor}; use network::ManageNetwork; use exit_future::Signal; pub use self::error::{ErrorKind, Error}; pub use config::{Configuration, Role, ChainSpec}; -type Client = client::Client>; +type CodeExecutor = NativeExecutor; /// Polkadot service. -pub struct Service { +pub struct Service { thread: Option>, - client: Arc, + client: Arc>, network: Arc, transaction_pool: Arc>, signal: Option, _consensus: Option, } -struct TransactionPoolAdapter { +struct TransactionPoolAdapter where A: Send + Sync, E: Send + Sync { pool: Arc>, - client: Arc, + client: Arc>, + api: Arc, } -impl network::TransactionPool for TransactionPoolAdapter { +impl network::TransactionPool for TransactionPoolAdapter + where + B: Backend + Send + Sync, + E: client::CallExecutor + Send + Sync, + client::error::Error: From<<::State as state_machine::backend::Backend>::Error>, + A: PolkadotApi + Send + Sync, +{ fn transactions(&self) -> Vec<(ExtrinsicHash, Vec)> { let best_block = match self.client.info() { Ok(info) => info.chain.best_hash, @@ -97,10 +106,11 @@ impl network::TransactionPool for TransactionPoolAdapter { return Vec::new(); } }; - let id = self.client.check_id(BlockId::Hash(best_block)).expect("Best block is always valid; qed."); + + let id = self.api.check_id(BlockId::Hash(best_block)).expect("Best block is always valid; qed."); let mut pool = self.pool.lock(); - pool.cull(None, transaction_pool::Ready::create(id, &*self.client)); - pool.pending(transaction_pool::Ready::create(id, &*self.client)).map(|t| { + pool.cull(None, transaction_pool::Ready::create(id.clone(), &*self.api)); + pool.pending(transaction_pool::Ready::create(id, &*self.api)).map(|t| { let hash = ::primitives::Hash::from(&t.hash()[..]); let tx = codec::Slicable::encode(t.as_transaction()); (hash, tx) @@ -257,16 +267,62 @@ fn local_testnet_config() -> ChainConfig { ]) } -impl Service { +struct GenesisBuilder { + config: GenesisConfig, +} + +impl client::GenesisBuilder for GenesisBuilder { + fn build(self) -> (Header, Vec<(Vec, Vec)>) { + let storage = self.config.build_externalities(); + let block = genesis::construct_genesis_block(&storage); + (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) + } +} + +/// Creates light client and register protocol with the network service +pub fn new_light(config: Configuration) -> Result>, error::Error> { // TODO: light + Service::new(move |_, executor, genesis_builder: GenesisBuilder| { + let fetcher = Arc::new(network::OnDemand::new()); + let client = client::light::new_light(fetcher.clone(), executor, genesis_builder)?; + Ok((Arc::new(client), Some(fetcher))) + }, + |client| Arc::new(polkadot_api::light::RemotePolkadotApi(client.clone())), + config) +} + +/// Creates full client and register protocol with the network service +pub fn new_full(config: Configuration) -> Result>, error::Error> { + Service::new(|db_settings, executor, genesis_builder: GenesisBuilder| + Ok((Arc::new(client_db::new_client(db_settings, executor, genesis_builder)?), None)), + |client| client.clone(), + config) +} + +impl Service + where + B: Backend + Send + Sync + 'static, + E: CallExecutor + Send + Sync + 'static, + client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ /// Creates and register protocol with the network service - pub fn new(mut config: Configuration) -> Result { + fn new(client_creator: F, api_creator: G, mut config: Configuration) -> Result + where + F: FnOnce( + client_db::DatabaseSettings, + CodeExecutor, + GenesisBuilder, + ) -> Result<(Arc>, Option>>), error::Error>, + G: Fn( + Arc>, + ) -> Arc, + A: PolkadotApi + Send + Sync + 'static, + { use std::sync::Barrier; let (signal, exit) = ::exit_future::signal(); // Create client let executor = polkadot_executor::Executor::new(); - let mut storage = Default::default(); let mut keystore = Keystore::open(config.keystore_path.into())?; for seed in &config.keys { @@ -285,10 +341,8 @@ impl Service { }; config.network.boot_nodes.extend(boot_nodes); - let prepare_genesis = || { - storage = genesis_config.build_externalities(); - let block = genesis::construct_genesis_block(&storage); - (primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) + let genesis_builder = GenesisBuilder { + config: genesis_config, }; let db_settings = client_db::DatabaseSettings { @@ -296,13 +350,15 @@ impl Service { path: config.database_path.into(), }; - let client = Arc::new(client_db::new_client(db_settings, executor, prepare_genesis)?); + let (client, on_demand) = client_creator(db_settings, executor, genesis_builder)?; + let api = api_creator(client.clone()); let best_header = client.best_block_header()?; info!("Starting Polkadot. Best block is #{}", best_header.number); let transaction_pool = Arc::new(Mutex::new(TransactionPool::new(config.transaction_pool))); let transaction_pool_adapter = Arc::new(TransactionPoolAdapter { pool: transaction_pool.clone(), client: client.clone(), + api: api.clone(), }); let network_params = network::Params { config: network::ProtocolConfig { @@ -310,11 +366,13 @@ impl Service { }, network_config: config.network, chain: client.clone(), + on_demand: on_demand.clone().map(|d| d as Arc), transaction_pool: transaction_pool_adapter, }; let network = network::Service::new(network_params)?; let barrier = ::std::sync::Arc::new(Barrier::new(2)); + on_demand.map(|on_demand| on_demand.set_service_link(Arc::downgrade(&network))); let thread = { let client = client.clone(); @@ -351,7 +409,7 @@ impl Service { // Load the first available key. Code above makes sure it exisis. let key = keystore.load(&keystore.contents()?[0], "")?; info!("Using authority key {:?}", key.public()); - Some(consensus::Service::new(client.clone(), network.clone(), transaction_pool.clone(), key)) + Some(consensus::Service::new(client.clone(), api, network.clone(), transaction_pool.clone(), key)) } else { None }; @@ -367,7 +425,7 @@ impl Service { } /// Get shared client instance. - pub fn client(&self) -> Arc { + pub fn client(&self) -> Arc> { self.client.clone() } @@ -390,7 +448,12 @@ fn prune_transactions(pool: &mut TransactionPool, extrinsics: &[Extrinsic]) { } /// Produce a task which prunes any finalized transactions from the pool. -pub fn prune_imported(client: &Client, pool: &Mutex, hash: HeaderHash) { +pub fn prune_imported(client: &Client, pool: &Mutex, hash: HeaderHash) + where + B: Backend + Send + Sync, + E: CallExecutor + Send + Sync, + client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ let id = BlockId::Hash(hash); match client.body(&id) { Ok(Some(body)) => prune_transactions(&mut *pool.lock(), &body[..]), @@ -399,7 +462,7 @@ pub fn prune_imported(client: &Client, pool: &Mutex, hash: Head } } -impl Drop for Service { +impl Drop for Service { fn drop(&mut self) { self.network.stop_network(); diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 5a21021862944..40df3b6f49bcf 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -65,14 +65,15 @@ pub struct DatabaseSettings { pub fn new_client( settings: DatabaseSettings, executor: E, - build_genesis: F -) -> Result, client::error::Error> + genesis_builder: F, +) -> Result>, client::error::Error> where E: CodeExecutor, - F: FnOnce() -> (block::Header, Vec<(Vec, Vec)>) + F: client::GenesisBuilder, { - let backend = Backend::new(&settings)?; - Ok(client::Client::new(backend, executor, build_genesis)?) + let backend = Arc::new(Backend::new(&settings)?); + let executor = client::LocalCallExecutor::new(backend.clone(), executor); + Ok(client::Client::new(backend, executor, genesis_builder)?) } mod columns { @@ -265,8 +266,8 @@ pub struct BlockImportOperation { impl client::backend::BlockImportOperation for BlockImportOperation { type State = DbState; - fn state(&self) -> Result<&Self::State, client::error::Error> { - Ok(&self.old_state) + fn state(&self) -> Result, client::error::Error> { + Ok(Some(&self.old_state)) } fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_best: bool) -> Result<(), client::error::Error> { diff --git a/substrate/client/src/backend.rs b/substrate/client/src/backend.rs index dad95dc0fdbee..9788fc2b7c80e 100644 --- a/substrate/client/src/backend.rs +++ b/substrate/client/src/backend.rs @@ -26,8 +26,8 @@ pub trait BlockImportOperation { /// Associated state backend type. type State: StateBackend; - /// Returns pending state. - fn state(&self) -> error::Result<&Self::State>; + /// Returns pending state. Returns None for backends with locally-unavailable state data. + fn state(&self) -> error::Result>; /// Append block data to the transaction. fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_new_best: bool) -> error::Result<()>; /// Inject storage data into the database. @@ -44,7 +44,7 @@ pub trait BlockImportOperation { /// /// The same applies for live `BlockImportOperation`s: while an import operation building on a parent `P` /// is alive, the state for `P` should not be pruned. -pub trait Backend { +pub trait Backend: Send + Sync { /// Associated block insertion operation type. type BlockImportOperation: BlockImportOperation; /// Associated blockchain backend type. diff --git a/substrate/client/src/block_builder.rs b/substrate/client/src/block_builder.rs index 5190a01634677..37e26a6a27e3b 100644 --- a/substrate/client/src/block_builder.rs +++ b/substrate/client/src/block_builder.rs @@ -18,16 +18,16 @@ use std::vec::Vec; use codec::{Joiner, Slicable}; -use state_machine::{self, CodeExecutor}; +use state_machine; use primitives::{Header, Block}; use primitives::block::{Id as BlockId, Extrinsic}; -use {backend, error, Client}; +use {backend, error, Client, CallExecutor}; use triehash::ordered_trie_root; /// Utility for building new (valid) blocks from a stream of transactions. pub struct BlockBuilder where B: backend::Backend, - E: CodeExecutor + Clone, + E: CallExecutor + Clone, error::Error: From<<::State as state_machine::backend::Backend>::Error>, { header: Header, @@ -39,7 +39,7 @@ pub struct BlockBuilder where impl BlockBuilder where B: backend::Backend, - E: CodeExecutor + Clone, + E: CallExecutor + Clone, error::Error: From<<::State as state_machine::backend::Backend>::Error>, { /// Create a new instance of builder from the given client, building on the latest block. @@ -59,7 +59,7 @@ impl BlockBuilder where digest: Default::default(), }, transactions: Default::default(), - executor: client.clone_executor(), + executor: client.executor().clone(), state: client.state_at(block_id)?, changes: Default::default(), }) @@ -69,7 +69,10 @@ impl BlockBuilder where /// can be validly executed (by executing it); if it is invalid, it'll be returned along with /// the error. Otherwise, it will return a mutable reference to self (in order to chain). pub fn push(&mut self, tx: Extrinsic) -> error::Result<()> { - let (output, _) = state_machine::execute(&self.state, &mut self.changes, &self.executor, "execute_transaction", + let (output, _) = self.executor.call_at_state( + &self.state, + &mut self.changes, + "execute_transaction", &vec![].and(&self.header).and(&tx))?; self.header = Header::decode(&mut &output[..]).expect("Header came straight out of runtime so must be valid"); self.transactions.push(tx); @@ -79,7 +82,10 @@ impl BlockBuilder where /// Consume the builder to return a valid `Block` containing all pushed transactions. pub fn bake(mut self) -> error::Result { self.header.extrinsics_root = ordered_trie_root(self.transactions.iter().map(Slicable::encode)).0.into(); - let (output, _) = state_machine::execute(&self.state, &mut self.changes, &self.executor, "finalise_block", + let (output, _) = self.executor.call_at_state( + &self.state, + &mut self.changes, + "finalise_block", &self.header.encode())?; self.header = Header::decode(&mut &output[..]).expect("Header came straight out of runtime so must be valid"); Ok(Block { diff --git a/substrate/client/src/call_executor.rs b/substrate/client/src/call_executor.rs new file mode 100644 index 0000000000000..3ba06eb138e6c --- /dev/null +++ b/substrate/client/src/call_executor.rs @@ -0,0 +1,191 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +use std::sync::Arc; +use primitives::block::Id as BlockId; +use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor}; +use state_machine::backend::InMemory as InMemoryStateBackend; +use triehash::trie_root; + +use backend; +use blockchain::Backend as ChainBackend; +use error; +use light::Fetcher; + +/// Information regarding the result of a call. +#[derive(Debug)] +pub struct CallResult { + /// The data that was returned from the call. + pub return_data: Vec, + /// The changes made to the state by the call. + pub changes: OverlayedChanges, +} + +/// Method call executor. +pub trait CallExecutor { + /// Externalities error type. + type Error: state_machine::Error; + + /// Execute a call to a contract on top of state in a block of given hash. + /// + /// No changes are made. + fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> Result; + + /// Execute a call to a contract on top of given state. + /// + /// No changes are made. + fn call_at_state(&self, state: &S, overlay: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> Result<(Vec, S::Transaction), error::Error>; +} + +/// Call executor that executes methods locally, querying all required +/// data from local backend. +pub struct LocalCallExecutor { + backend: Arc, + executor: E, +} + +/// Call executor that executes methods on remote node, querying execution proof +/// and checking proof by re-executing locally. +pub struct RemoteCallExecutor { + backend: Arc, + executor: E, + fetcher: Arc, +} + +impl LocalCallExecutor { + /// Creates new instance of local call executor. + pub fn new(backend: Arc, executor: E) -> Self { + LocalCallExecutor { backend, executor } + } +} + +impl Clone for LocalCallExecutor where E: Clone { + fn clone(&self) -> Self { + LocalCallExecutor { + backend: self.backend.clone(), + executor: self.executor.clone(), + } + } +} + +impl CallExecutor for LocalCallExecutor + where + B: backend::Backend, + E: CodeExecutor, + error::Error: From<<::State as StateBackend>::Error>, +{ + type Error = E::Error; + + fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result { + let mut changes = OverlayedChanges::default(); + let (return_data, _) = self.call_at_state(&self.backend.state_at(*id)?, &mut changes, method, call_data)?; + Ok(CallResult{ return_data, changes }) + } + + fn call_at_state(&self, state: &S, changes: &mut OverlayedChanges, method: &str, call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { + state_machine::execute( + state, + changes, + &self.executor, + method, + call_data, + ).map_err(Into::into) + } +} + +impl RemoteCallExecutor { + /// Creates new instance of remote call executor. + pub fn new(backend: Arc, executor: E, fetcher: Arc) -> Self { + RemoteCallExecutor { backend, executor, fetcher } + } +} + +impl CallExecutor for RemoteCallExecutor + where + B: backend::Backend, + E: CodeExecutor, + error::Error: From<<::State as StateBackend>::Error>, +{ + type Error = E::Error; + + fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result { + let block_hash = match *id { + BlockId::Hash(hash) => hash, + BlockId::Number(number) => self.backend.blockchain().hash(number)? + .ok_or_else(|| error::ErrorKind::UnknownBlock(BlockId::Number(number)))?, + }; + + let (remote_result, remote_proof) = self.fetcher.execution_proof(block_hash, method, call_data)?; + + // code below will be replaced with proper proof check once trie-based proofs will be possible + + let remote_state = state_from_execution_proof(remote_proof); + let remote_state_root = trie_root(remote_state.pairs().into_iter()).0; + + let local_header = self.backend.blockchain().header(BlockId::Hash(block_hash))?; + let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(BlockId::Hash(block_hash)))?; + let local_state_root = local_header.state_root; + + if remote_state_root != *local_state_root { + return Err(error::ErrorKind::InvalidExecutionProof.into()); + } + + let mut changes = OverlayedChanges::default(); + let (local_result, _) = state_machine::execute( + &remote_state, + &mut changes, + &self.executor, + method, + call_data, + )?; + + if local_result != remote_result { + return Err(error::ErrorKind::InvalidExecutionProof.into()); + } + + Ok(CallResult { return_data: local_result, changes }) + } + + fn call_at_state(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { + Err(error::ErrorKind::NotAvailableOnLightClient.into()) + } +} + +/// Convert state to execution proof. Proof is simple the whole state (temporary). +// TODO [light]: this method must be removed after trie-based proofs are landed. +pub fn state_to_execution_proof(state: &B) -> Vec> { + state.pairs().into_iter() + .flat_map(|(k, v)| ::std::iter::once(k).chain(::std::iter::once(v))) + .collect() +} + +/// Convert execution proof to in-memory state for check. Reverse function for state_to_execution_proof. +// TODO [light]: this method must be removed after trie-based proofs are landed. +fn state_from_execution_proof(proof: Vec>) -> InMemoryStateBackend { + let mut changes = Vec::new(); + let mut proof_iter = proof.into_iter(); + loop { + let key = proof_iter.next(); + let value = proof_iter.next(); + if let (Some(key), Some(value)) = (key, value) { + changes.push((key, Some(value))); + } else { + break; + } + } + + InMemoryStateBackend::default().update(changes) +} diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index 30ae5a261134a..1e68bd251dc33 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -16,25 +16,33 @@ //! Substrate Client +use std::sync::Arc; use futures::sync::mpsc; use parking_lot::Mutex; use primitives::{self, block, AuthorityId}; use primitives::block::Id as BlockId; use primitives::storage::{StorageKey, StorageData}; use runtime_support::Hashable; -use codec::{KeyedVec, Slicable}; +use codec::Slicable; use state_machine::{self, Ext, OverlayedChanges, Backend as StateBackend, CodeExecutor}; use backend::{self, BlockImportOperation}; use blockchain::{self, Info as ChainInfo, Backend as ChainBackend}; +use call_executor::{CallExecutor, LocalCallExecutor}; use {error, in_mem, block_builder, runtime_io, bft}; /// Type that implements `futures::Stream` of block import events. pub type BlockchainEventStream = mpsc::UnboundedReceiver; +/// Polkadot Client genesis block builder. +pub trait GenesisBuilder { + /// Build genesis block. + fn build(self) -> (block::Header, Vec<(Vec, Vec)>); +} + /// Polkadot Client pub struct Client { - backend: B, + backend: Arc, executor: E, import_notification_sinks: Mutex>>, } @@ -63,14 +71,6 @@ pub struct ClientInfo { pub best_queued_hash: Option, } -/// Information regarding the result of a call. -pub struct CallResult { - /// The data that was returned from the call. - pub return_data: Vec, - /// The changes made to the state by the call. - pub changes: OverlayedChanges, -} - /// Block import result. #[derive(Debug)] pub enum ImportResult { @@ -146,32 +146,34 @@ impl JustifiedHeader { /// Create an instance of in-memory client. pub fn new_in_mem( executor: E, - build_genesis: F -) -> error::Result> + genesis_builder: F +) -> error::Result>> where E: CodeExecutor, - F: FnOnce() -> (block::Header, Vec<(Vec, Vec)>) + F: GenesisBuilder, { - Client::new(in_mem::Backend::new(), executor, build_genesis) + let backend = Arc::new(in_mem::Backend::new()); + let executor = LocalCallExecutor::new(backend.clone(), executor); + Client::new(backend, executor, genesis_builder) } impl Client where B: backend::Backend, - E: CodeExecutor, + E: CallExecutor, error::Error: From<<::State as StateBackend>::Error>, { /// Creates new Polkadot Client with given blockchain and code executor. pub fn new( - backend: B, + backend: Arc, executor: E, - build_genesis: F + genesis_builder: F, ) -> error::Result where - F: FnOnce() -> (block::Header, Vec<(Vec, Vec)>) + F: GenesisBuilder { if backend.blockchain().header(BlockId::Number(0))?.is_none() { trace!("Empty database, writing genesis block"); - let (genesis_header, genesis_store) = build_genesis(); + let (genesis_header, genesis_store) = genesis_builder.build(); let mut op = backend.begin_operation(BlockId::Hash(block::HeaderHash::default()))?; op.reset_storage(genesis_store.into_iter())?; op.set_block_data(genesis_header, Some(vec![]), None, true)?; @@ -207,36 +209,29 @@ impl Client where self.storage(id, &StorageKey(b":code".to_vec())).map(|data| data.0) } - /// Clone a new instance of Executor. - pub fn clone_executor(&self) -> E where E: Clone { - self.executor.clone() + /// Get the set of authorities at a given block. + pub fn authorities_at(&self, id: &BlockId) -> error::Result> { + self.executor.call(id, "authorities",&[]) + .and_then(|r| Vec::::decode(&mut &r.return_data[..]) + .ok_or(error::ErrorKind::AuthLenInvalid.into())) } - /// Get the current set of authorities from storage. - pub fn authorities_at(&self, id: &BlockId) -> error::Result> { - let state = self.state_at(id)?; - (0..u32::decode(&mut state.storage(b":auth:len")?.ok_or(error::ErrorKind::AuthLenEmpty)?.as_slice()).ok_or(error::ErrorKind::AuthLenInvalid)?) - .map(|i| state.storage(&i.to_keyed_vec(b":auth:")) - .map_err(|e| error::Error::from(e).into()) - .and_then(|v| v.ok_or(error::ErrorKind::AuthEmpty(i))) - .and_then(|s| AuthorityId::decode(&mut s.as_slice()).ok_or(error::ErrorKind::AuthInvalid(i))) - .map_err(Into::into) - ).collect() + /// Get call executor reference. + pub fn executor(&self) -> &E { + &self.executor } - /// Execute a call to a contract on top of state in a block of given hash. + /// Execute a call to a contract on top of state in a block of given hash + /// AND returning execution proof. /// /// No changes are made. - pub fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result { - let mut changes = OverlayedChanges::default(); - let (return_data, _) = state_machine::execute( - &self.state_at(id)?, - &mut changes, - &self.executor, - method, - call_data, - )?; - Ok(CallResult { return_data, changes }) + pub fn execution_proof(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result<(Vec, Vec>)> { + use call_executor::state_to_execution_proof; + + let result = self.executor.call(id, method, call_data); + let result = result?.return_data; + let proof = self.backend.state_at(*id).map(|state| state_to_execution_proof(&state))?; + Ok((result, proof)) } /// Set up the native execution environment to call into a native runtime code. @@ -297,21 +292,28 @@ impl Client where } let mut transaction = self.backend.begin_operation(BlockId::Hash(header.parent_hash))?; - let mut overlay = OverlayedChanges::default(); - - let (_out, storage_update) = state_machine::execute( - transaction.state()?, - &mut overlay, - &self.executor, - "execute_block", - &block::Block { header: header.clone(), transactions: body.clone().unwrap_or_default().clone() }.encode() - )?; + let storage_update = match transaction.state()? { + Some(transaction_state) => { + let mut overlay = Default::default(); + let (_, storage_update) = self.executor.call_at_state( + transaction_state, + &mut overlay, + "execute_block", + &block::Block { header: header.clone(), transactions: body.clone().unwrap_or_default().clone() }.encode(), + )?; + + Some(storage_update) + }, + None => None, + }; let is_new_best = header.number == self.backend.blockchain().info()?.best_number + 1; let hash: block::HeaderHash = header.blake2_256().into(); trace!("Imported {}, (#{}), best={}, origin={:?}", hash, header.number, is_new_best, origin); transaction.set_block_data(header.clone(), body, Some(justification.uncheck().into()), is_new_best)?; - transaction.update_storage(storage_update)?; + if let Some(storage_update) = storage_update { + transaction.update_storage(storage_update)?; + } self.backend.commit_operation(transaction)?; if origin == BlockOrigin::NetworkBroadcast || origin == BlockOrigin::Own || origin == BlockOrigin::ConsensusBroadcast { @@ -392,7 +394,7 @@ impl Client where impl bft::BlockImport for Client where B: backend::Backend, - E: state_machine::CodeExecutor, + E: CallExecutor, error::Error: From<::Error> { fn import_block(&self, block: block::Block, justification: bft::Justification) { @@ -408,7 +410,7 @@ impl bft::BlockImport for Client impl bft::Authorities for Client where B: backend::Backend, - E: state_machine::CodeExecutor, + E: CallExecutor, error::Error: From<::Error> { fn authorities(&self, at: &BlockId) -> Result, bft::Error> { @@ -419,7 +421,7 @@ impl bft::Authorities for Client impl BlockchainEvents for Client where B: backend::Backend, - E: state_machine::CodeExecutor, + E: CallExecutor, error::Error: From<::Error> { /// Get block import event stream. @@ -433,7 +435,7 @@ impl BlockchainEvents for Client impl ChainHead for Client where B: backend::Backend, - E: state_machine::CodeExecutor, + E: CallExecutor, error::Error: From<::Error> { fn best_block_header(&self) -> error::Result { diff --git a/substrate/client/src/error.rs b/substrate/client/src/error.rs index 6b34ac6eb6a07..7e1a9dd99c00b 100644 --- a/substrate/client/src/error.rs +++ b/substrate/client/src/error.rs @@ -81,6 +81,18 @@ error_chain! { description("bad justification for header"), display("bad justification for header: {}", h), } + + /// Not available on light client. + NotAvailableOnLightClient { + description("not available on light client"), + display("This method is not currently available when running in light client mode"), + } + + /// Invalid remote proof. + InvalidExecutionProof { + description("invalid execution proof"), + display("Remote node has responded with invalid execution proof"), + } } } diff --git a/substrate/client/src/in_mem.rs b/substrate/client/src/in_mem.rs index 138b6f27a24f8..3b29807bd188b 100644 --- a/substrate/client/src/in_mem.rs +++ b/substrate/client/src/in_mem.rs @@ -26,7 +26,8 @@ use primitives::block::{self, Id as BlockId, HeaderHash}; use blockchain::{self, BlockStatus}; use state_machine::backend::{Backend as StateBackend, InMemory}; -fn header_hash(header: &block::Header) -> block::HeaderHash { +/// Compute block header hash. +pub fn header_hash(header: &block::Header) -> block::HeaderHash { header.blake2_256().into() } @@ -65,14 +66,8 @@ impl Clone for Blockchain { } impl Blockchain { - fn id(&self, id: BlockId) -> Option { - match id { - BlockId::Hash(h) => Some(h), - BlockId::Number(n) => self.storage.read().hashes.get(&n).cloned(), - } - } - - fn new() -> Blockchain { + /// Create new in-memory blockchain storage. + pub fn new() -> Blockchain { Blockchain { storage: RwLock::new( BlockchainStorage { @@ -85,7 +80,16 @@ impl Blockchain { } } - fn insert(&self, hash: HeaderHash, header: block::Header, justification: Option, body: Option, is_new_best: bool) { + /// Get header hash of given block. + pub fn id(&self, id: BlockId) -> Option { + match id { + BlockId::Hash(h) => Some(h), + BlockId::Number(n) => self.storage.read().hashes.get(&n).cloned(), + } + } + + /// Insert block. + pub fn insert(&self, hash: HeaderHash, header: block::Header, justification: Option, body: Option, is_new_best: bool) { let number = header.number; let mut storage = self.storage.write(); storage.blocks.insert(hash, Block { @@ -113,7 +117,7 @@ impl Blockchain { let this = self.storage.read(); let other = other.storage.read(); this.hashes == other.hashes - && this.best_hash == other.best_hash + && this.best_hash == other.best_hash && this.best_number == other.best_number && this.genesis_hash == other.genesis_hash } @@ -163,8 +167,8 @@ pub struct BlockImportOperation { impl backend::BlockImportOperation for BlockImportOperation { type State = InMemory; - fn state(&self) -> error::Result<&Self::State> { - Ok(&self.old_state) + fn state(&self) -> error::Result> { + Ok(Some(&self.old_state)) } fn set_block_data(&mut self, header: block::Header, body: Option, justification: Option, is_new_best: bool) -> error::Result<()> { diff --git a/substrate/client/src/lib.rs b/substrate/client/src/lib.rs index 7ef6b65d94314..8045480fbd5c5 100644 --- a/substrate/client/src/lib.rs +++ b/substrate/client/src/lib.rs @@ -17,6 +17,7 @@ //! Substrate Client and associated logic. #![warn(missing_docs)] +#![recursion_limit="128"] extern crate substrate_bft as bft; extern crate substrate_codec as codec; @@ -43,12 +44,17 @@ pub mod backend; pub mod in_mem; pub mod genesis; pub mod block_builder; +pub mod light; +mod call_executor; mod client; pub use client::{ new_in_mem, BlockStatus, BlockOrigin, BlockchainEventStream, BlockchainEvents, - Client, ClientInfo, CallResult, ChainHead, - ImportResult, + Client, ClientInfo, ChainHead, + ImportResult, GenesisBuilder, }; pub use blockchain::Info as ChainInfo; +pub use call_executor::{ + CallResult, CallExecutor, LocalCallExecutor, RemoteCallExecutor, +}; diff --git a/substrate/client/src/light.rs b/substrate/client/src/light.rs new file mode 100644 index 0000000000000..800754321d4ef --- /dev/null +++ b/substrate/client/src/light.rs @@ -0,0 +1,187 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Light client backend. Only stores headers and justifications of blocks. +//! Everything else is requested from full nodes on demand. + +use std::sync::Arc; +use primitives; +use primitives::block::{self, Id as BlockId, HeaderHash}; +use state_machine::CodeExecutor; +use state_machine::backend::Backend as StateBackend; +use blockchain::{self, BlockStatus}; +use backend; +use call_executor::RemoteCallExecutor; +use client::{Client, GenesisBuilder}; +use error; +use in_mem::{header_hash, Blockchain as InMemBlockchain}; + +/// Light client data fetcher. +pub trait Fetcher: Send + Sync { + /// Fetch method execution proof. + fn execution_proof(&self, block: HeaderHash, method: &str, call_data: &[u8]) -> error::Result<(Vec, Vec>)>; +} + +/// Light client backend. +pub struct Backend { + blockchain: Blockchain, +} + +/// Light client blockchain. +pub struct Blockchain { + storage: InMemBlockchain, +} + +/// Block (header and justification) import operation. +pub struct BlockImportOperation { + pending_block: Option, +} + +/// On-demand state. +#[derive(Clone)] +pub struct OnDemandState { + /// Hash of the block, state is valid for. + _block: HeaderHash, +} + +struct PendingBlock { + header: block::Header, + justification: Option, + is_best: bool, +} + +impl backend::Backend for Backend { + type BlockImportOperation = BlockImportOperation; + type Blockchain = Blockchain; + type State = OnDemandState; + + fn begin_operation(&self, _block: BlockId) -> error::Result { + Ok(BlockImportOperation { + pending_block: None, + }) + } + + fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> { + if let Some(pending_block) = operation.pending_block { + let hash = header_hash(&pending_block.header); + self.blockchain.storage.insert(hash, pending_block.header, pending_block.justification, None, pending_block.is_best); + } + Ok(()) + } + + fn blockchain(&self) -> &Blockchain { + &self.blockchain + } + + fn state_at(&self, block: BlockId) -> error::Result { + Ok(OnDemandState { + _block: self.blockchain.storage.id(block).ok_or(error::ErrorKind::UnknownBlock(block))?, + }) + } +} + +impl backend::BlockImportOperation for BlockImportOperation { + type State = OnDemandState; + + fn state(&self) -> error::Result> { + // None means 'locally-stateless' backend + Ok(None) + } + + fn set_block_data(&mut self, header: block::Header, _body: Option, justification: Option, is_new_best: bool) -> error::Result<()> { + assert!(self.pending_block.is_none(), "Only one block per operation is allowed"); + self.pending_block = Some(PendingBlock { + header, + justification, + is_best: is_new_best, + }); + Ok(()) + } + + fn update_storage(&mut self, _update: ::Transaction) -> error::Result<()> { + // we're not storing anything locally => ignore changes + Ok(()) + } + + fn reset_storage, Vec)>>(&mut self, _iter: I) -> error::Result<()> { + // we're not storing anything locally => ignore changes + Ok(()) + } +} + +impl blockchain::Backend for Blockchain { + fn header(&self, id: BlockId) -> error::Result> { + self.storage.header(id) + } + + fn body(&self, _id: BlockId) -> error::Result> { + // TODO [light]: fetch from remote node + Ok(None) + } + + fn justification(&self, id: BlockId) -> error::Result> { + self.storage.justification(id) + } + + fn info(&self) -> error::Result { + self.storage.info() + } + + fn status(&self, id: BlockId) -> error::Result { + self.storage.status(id) + } + + fn hash(&self, number: block::Number) -> error::Result> { + self.storage.hash(number) + } +} + +impl StateBackend for OnDemandState { + type Error = error::Error; + type Transaction = (); + + fn storage(&self, _key: &[u8]) -> Result>, Self::Error> { + // TODO [light]: fetch from remote node + Err(error::ErrorKind::NotAvailableOnLightClient.into()) + } + + fn storage_root(&self, _delta: I) -> ([u8; 32], Self::Transaction) + where I: IntoIterator, Option>)> { + ([0; 32], ()) + } + + fn pairs(&self) -> Vec<(Vec, Vec)> { + // whole state is not available on light node + Vec::new() + } +} + +/// Create an instance of in-memory client. +pub fn new_light( + fetcher: Arc, + executor: E, + genesis_builder: F +) -> error::Result>> + where + E: CodeExecutor, + F: GenesisBuilder, +{ + let storage = InMemBlockchain::new(); + let blockchain = Blockchain { storage }; + let backend = Arc::new(Backend { blockchain }); + let executor = RemoteCallExecutor::new(backend.clone(), executor, fetcher); + Client::new(backend, executor, genesis_builder) +} diff --git a/substrate/network/Cargo.toml b/substrate/network/Cargo.toml index 6a540d4b76a66..ca715c551c1b1 100644 --- a/substrate/network/Cargo.toml +++ b/substrate/network/Cargo.toml @@ -17,6 +17,7 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0" futures = "0.1.17" +linked-hash-map = "0.5" ethcore-network = { git = "https://github.com/paritytech/parity.git" } ethcore-network-devp2p = { git = "https://github.com/paritytech/parity.git" } ethcore-io = { git = "https://github.com/paritytech/parity.git" } diff --git a/substrate/network/src/chain.rs b/substrate/network/src/chain.rs index 10d7a0c7d5a79..cb09f88eada54 100644 --- a/substrate/network/src/chain.rs +++ b/substrate/network/src/chain.rs @@ -16,7 +16,7 @@ //! Blockchain access trait -use client::{self, Client as PolkadotClient, ImportResult, ClientInfo, BlockStatus, BlockOrigin}; +use client::{self, Client as PolkadotClient, ImportResult, ClientInfo, BlockStatus, BlockOrigin, CallExecutor}; use client::error::Error; use state_machine; use primitives::block::{self, Id as BlockId}; @@ -43,11 +43,14 @@ pub trait Client: Send + Sync { /// Get block justification. fn justification(&self, id: &BlockId) -> Result, Error>; + + /// Get method execution proof. + fn execution_proof(&self, block: &block::HeaderHash, method: &str, data: &[u8]) -> Result<(Vec, Vec>), Error>; } impl Client for PolkadotClient where B: client::backend::Backend + Send + Sync + 'static, - E: state_machine::CodeExecutor + Send + Sync + 'static, + E: CallExecutor + Send + Sync + 'static, Error: From<<::State as state_machine::backend::Backend>::Error>, { fn import(&self, is_best: bool, header: block::Header, justification: Justification, body: Option) -> Result { @@ -80,4 +83,8 @@ impl Client for PolkadotClient where fn justification(&self, id: &BlockId) -> Result, Error> { (self as &PolkadotClient).justification(id) } + + fn execution_proof(&self, block: &block::HeaderHash, method: &str, data: &[u8]) -> Result<(Vec, Vec>), Error> { + (self as &PolkadotClient).execution_proof(&BlockId::Hash(block.clone()), method, data) + } } diff --git a/substrate/network/src/lib.rs b/substrate/network/src/lib.rs index 3cc9d994eee91..f894def9c4f6c 100644 --- a/substrate/network/src/lib.rs +++ b/substrate/network/src/lib.rs @@ -22,6 +22,7 @@ extern crate ethcore_network_devp2p as network_devp2p; extern crate ethcore_network as network; extern crate ethcore_io as core_io; +extern crate linked_hash_map; extern crate rand; extern crate parking_lot; extern crate substrate_primitives as primitives; @@ -53,6 +54,7 @@ mod config; mod chain; mod blocks; mod consensus; +mod on_demand; pub mod error; #[cfg(test)] mod test; @@ -66,6 +68,7 @@ pub use network_devp2p::{ConnectionFilter, ConnectionDirection}; pub use message::{Statement, BftMessage, LocalizedBftMessage, ConsensusVote, SignedConsensusVote, SignedConsensusMessage, SignedConsensusProposal}; pub use error::Error; pub use config::{Role, ProtocolConfig}; +pub use on_demand::{OnDemand, OnDemandService, Response as OnDemandResponse}; // TODO: move it elsewhere fn header_hash(header: &primitives::Header) -> primitives::block::HeaderHash { diff --git a/substrate/network/src/message.rs b/substrate/network/src/message.rs index 8e1fe804dda05..032cd375f0901 100644 --- a/substrate/network/src/message.rs +++ b/substrate/network/src/message.rs @@ -247,6 +247,10 @@ pub enum Message { CandidateResponse(CandidateResponse), /// BFT Consensus statement. BftMessage(LocalizedBftMessage), + /// Remote method call request. + RemoteCallRequest(RemoteCallRequest), + /// Remote method call response. + RemoteCallResponse(RemoteCallResponse), } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] @@ -319,3 +323,27 @@ pub struct BlockAnnounce { /// New block header. pub header: Header, } + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Remote call request. +pub struct RemoteCallRequest { + /// Unique request id. + pub id: RequestId, + /// Block at which to perform call. + pub block: HeaderHash, + /// Method name. + pub method: String, + /// Call data. + pub data: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +/// Remote call response. +pub struct RemoteCallResponse { + /// Id of a request this response was made for. + pub id: RequestId, + /// Method return value. + pub value: Vec, + /// Execution proof. + pub proof: Vec>, +} diff --git a/substrate/network/src/on_demand.rs b/substrate/network/src/on_demand.rs new file mode 100644 index 0000000000000..645f6ed70f391 --- /dev/null +++ b/substrate/network/src/on_demand.rs @@ -0,0 +1,374 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see .? + +//! On-demand requests service. + +use std::collections::VecDeque; +use std::sync::Weak; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::time::{Instant, Duration}; +use linked_hash_map::LinkedHashMap; +use linked_hash_map::Entry; +use parking_lot::Mutex; +use primitives::block::HeaderHash; +use client; +use client::light::Fetcher; +use io::SyncIo; +use message; +use network::PeerId; +use service; + +/// Remote request timeout. +const REQUEST_TIMEOUT: Duration = Duration::from_secs(15); + +/// On-demand service API. +pub trait OnDemandService: Send + Sync { + /// When new node is connected. + fn on_connect(&self, peer: PeerId, role: service::Role); + + /// When node is disconnected. + fn on_disconnect(&self, peer: PeerId); + + /// Maintain peers requests. + fn maintain_peers(&self, io: &mut SyncIo); + + /// When response is received from remote node. + fn on_remote_response(&self, io: &mut SyncIo, peer: PeerId, response: message::RemoteCallResponse); +} + +/// On-demand requests service. Dispatches requests to appropriate peers. +pub struct OnDemand { + core: Mutex>, +} + +/// On-demand response. +pub struct Response { + receiver: Receiver, +} + +#[derive(Default)] +struct OnDemandCore { + service: Weak, + next_request_id: u64, + pending_requests: VecDeque, + active_peers: LinkedHashMap, + idle_peers: VecDeque, +} + +struct Request { + id: u64, + timestamp: Instant, + sender: Sender, + message: message::RemoteCallRequest, +} + +impl Response { + fn wait(&mut self) -> client::error::Result<(Vec, Vec>)> { + self.receiver.recv().map(|r| (r.value, r.proof)).map_err(|_| unimplemented!()) + } +} + +impl OnDemand where E: service::ExecuteInContext { + /// Creates new on-demand service. + pub fn new() -> Self { + OnDemand { + core: Mutex::new(OnDemandCore { + service: Weak::new(), + next_request_id: 0, + pending_requests: VecDeque::new(), + active_peers: LinkedHashMap::new(), + idle_peers: VecDeque::new(), + }) + } + } + + /// Sets weak reference to network service. + pub fn set_service_link(&self, service: Weak) { + self.core.lock().service = service; + } + + /// Execute method call on remote node, returning execution result and proof. + pub fn remote_call(&self, block: HeaderHash, method: &str, data: &[u8]) -> Response { + let (sender, receiver) = channel(); + let result = Response { + receiver: receiver, + }; + + { + let mut core = self.core.lock(); + core.insert(sender, message::RemoteCallRequest { + id: 0, + block: block, + method: method.into(), + data: data.to_vec(), + }); + core.dispatch(); + } + + result + } +} + +impl OnDemandService for OnDemand where E: service::ExecuteInContext { + fn on_connect(&self, peer: PeerId, role: service::Role) { + if !role.intersects(service::Role::FULL | service::Role::COLLATOR | service::Role::VALIDATOR) { // TODO: correct? + return; + } + + let mut core = self.core.lock(); + core.add_peer(peer); + core.dispatch(); + } + + fn on_disconnect(&self, peer: PeerId) { + let mut core = self.core.lock(); + core.remove_peer(peer); + core.dispatch(); + } + + fn maintain_peers(&self, io: &mut SyncIo) { + let mut core = self.core.lock(); + for bad_peer in core.maintain_peers() { + trace!(target: "sync", "Remote request timeout for peer {}", bad_peer); + io.disconnect_peer(bad_peer); + } + core.dispatch(); + } + + fn on_remote_response(&self, io: &mut SyncIo, peer: PeerId, response: message::RemoteCallResponse) { + let mut core = self.core.lock(); + match core.remove(peer, response.id) { + Some(request) => { + // we do not bother if receiver has been dropped already + let _ = request.sender.send(response); + }, + None => { + trace!(target: "sync", "Invalid remote response from peer {}", peer); + io.disconnect_peer(peer); + core.remove_peer(peer); + }, + } + + core.dispatch(); + } +} + +impl Fetcher for OnDemand where E: service::ExecuteInContext { + fn execution_proof(&self, block: HeaderHash, method: &str, call_data: &[u8]) -> client::error::Result<(Vec, Vec>)> { + self.remote_call(block, method, call_data).wait().map_err(|_| unimplemented!()) + } +} + +impl OnDemandCore where E: service::ExecuteInContext { + pub fn add_peer(&mut self, peer: PeerId) { + self.idle_peers.push_back(peer); + } + + pub fn remove_peer(&mut self, peer: PeerId) { + if let Some(request) = self.active_peers.remove(&peer) { + self.pending_requests.push_front(request); + return; + } + + if let Some(idle_index) = self.idle_peers.iter().position(|i| *i == peer) { + self.idle_peers.swap_remove_back(idle_index); + } + } + + pub fn maintain_peers(&mut self) -> Vec { + let now = Instant::now(); + let mut bad_peers = Vec::new(); + loop { + match self.active_peers.front() { + Some((_, request)) if now - request.timestamp >= REQUEST_TIMEOUT => (), + _ => return bad_peers, + } + + let (bad_peer, request) = self.active_peers.pop_front().expect("front() is Some as checked above"); + self.pending_requests.push_front(request); + bad_peers.push(bad_peer); + } + } + + pub fn insert(&mut self, sender: Sender, mut message: message::RemoteCallRequest) { + message.id = self.next_request_id; + self.next_request_id += 1; + self.pending_requests.push_back(Request { + id: message.id, + timestamp: Instant::now(), + sender, + message, + }); + } + + pub fn remove(&mut self, peer: PeerId, id: u64) -> Option { + match self.active_peers.entry(peer) { + Entry::Occupied(entry) => match entry.get().id == id { + true => { + self.idle_peers.push_back(peer); + Some(entry.remove()) + }, + false => None, + }, + Entry::Vacant(_) => None, + } + } + + pub fn dispatch(&mut self) { + let service = match self.service.upgrade() { + Some(service) => service, + None => return, + }; + + while !self.pending_requests.is_empty() { + let peer = match self.idle_peers.pop_front() { + Some(peer) => peer, + None => return, + }; + + let mut request = self.pending_requests.pop_front().expect("checked in loop condition; qed"); + request.timestamp = Instant::now(); + trace!(target: "sync", "Dispatching remote request {} to peer {}", request.id, peer); + + service.execute_in_context(|ctx, protocol| + protocol.send_message(ctx, peer, message::Message::RemoteCallRequest(request.message.clone()))); + self.active_peers.insert(peer, request); + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::VecDeque; + use std::sync::Arc; + use std::time::Instant; + use parking_lot::RwLock; + use io::NetSyncIo; + use message; + use network::PeerId; + use protocol::Protocol; + use service::{Role, ExecuteInContext}; + use test::TestIo; + use super::{REQUEST_TIMEOUT, OnDemand, OnDemandService}; + + struct DummyExecutor; + + impl ExecuteInContext for DummyExecutor { + fn execute_in_context(&self, _closure: F) {} + } + + fn dummy() -> (Arc, Arc>) { + let executor = Arc::new(DummyExecutor); + let service = Arc::new(OnDemand::new()); + service.set_service_link(Arc::downgrade(&executor)); + (executor, service) + } + + fn total_peers(on_demand: &OnDemand) -> usize { + let core = on_demand.core.lock(); + core.idle_peers.len() + core.active_peers.len() + } + + fn receive_response(on_demand: &OnDemand, network: &mut TestIo, peer: PeerId, id: message::RequestId) { + on_demand.on_remote_response(network, peer, message::RemoteCallResponse { + id: id, + value: vec![1], + proof: vec![vec![2]], + }); + } + + #[test] + fn knows_about_peers_roles() { + let (_, on_demand) = dummy(); + on_demand.on_connect(0, Role::LIGHT); + on_demand.on_connect(1, Role::FULL); + on_demand.on_connect(2, Role::COLLATOR); + on_demand.on_connect(3, Role::VALIDATOR); + assert_eq!(vec![1, 2, 3], on_demand.core.lock().idle_peers.iter().cloned().collect::>()); + } + + #[test] + fn disconnects_from_idle_peer() { + let (_, on_demand) = dummy(); + on_demand.on_connect(0, Role::FULL); + assert_eq!(1, total_peers(&*on_demand)); + on_demand.on_disconnect(0); + assert_eq!(0, total_peers(&*on_demand)); + } + + #[test] + fn disconnects_from_timeouted_peer() { + let (_x, on_demand) = dummy(); + let queue = RwLock::new(VecDeque::new()); + let mut network = TestIo::new(&queue, None); + + on_demand.on_connect(0, Role::FULL); + on_demand.on_connect(1, Role::FULL); + assert_eq!(vec![0, 1], on_demand.core.lock().idle_peers.iter().cloned().collect::>()); + assert!(on_demand.core.lock().active_peers.is_empty()); + + on_demand.remote_call(Default::default(), "test", &[]); + assert_eq!(vec![1], on_demand.core.lock().idle_peers.iter().cloned().collect::>()); + assert_eq!(vec![0], on_demand.core.lock().active_peers.keys().cloned().collect::>()); + + on_demand.core.lock().active_peers[&0].timestamp = Instant::now() - REQUEST_TIMEOUT - REQUEST_TIMEOUT; + on_demand.maintain_peers(&mut network); + assert!(on_demand.core.lock().idle_peers.is_empty()); + assert_eq!(vec![1], on_demand.core.lock().active_peers.keys().cloned().collect::>()); + assert!(network.to_disconnect.contains(&0)); + } + + #[test] + fn disconnects_from_peer_on_incorrect_response() { + let (_x, on_demand) = dummy(); + let queue = RwLock::new(VecDeque::new()); + let mut network = TestIo::new(&queue, None); + on_demand.on_connect(0, Role::FULL); + + on_demand.remote_call(Default::default(), "test", &[]); + receive_response(&*on_demand, &mut network, 0, 1); + assert!(network.to_disconnect.contains(&0)); + } + + #[test] + fn disconnects_from_peer_on_unexpected_response() { + let (_x, on_demand) = dummy(); + let queue = RwLock::new(VecDeque::new()); + let mut network = TestIo::new(&queue, None); + on_demand.on_connect(0, Role::FULL); + + receive_response(&*on_demand, &mut network, 0, 0); + assert!(network.to_disconnect.contains(&0)); + } + + #[test] + fn receives_remote_response() { + let (_x, on_demand) = dummy(); + let queue = RwLock::new(VecDeque::new()); + let mut network = TestIo::new(&queue, None); + on_demand.on_connect(0, Role::FULL); + + let mut response = on_demand.remote_call(Default::default(), "test", &[]); + let thread = ::std::thread::spawn(move || { + let (result, proof) = response.wait().unwrap(); + assert_eq!(result, vec![1]); + assert_eq!(proof, vec![vec![2]]); + }); + + receive_response(&*on_demand, &mut network, 0, 0); + thread.join().unwrap(); + } +} diff --git a/substrate/network/src/protocol.rs b/substrate/network/src/protocol.rs index b2b87e80ff553..8e9b3ace68902 100644 --- a/substrate/network/src/protocol.rs +++ b/substrate/network/src/protocol.rs @@ -32,6 +32,7 @@ use consensus::Consensus; use service::{Role, TransactionPool, StatementStream, BftMessageStream}; use config::ProtocolConfig; use chain::Client; +use on_demand::OnDemandService; use io::SyncIo; use error; use super::header_hash; @@ -46,6 +47,7 @@ const MAX_BLOCK_DATA_RESPONSE: u32 = 128; pub struct Protocol { config: ProtocolConfig, chain: Arc, + on_demand: Option>, genesis_hash: HeaderHash, sync: RwLock, consensus: Mutex, @@ -112,14 +114,16 @@ pub struct TransactionStats { impl Protocol { /// Create a new instance. - pub fn new(config: ProtocolConfig, chain: Arc, transaction_pool: Arc) -> error::Result { + pub fn new(config: ProtocolConfig, chain: Arc, on_demand: Option>, transaction_pool: Arc) -> error::Result { let info = chain.info()?; let best_hash = info.chain.best_hash; + let sync = ChainSync::new(config.roles, &info); let protocol = Protocol { config: config, chain: chain, + on_demand: on_demand, genesis_hash: info.chain.genesis_hash, - sync: RwLock::new(ChainSync::new(&info)), + sync: RwLock::new(sync), consensus: Mutex::new(Consensus::new(best_hash)), peers: RwLock::new(HashMap::new()), handshaking_peers: RwLock::new(HashMap::new()), @@ -185,6 +189,8 @@ impl Protocol { Message::CandidateResponse(r) => self.on_candidate_response(io, peer_id, r), Message::BftMessage(m) => self.on_bft_message(io, peer_id, m, blake2_256(data).into()), Message::Transactions(m) => self.on_transactions(io, peer_id, m), + Message::RemoteCallRequest(request) => self.on_remote_call_request(io, peer_id, request), + Message::RemoteCallResponse(response) => self.on_remote_call_response(io, peer_id, response) } } @@ -232,6 +238,7 @@ impl Protocol { if removed { self.consensus.lock().peer_disconnected(io, self, peer); self.sync.write().peer_disconnected(io, self, peer); + self.on_demand.as_ref().map(|s| s.on_disconnect(peer)); } } @@ -345,6 +352,7 @@ impl Protocol { /// Perform time based maintenance. pub fn tick(&self, io: &mut SyncIo) { self.maintain_peers(io); + self.on_demand.as_ref().map(|s| s.maintain_peers(io)); self.consensus.lock().collect_garbage(None); } @@ -388,8 +396,6 @@ impl Protocol { return; } - let mut sync = self.sync.write(); - let mut consensus = self.consensus.lock(); { let mut peers = self.peers.write(); let mut handshaking_peers = self.handshaking_peers.write(); @@ -423,8 +429,10 @@ impl Protocol { handshaking_peers.remove(&peer_id); debug!(target: "sync", "Connected {} {}", peer_id, io.peer_info(peer_id)); } - sync.new_peer(io, self, peer_id); - consensus.new_peer(io, self, peer_id, &status.roles); + + self.sync.write().new_peer(io, self, peer_id); + self.consensus.lock().new_peer(io, self, peer_id, &status.roles); + self.on_demand.as_ref().map(|s| s.on_connect(peer_id, message::Role::as_flags(&status.roles))); } /// Called when peer sends us new transactions @@ -523,6 +531,27 @@ impl Protocol { self.consensus.lock().collect_garbage(Some((hash, &header))); } + fn on_remote_call_request(&self, io: &mut SyncIo, peer_id: PeerId, request: message::RemoteCallRequest) { + trace!(target: "sync", "Remote request {} from {} ({} at {})", request.id, peer_id, request.method, request.block); + let (value, proof) = match self.chain.execution_proof(&request.block, &request.method, &request.data) { + Ok((value, proof)) => (value, proof), + Err(error) => { + trace!(target: "sync", "Remote request {} from {} ({} at {}) failed with: {}", + request.id, peer_id, request.method, request.block, error); + (Default::default(), Default::default()) + }, + }; + + self.send_message(io, peer_id, message::Message::RemoteCallResponse(message::RemoteCallResponse { + id: request.id, value, proof, + })); + } + + fn on_remote_call_response(&self, io: &mut SyncIo, peer_id: PeerId, response: message::RemoteCallResponse) { + trace!(target: "sync", "Remote response {} from {}", response.id, peer_id); + self.on_demand.as_ref().map(|s| s.on_remote_response(io, peer_id, response)); + } + pub fn transactions_stats(&self) -> BTreeMap { BTreeMap::new() } diff --git a/substrate/network/src/service.rs b/substrate/network/src/service.rs index f32c88a26339b..c664d0bd06129 100644 --- a/substrate/network/src/service.rs +++ b/substrate/network/src/service.rs @@ -31,6 +31,7 @@ use config::{ProtocolConfig}; use error::Error; use chain::Client; use message::{Statement, LocalizedBftMessage}; +use on_demand::OnDemandService; /// Polkadot devp2p protocol id pub const DOT_PROTOCOL_ID: ProtocolId = *b"dot"; @@ -107,6 +108,12 @@ pub trait ConsensusService: Send + Sync { fn send_bft_message(&self, message: LocalizedBftMessage); } +/// Service able to execute closure in the network context. +pub trait ExecuteInContext: Send + Sync { + /// Execute closure in network context. + fn execute_in_context(&self, closure: F); +} + /// devp2p Protocol handler struct ProtocolHandler { protocol: Protocol, @@ -137,6 +144,8 @@ pub struct Params { pub network_config: NetworkConfiguration, /// Polkadot relay chain access point. pub chain: Arc, + /// On-demand service reference. + pub on_demand: Option>, /// Transaction pool. pub transaction_pool: Arc, } @@ -156,7 +165,7 @@ impl Service { let sync = Arc::new(Service { network: service, handler: Arc::new(ProtocolHandler { - protocol: Protocol::new(params.config, params.chain.clone(), params.transaction_pool)?, + protocol: Protocol::new(params.config, params.chain, params.on_demand, params.transaction_pool)?, }), }); @@ -200,6 +209,14 @@ impl Drop for Service { } } +impl ExecuteInContext for Service { + fn execute_in_context(&self, closure: F) { + self.network.with_context(DOT_PROTOCOL_ID, |context| { + closure(&mut NetSyncIo::new(context), &self.handler.protocol) + }); + } +} + impl SyncProvider for Service { /// Get sync status fn status(&self) -> ProtocolStatus { diff --git a/substrate/network/src/sync.rs b/substrate/network/src/sync.rs index 9cef0cd021631..4bf6891aa2d70 100644 --- a/substrate/network/src/sync.rs +++ b/substrate/network/src/sync.rs @@ -22,6 +22,7 @@ use client::{ImportResult, BlockStatus, ClientInfo}; use primitives::block::{HeaderHash, Number as BlockNumber, Header, Id as BlockId}; use blocks::{self, BlockCollection}; use message::{self, Message}; +use service::Role; use super::header_hash; // Maximum blocks to request in a single packet. @@ -37,10 +38,10 @@ struct PeerSync { #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum PeerSyncState { - AncestorSearch(BlockNumber), - Available, - DownloadingNew(BlockNumber), - DownloadingStale(HeaderHash), + AncestorSearch(BlockNumber), + Available, + DownloadingNew(BlockNumber), + DownloadingStale(HeaderHash), } /// Relay chain sync strategy. @@ -73,14 +74,22 @@ pub struct Status { impl ChainSync { /// Create a new instance. - pub fn new(info: &ClientInfo) -> ChainSync { + pub fn new(role: Role, info: &ClientInfo) -> ChainSync { + let mut required_block_attributes = vec![ + message::BlockAttribute::Header, + message::BlockAttribute::Justification + ]; + if role.intersects(Role::FULL | Role::VALIDATOR | Role::COLLATOR) { + required_block_attributes.push(message::BlockAttribute::Body); + } + ChainSync { genesis_hash: info.chain.genesis_hash, peers: HashMap::new(), blocks: BlockCollection::new(), best_queued_hash: info.best_queued_hash.unwrap_or(info.chain.best_hash), best_queued_number: info.best_queued_number.unwrap_or(info.chain.best_number), - required_block_attributes: vec![message::BlockAttribute::Header, message::BlockAttribute::Body, message::BlockAttribute::Justification], + required_block_attributes: required_block_attributes, } } diff --git a/substrate/network/src/test/mod.rs b/substrate/network/src/test/mod.rs index a62bf5177ca0f..3699802daf9e4 100644 --- a/substrate/network/src/test/mod.rs +++ b/substrate/network/src/test/mod.rs @@ -227,7 +227,7 @@ impl TestNet { for _ in 0..n { let client = Arc::new(test_client::new()); let tx_pool = Arc::new(EmptyTransactionPool); - let sync = Protocol::new(config.clone(), client.clone(), tx_pool).unwrap(); + let sync = Protocol::new(config.clone(), client.clone(), None, tx_pool).unwrap(); net.peers.push(Arc::new(Peer { sync: sync, client: client, diff --git a/substrate/rpc/src/chain/mod.rs b/substrate/rpc/src/chain/mod.rs index c5c179837c3ed..f6e767ef66270 100644 --- a/substrate/rpc/src/chain/mod.rs +++ b/substrate/rpc/src/chain/mod.rs @@ -81,7 +81,7 @@ impl Chain { impl ChainApi for Chain where B: client::backend::Backend + Send + Sync + 'static, - E: state_machine::CodeExecutor + Send + Sync + 'static, + E: client::CallExecutor + Send + Sync + 'static, client::error::Error: From<<::State as state_machine::backend::Backend>::Error>, { type Metadata = ::metadata::Metadata; diff --git a/substrate/rpc/src/chain/tests.rs b/substrate/rpc/src/chain/tests.rs index 6d44ba7a3f708..237df1f976936 100644 --- a/substrate/rpc/src/chain/tests.rs +++ b/substrate/rpc/src/chain/tests.rs @@ -28,13 +28,12 @@ fn should_return_header() { client: Arc::new(test_client::new()), subscriptions: Subscriptions::new(remote), }; - assert_matches!( client.header(client.client.genesis_hash()), Ok(Some(ref x)) if x == &block::Header { parent_hash: 0.into(), number: 0, - state_root: "6da331d07a82d99f4debaafb0110a2e36244ed34162f9a7f6312a23fd52989ed".into(), + state_root: "ed0758a19e0f35e95de93046def6a8a6da7c816b7c3d15342743dd5e464a87fc".into(), extrinsics_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".into(), digest: Default::default(), } @@ -69,8 +68,9 @@ fn should_notify_about_latest_block() { // assert notification send to transport let (notification, next) = core.run(transport.into_future()).unwrap(); + assert_eq!(notification, Some( - r#"{"jsonrpc":"2.0","method":"test","params":{"result":{"digest":{"logs":[]},"extrinsicsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":1,"parentHash":"0x4c4ab196ed07bbd5b8c901ae5092d9d3990cbb4d44421af8e988af7d3c2a4226","stateRoot":"0x75b634da2a0d272e8a5145ab704406d3b50676c7739f977f2ccb2d0e5a0cdbd0"},"subscription":0}}"#.to_owned() + r#"{"jsonrpc":"2.0","method":"test","params":{"result":{"digest":{"logs":[]},"extrinsicsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":1,"parentHash":"0xfc47a48119e6e11992c9aba9d0084398f8789bb79a9390b4b60d6760575bee20","stateRoot":"0x59b17ea618f09dd634e6e0bde2becc8a10348fb35df2163f136b829b28f3751f"},"subscription":0}}"#.to_owned() )); // no more notifications on this channel assert_eq!(core.run(next.into_future()).unwrap().0, None); diff --git a/substrate/rpc/src/state/mod.rs b/substrate/rpc/src/state/mod.rs index 29d372aa8f022..9fa41d38a14bb 100644 --- a/substrate/rpc/src/state/mod.rs +++ b/substrate/rpc/src/state/mod.rs @@ -22,7 +22,7 @@ mod error; mod tests; use std::sync::Arc; -use client::{self, Client}; +use client::{self, Client, CallExecutor}; use primitives::{block, Hash, blake2_256}; use primitives::storage::{StorageKey, StorageData}; use primitives::hexdisplay::HexDisplay; @@ -69,7 +69,7 @@ build_rpc_trait! { impl StateApi for Arc> where B: client::backend::Backend + Send + Sync + 'static, - E: state_machine::CodeExecutor + Send + Sync + 'static, + E: CallExecutor + Send + Sync + 'static, client::error::Error: From<<::State as state_machine::backend::Backend>::Error>, { fn storage_at(&self, key: StorageKey, block: block::HeaderHash) -> Result { @@ -79,7 +79,7 @@ impl StateApi for Arc> where fn call_at(&self, method: String, data: Vec, block: block::HeaderHash) -> Result> { trace!(target: "rpc", "Calling runtime at {:?} for method {} ({})", block, method, HexDisplay::from(&data)); - Ok(self.as_ref().call(&block::Id::Hash(block), &method, &data)?.return_data) + Ok(self.as_ref().executor().call(&block::Id::Hash(block), &method, &data)?.return_data) } fn storage_hash_at(&self, key: StorageKey, block: block::HeaderHash) -> Result { diff --git a/substrate/test-client/src/client_ext.rs b/substrate/test-client/src/client_ext.rs index 3af87c4db4fcc..071a889d1efd8 100644 --- a/substrate/test-client/src/client_ext.rs +++ b/substrate/test-client/src/client_ext.rs @@ -39,7 +39,7 @@ pub trait TestClient { impl TestClient for Client { fn new_for_tests() -> Self { - client::new_in_mem(NativeExecutor::new(), prepare_genesis).unwrap() + client::new_in_mem(NativeExecutor::new(), GenesisBuilder).unwrap() } fn justify_and_import(&self, origin: client::BlockOrigin, block: block::Block) -> client::error::Result<()> { @@ -95,14 +95,18 @@ fn genesis_config() -> GenesisConfig { ], 1000) } -fn prepare_genesis() -> (block::Header, Vec<(Vec, Vec)>) { - let mut storage = genesis_config().genesis_map(); - let block = client::genesis::construct_genesis_block(&storage); - storage.extend(additional_storage_with_genesis(&block)); +struct GenesisBuilder; - ( - block::Header::decode(&mut block.header.encode().as_ref()) - .expect("to_vec() always gives a valid serialisation; qed"), - storage.into_iter().collect() - ) +impl client::GenesisBuilder for GenesisBuilder { + fn build(self) -> (block::Header, Vec<(Vec, Vec)>) { + let mut storage = genesis_config().genesis_map(); + let block = client::genesis::construct_genesis_block(&storage); + storage.extend(additional_storage_with_genesis(&block)); + + ( + block::Header::decode(&mut block.header.encode().as_ref()) + .expect("to_vec() always gives a valid serialisation; qed"), + storage.into_iter().collect() + ) + } } diff --git a/substrate/test-client/src/lib.rs b/substrate/test-client/src/lib.rs index 123a8a6398699..dacf41e851993 100644 --- a/substrate/test-client/src/lib.rs +++ b/substrate/test-client/src/lib.rs @@ -46,7 +46,7 @@ pub use self::native_executor::NativeExecutor; pub type Backend = client::in_mem::Backend; /// Test client executor. -pub type Executor = executor::NativeExecutor; +pub type Executor = client::LocalCallExecutor>; /// Creates new client instance used for tests. pub fn new() -> client::Client { diff --git a/substrate/test-runtime/src/lib.rs b/substrate/test-runtime/src/lib.rs index fb09970820225..d264417b547ca 100644 --- a/substrate/test-runtime/src/lib.rs +++ b/substrate/test-runtime/src/lib.rs @@ -68,6 +68,7 @@ pub mod api { use system; impl_stubs!( + authorities => |()| system::authorities(), execute_block => |block| system::execute_block(block), execute_transaction => |(header, utx)| system::execute_transaction(utx, header), finalise_block => |header| system::finalise_block(header) diff --git a/substrate/test-runtime/src/system.rs b/substrate/test-runtime/src/system.rs index eaa741fa3387e..7f57cbc28f6b2 100644 --- a/substrate/test-runtime/src/system.rs +++ b/substrate/test-runtime/src/system.rs @@ -18,6 +18,7 @@ //! and depositing logs. use rstd::prelude::*; +use primitives::AuthorityId; use runtime_io::{storage_root, enumerated_trie_root, ed25519_verify}; use runtime_support::{Hashable, storage}; use codec::{KeyedVec, Slicable}; @@ -26,6 +27,8 @@ use super::{AccountId, UncheckedTransaction, H256 as Hash, Block, Header}; const NONCE_OF: &[u8] = b"nonce:"; const BALANCE_OF: &[u8] = b"balance:"; const LATEST_BLOCK_HASH: &[u8] = b"latest"; +const AUTHORITY_AT: &'static[u8] = b":auth:"; +const AUTHORITY_COUNT: &'static[u8] = b":auth:len"; pub fn latest_block_hash() -> Hash { storage::get(LATEST_BLOCK_HASH).expect("There must always be a latest block") @@ -39,6 +42,14 @@ pub fn nonce_of(who: AccountId) -> u64 { storage::get_or(&who.to_keyed_vec(NONCE_OF), 0) } +/// Get authorities ar given block. +pub fn authorities() -> Vec { + let len: u32 = storage::unhashed::get(AUTHORITY_COUNT).expect("There are always authorities in test-runtime"); + (0..len) + .map(|i| storage::unhashed::get(&i.to_keyed_vec(AUTHORITY_AT)).expect("Authority is properly encoded in test-runtime")) + .collect() +} + /// Actually execute all transitioning for `block`. pub fn execute_block(block: Block) { let ref header = block.header; diff --git a/substrate/test-runtime/wasm/genesis.wasm b/substrate/test-runtime/wasm/genesis.wasm index 7daba8a15c5866dd375c889a8eeb5f9cec8e7dbe..e96de613391af9ad80d0a7c85189b2545a910ed4 100644 GIT binary patch literal 17273 zcmcIsU2GiJb-wen|GVT+k{!iz;=5xdAro0%l1uJS)kGsywge}so47&WsO9d^;y=4w z$`aB@ETFnZQ3Pt67J0}6TNH4C+CYl-r9go4!39zvE&Ncl0h$6V-~xKcgHZ&9fdpv3 z@7z1HyObO`c59lPx#!+{?z!jx+?kQ;>~1n?(o;SbIrSRC%LF=MKtVx z>Y96pV{WT!K0z2NSAjdXuVrMMbI1K#chZZuZWQC2gQd;R#>RG6xdh}1Sn0>Ha@d?; zbEm($HCWmktTa`Yz{(J~wYRBqLqzdrcjuP!#sHf;!bAZr->X6TRw%Nw2bcy4KKVG;C%RY(N?IUatMpHZ0a zI*yagI)!4fn9UZ8e#Y4WB;z@1DqDcaqIbV}s7krre0DS5-0t6U)l`3Pcd)b^zr4D& zw72!j>Q+xZnYE&*`XYj`)Y%(c-|nvtR^wguSr8p}_XhFOGQ_p6K6f%{5Bi<0-A;F~ zy1k{&PpqtNbv9OalVQ&m|8c#VQ9+C=Zh6jD?qEBb^;apmn*LJH5wRE-Uu@~qn#Jd1dl(Vy?`)hP<6^q zDXN*lPDv~6FS<&wYX^6erQl<#6r5J2BA`SR_@wd^39WY@McIM-NYd^(KZmwM|53y} zk~(ACb>(5X0)syNyM142In|Fm-^bigWb2YgglD#B>( zk-5T9bb$;o+s9xuz*Jy1yz~PyF&W^IiS8H^OW-1;OirFsI;(SmtMhLLo-W{zQl*lL z_VZB~xVSg6|AV~e-*Il|>~6THNE-r#+8K4Kp+xR$A?S$Ct<5N9Ai-J)`l7Q#NPZ2H zccLc~h%@7XXuQ7+0S?Hq{8V^?d~ z>km`{g=nL<(iVAjA?X5}W>lSA%k%D~hPna-ZwrDSrBm|J4+Zkh2;@j0KTaVIsG`}q zFwtB@g)NXH1?Yi-Y@KRRLv=vPw3~-*(ip2HjOF;UcM*!1m-&v&g-pm^2XnPYv>D1@ zJ4A^QBHyb}L_f;&7b*)KzCr*Nu3Ux>s9?GuafE*r=3Ye`%&Am!GA z?0hYRmR+r6byR!@Fw5zfJM6v z4=Ns zSF^$%vlAjocLGWcr(`E##v%gF3)CF`z(ie8NaqDbz|Ml4cnh%Bi~h10WrJx>&WF&0 zX*USnU?$9BD7>eqzCz{J*-OrEH9QOxF;YmEl&B9c@QmCAy8@uFVnis!bfaUbP}mn> z$TV0S6mSFHdC^}L(GnU?Gh$7|~= zdBZ2LDN$FPj{{p;JBu_c`ql&!z~~Kgc}p(8<07Gfz3KJPKdUg&V~&R#-Wv4q&=rT7 zMFgJ=ho`+o_Y^Kr>;V2B_IVnY0CovltWKRqKHxzyPeW^9EHF10opkTUC;&T6{e%L} zcmc(0bXM#O4a!^$%PM(dXfVN91bObo==Tay z;rI%aMvco$FK|KCAtF$KW&AH0!LlFXb*c15#&2HAyc+tihZ)A4EYx1V_IjkA$884T zC(Fb_P@QHFl)GT9nvYbqptIGCcM-;~!Xonmmw~0G-AmbqTQ@@$de4|!u(GI&^joDU zyB3zI0@yJKSqm$XnpXdsXG-FN1!Q|k%ZLgxiAX)G{w@DY>?EgZ4NuE3T>>`bG-rT^ zw?KpR#Y`h&9hMLX1?jYV7GWMZ$C!r1!#JV5xfIPsWL4v6L%Ajs!!iYlv>|%xOSlxk zRB(WJ+~ft(vMp$Mc_^@#8ptAzsap`Z(tQ4zyK423T< zT9|`gV0GY0t+xm^qMcfAk&GdCto0U$rNXpZ2QQFY0XNb(EXPIiMjwjE-~oi|DPgsO zSr;2Z)`_%-%r9edMVFs9jO64zr3xI?dDlv&3AooaHhSA{X*a{Tls= zwbkg~^U*Uj+7!aVEc&BDbdJ!Up2@K6qB)nvoPBG~KrU$>Vk{WLYR`q9;uaERgP9t0 zn)}E=-QXPV-g70Rhs{m9KXI$*oOa)J$%z^7zKgi$Mn7~gIQp5p7QLgu0!s4HKZZ4K z^e!U3pfHnh4+#l41%TB1bPYljnGWxOR1XC%Dj!w`A(tibTCEHH5QbvH!tF*L91Y!vZ}|C9eywo5O~jD4)rHp=>$S0VhjQzM3OP@{8`DW7kYx58&jg4k!Ud ze?dV>y(ol%>S__a3RO_TzGnoj@adBJH4vd_%AiF!R*ddRDFS@cSmAj(=jgy+3qEeH zz)0ng?gNx#A}o%iDL|!IFrbPrsZaugK&HxG-|^rB7-ofqN1o2U zh)T>z7{=<1c*33N?+Ot$3Q8ei?+`AT8=*f!^cJVQ)9?o=dka%5ynPynzqu^9+B^4bd*9LH=^%>dvk(Y=z zx-Yk(q8NFI!PAC&&SqG8^HTYCn0-CWF<1sjYHhF#AbS-e37gW2MQnk2n2E2f#vPrh zx*qHgG|Iu62po!Jya_5q=BKC*9nl9U6QTk7q90ALPJ#wYg{M^|%NrC%)k%2#DclFR zrvc<6uL`OnZ%|cep1=`Qwwxd45z!Ci2r&BlLi9A9Q80<$QwrrEH;H_wESBPnr9k1q z>JU+06pEqbfYK%01e3hZgM4rZ5x2rY(1U51BKl1fr<{QTw4^9@tb|M@g(<|qBbji5 zW5j>#psW(*8hU#M0&pj-*d$Cco43)xrg`G@pa@trrv+igR78n>T7l=VDr9~J>+u%L zB?LhK;r~+t(t^zraG2I-mV%Lj&73^3-gt0nRLMJHEJ7hYD>p8MoaF&`Ob!SEJGTCU zv&1lA9=$pYjFpk7vE8cBFL=_ekmD()e3-MiurEeB5g_87!tSjA(Xi~VSOL;YOS~8f zlV;*XbT~3IhnB5@MREszDDoB;hbqWQhxH=pJ{kc?Dt>!^ z9ML2IZY7z5*4yd$45JGz>K;71gRSOpEJw90LS2ocrRys)iVz6o``PRV5(0V z@&$+SXC#=1ek2MTq9Tw|u-I9Tyc^uZ2{PQ-!4Ydk_Y2Vk9xK2L2P}$A4^A9MhE-gd zoPP_NM68b0!FFC`9anU;_a1@-9&=Rt5}Oz5yEZ@onD(uXq)>JIyGJG2MR zgM;I#2m5e4cCxXL=Kx0}f@fl3iT;ca?Qmb_JqmE%SB#%KH0hzQN7`5nY3Q&Ygypp0 zkreL8AyQ~0Vf(CWw0OSMmf42A4mX1~w0i7_?xGwX(L@9TnT`kD3^_L_QY=A<*^4@fuuq6_ved@K^74Pk;jS*s;vREh&_sjyRzc3 zIzjOacgQ}<#EJm7WU67A6;ILjK|FGN1l9}ttaz||DxQLP1S66O@dn|f!L@HhozWw0 zEEXi)7yu&5Bmg>|eN-FQEMv1^EPg)_W9c$L z5zZ~w!+@f)+?9tgS#p<8)e~OgoWU+yG{ zdm;{O>ow*?QznYM<}wH7s}VS+r5oeEk0?K|OpiQK4~=K1Hc!w$q)rI(Fv4>$@&^t* zIYc{hjKmy)Gh-8}A#Ss-VM9N`hw}~}&f~QRx*+~SN%D9A4hujlpK1xR@nj(x1#~#z zcalTz%$p%p?WZqmZrGsX)` z3$MADJXBhE4aWFoY2h^)le0?epV0zY@^BCveIIWToW!}-6n^p0@~M+h7!)BpF7sEy6Up?2u81a4{*P4h^G*L7xCHD$Ru=0uVdXd{!h1yFCP> zn86Ca4KF5s68tjRN?2bq^h2052A9Nu@NvJl50yF~ z!?|6mmxI8IT)*m-vp5Ul-kG=@c_!;H1|+!l#R8MaFNP&+6F!pKfb(__a+8e5dWyOB zp?Q&w2T!!Wq=>N#MASNSD!$K=$hTNZ`!G)(sV`FY(E*5dPH(G9ZA-Y8=fB8 z9o!k%C{J4?3`tIx2Y@ckWCBd)^iZYw(qno!!WZMxNTZ-liK%eXAu_MTFyuYO)NIT< zfyuccj=#uvp7q+QU$KS9G$tRA7930ZDvRPn9%HG}$A@ELO47n3B4cIJIzmgFfwb^| zNw($i#hK$?oYafnHRbt`Q3Wvx3$KmC!3Q&bdK|73xE_a-rGg*Qus+=Cy@f+$qj0zm zGahn%z~>(eMWz4oT6`0q% zazhLGQYiK79$p2Io>RkD3*I9vc!sEEy!SlwqJ$g00Y@ei(svNP4QXO;$bysC91J>) zOvlLfF)$q>*hxz=2LL=3a3c;XLn-O>Obd=y-j#qI;Y|;Q^Gw70Hn+mDEL2E;x|G3; zE*_*WxchjU?Nx4a!F|uGW)nW*BzdC>AM(i3b9mN_1U`G0ugdc!bxbv9zV|YG4Jb3o zLu~s3JurjTM`Cta)VfmCrU>oR{y+9z-zdO$O~v9~Owhx?!5Vqz&z8nACa z-Vyer2e!tv4#(soGq67aFxJBjror08{^64@w&Ay-2I5wE*{28qmnScj0OsR0Z|lR1 zP~d4%ZH<9|1qTlaNPG-j#y$E-oU#*%IX*V-n;^!L5xfKe379^L^u4& zLaC7P+!eKSqu+Ou#|l=rzRSc^XYneic?qQS(=6T^*w2@~mHEov)?jrr*5;cs9pAj( z!AE6r@4SwdxBG+Dt(P}em*wleXZxL3@R42je1Esw+39pw2e2L4E{f%2X-s*1m;+|gJ(t~(+@J!#3V5V%uTQ6OY z`?2oucjJ}LtzA9x3ES~W8~8MDcVK30g79R@GA3)&spVU@E1lizdTV>2ok91y9$fDXbhoq7-Q(Qe`Q3i^+1*><$0qi)wY}Ai+h9hA*Jxuyk9;+^ zt9P$&?``x9Y$fh%VA|XUMT6_H-i^0<0NJ_kH}!hwMl8d@%YNJ)fL^ThextF)l%>0? zFK@-YmxdqlfsEC7WP2EV1E2Ku&c6u0ZVmKxvcpoG_Hnn6B(}Oc=x^g2Ns5g;+uGaM zI7iC*@e0IjI3_F>q36j5u}^5dkxx(Yd8%(F$%nI<^nPFD#H zZ?~lzc8HI1+wHG^y}Pl!yVsAu@r`!-daX6T*saI&i_O`_{OrnnP(O|<@li{OkHV~O zeRjDsyRumCH5P00jn>phA)!7$TL->gv)5|OE%rJmJ_@oW#(j{BrGaez}a#yFYf;3d>%$w>L4m)0k_m)K_|o4g9|{-#Y!=2VH1fHCCF<`us|( z(W=#&^~FZ*;~$*hmBras+*_V&E-cg^1-jXKarOYzrnUoeiX^Vg1Rg9*?OxPFVD|+dr!T5 zVRvsCuAe4{FZMU_(fEycx80Ti+HPNoI}BnSgt~MXBYC&orql0;(7$T$V7?~KV@tKU zm1e6GH{x!$+33voH1z;pscJt~59M3+r)QW#wA(wKt<^5QqrC-!*;z_sZg#%ji(6pq z*=HU;bZKRCfZ_Gp%51G(?=~0fwJw(ZRNV}h&}4|EPwB-smym8Ot#np5Fl({ZY|V9B z^=7Nv?R1uxKYiZJGC2*lb8`nVSw_b_jE`r#_?RDZT3Co%i*qZV`SOr0OiGrLyryk3 z2x2fF-;bmXOUs?^I+6q8>vk7wo#j@i)?A!#G!~~{xbV_LnY|w0ihD@J+U*y32agG6 z54M*C&J2yn=329`S+KlbtM$6eGrv3}&eFBCySKB06b}qB*-*R9e+Xf_kLe3@u-W;A z#rcKaB5bnx+0lPS;B({uiom)5W3_=oL8(B*e0{dpoNdkamRt2&v(tR$cbyRh9cS_0 zRu>Fhmw%=q{g({d?O#v8qp8scR^XJ9L_!k583bi( zwJ|ScCXwR_&hv|1r*V*0vE)>0m@Fl6UDrm|k{vrQm6a|#_RI5&(JW?Q_n(ASqjR)73yC_SwI^_iz99Z~vw}oC9y_^fBM_Jpb;-f$;3vv;Nrwf&Es- zI}q_(FiX7yJ|I7{zs-~4?p;*7ph>y3XY#SHa$BvH+^XT-0b1${j(=d%z2TDt15nQ zc6!Y-`RQ+ZwG$E?sR_W;w!4jwz%Ujx9&N0$Rm zPtQ3(Q#ek}9zH&||KaI(dKIDRgF&iS70kxz>YbT;aMD#ARAW%>&&je;v<&J9b%V3f zGz6Y|$BB3D-?n`cK3#Wh6jXx9^U5XP_dVaQM1Ccxlq!{a$&cf>QYrSjn?818_R+u_nmuzG&QCvd`1t-a$KP@I_#tnwoH1i{WK3McK&B6#nVV*U zP8>Yqy?RBSpPQXJetPQQ+~E_)y&DD|JbZlW=;70OwHs>#zL$D&b;riF8`Yr}KU~)M|Ti`W>&e*^43hZ)!n&)_+&ZI7fph zeZf1@X^aO^(n!7Wpqe!H`4ge1T$;36NkaqYSG5z(bjt9-TRHA|Njcp>AL-c4sOP!- zi1Ih~@+*}e?d4m_Z|>zA$|t>iRrw9QyvC4@~vWKKd^9;Wqld)dw*8Ug`rFeJ}O_jJ~BlfYJAjK7bMU^#YJlh&aLSv?ZZ#BN~#B zNhcv=40l;hNIS7X@g2Br?hsH#z9 zyoNc?~Jhr+C%>eMYS0-;>Z zDzt~Q!kH6SQ`;zQ%wWC3bZ({-Ha%>YXM-D&TV|uU!o#q4tdlckz)SL+SrkXqS8q%& z%wYd=7=(QWd+dnJU<7+Yi3*dTM@?afjFg%_HXa$lUzC$j>QhdTO$0&`GeUyusqU~7 zk&#X*3E|QR(~86}|M)4*Gj`J?-@qdsP|55w^OW1ORTn&Qm{v-GH}gyqkz}<3<`fe= z3|>8ULh!16Xab6?$f=kU37u0ZoIvPoap45Q)J)yV+>VBlRx?j|doy&6Ub)#h1ST&x zGl!3M`H~~Z^spyBj8Z~Xkx-SB%0!sJt}@2XNs^nEIs%vYNzkgq#bY%t18tRD62?`l z(^?xp75f8cF_q?TnZ7|VrPsG(n>jiCj+f3q@7>+>1jUjKrv6NVg{Y?QnnAEBJK3qF zFM215AJPv8Gh2P{77raw{}EaV{P=Qs47^P^kY4)3uWh{t^)nR5JGC}#a?SMGH3+cg zcOg5jOZvky8St{ctQ%`$jcTv!wK+S}L1}AnXpiSL{J0O7!TV{B)9VxONV}HakaoMz z!9tB5aXr)8G*@m=UALn`vhOl311hr$*J(zY_|4Y>qILWt?U@13ymO6sOfw z&48&JIMObq*!i2hu?|y_hIgz_!&C4&xT6^*-tAb~QX8MvE)iTYs?4t-epqkp=4c?( zH7K>YF&Rw4J>HGz6)XgH;|!SN(V#KpdCjU?M`)lGThxNcFKj|?ETjpTL;juY`&UWBWc-n1>PN{vL0PWXu_xSHPVsIaAI#llOQvuF#gp2uMVwhl@QDj^f z0$0!s8PYNW;%>Jk;D%c`~=aP31f;X6Wd%RKRdw{l9?4{in z5q}!A2LPATMZaB7H?=E$<=sdXLMjAQ@Y`{^q)J}9k`A_;ra25|_>uIYKP&7JHA89! z)aK~qX(IsxM?eJ=B&&WiMhK>iNr=zqI}M;CMwKR z*(OmWN*b!tKP+q8Gh$2kq4_;|2uvOdVpSPoh~*w-sML&KP-4bCVcmzFAUIkZU#j`_ z!el4e3JEnPipkB;%cWYzRb|s;pz4Hd#63hqQ+2{+GPSfW(-nB)G0$vu&b8b zhlKh`eTJs#=Lk_4f19ajma%ZzV~If4`eE#h*7bN3^=~@`_IjJrT6)Ppg@)i3#cC$L zB>Y-gXq;+P&RRrj;U+W^X?f#|Y7C-Hr`k2*Q}7Wp32S+7wjKT;tR;t$Kq{mGXolxt z`bF>5ELH+6v)L4|NN37e@-umoDe`eP-zKE=4O@M&d20zwcZHW}lb=_aCzvA4zJmFW`q;^oS(?P(3pJ2mn}S+96uu$#2#nDD#_j`Aw$v zc2rDO2{C}imzO5WLRnG*!OW(Hj-h7n;h9Oh$QO>NQ_oB*yximA-`h2+TXDTGtu??^ znbs1fHR>zRO=}HfskUZX#p?*uDjTdRDokq?(^|u{R&&#e)u*PJPN`TD&0;$1W?DlCcK_xs@AbZlIP}3Dw~vLUQKgR4d=n?h=KJ| z6Jv4@X3Hs7Zp^fh$oQ$qCfu|?31$3xsAT;5QS}+Wx|@i)jGq>`ir4FdQrZAWy|u}U zA7ckjiw;~F6Qo$E6A-%@%;`$!}J;)CJV53Ni#jLspbVi z7+mG2ELgb~r{7jc#v0d8zu^(8>EY6)@E)m9dT}t_lD^PNF9d0KMf!!Hb$v6C7yti@ zFwRFUi3>a`zpyH8rwgmoU-8q$HR(&L>`P|-bpCJdoZ(k|H!SnhzkKi9O#1KMf8#wo z|N2ure|$ubfAN<*HudzqOP9P;>BleD?ShjNdnSEV;UhvMF#9^9jX;#~pJpN?cPijxLD`Fh7;I}imaE!#v5v+npM$dP; z=cnupN_c$)s&XnN@{ZEz9zfqYYiURXq-|t zsUVbMk5<;^Qlg~lkYp3q=dfUj8B3!^A*I7t%8;?F1Q~v!iZ06+RmBs{j$tngEy%58 z8FJ^)zCZE;aifQrXjaQDJdv={>E*7BrKT!i;F)nT7N;-%A)yFFc!XVnBS{5ALCno6 zkUA7c@$c6BauFt7WG7irHBlT>(++^vSCu5QBI&t6i(Jqi!Q6H+)slQ>hCp8$3-T;z zcpcgVt!HN}P_FQZ9$@*X&}Kt|>42;9k}``aaWQ1c?I&!FtRx|1YB5WA03>xh*ue9A zDP1U8Zp87Feo_8=*j7Ufc_3k^m>Cs22THlbLL2s2A0CXny?2RCp&Y#pHBn`DH&mHl zOt)-ZXjt-rAyQ=*4{2z^ufbJGtOrdg(_bY{Dl2vnhZ$;F8VH+Yv}_`hDx(=?H0(#? z1yb9)a5)0SEC=`Rr3&2NNvj&WB;&#Tq@NB$5VqRXyHjz(IZdl`v56h*7*>gWga0c zWh;p>q+cNmQ^kT)RW5PP(qWFzrobNRAn`my_ zi44MNS{*oL*FGDOCv%#3>)B^8Goc8SC21ah+Jc%G6ScOcOg^m)jf-E=0Z5Uh*BVq3 z6$t~o- zN*j(cvI@E2ZbozD-f5voS-xD)Ej@=Rq=G5Bd%e>vHhd90n4!tM(Q&^gD<+UgNHzyf z1gWIp!p;0LlhQH*9$t?$tgQ0p@6M7yGs(_uj!mj-^_rk~$-> zT~PgE^PX(BR}~;}I-o<|*e9R~qlfLRO95N_iHY53i;cWDBq`gkK?a@SWbr`vK^h>A z&PYTVgrnU3SIvIaIQl zA}Wq1A^NON4B8OX%uGT$PXR$V)b!etl`S11Wf&Pg#Ml!h&-#6F*di8<@HG;z8w3zgx7 zm*z~eAtxZ&aMNZ5I>@A4@*x5wV|{a`MsChb^lZ+o!qWr)OG)`K2TZS&lqUv7%EiF1 zoJ9$nJyWi*AlpOYbV;GAmv+Awo=U5aunWzmIe9IibY8{sbcVHsf0O6qW!&a5?U9$8 z6jl+ZV-6a3YqK)Zauos4CO$1);H*#>=2IE87C-BZI97ab8}}g6LcLCD4cTQC!#?d`WCRNlggK zsfabw;=fAVguR45c7yHePSQ;M(_BO;sR=gLdTDrCkZ?aGR`Dt9*Hi0cv}jmwT}KKB zTGx8$YUCE)eX6KN2lt>46ibxt2!z6(%v1Nsw@?ot$j6Y*thmt*sv#ic^mf5JWp&d zqM%x1)T}T_Lc(H@lPb#wd8JU>|)sVkC#pLA8Vu5z}jr-H4TF`;yf?Xa@3-st$&{J0f5+kUfPhBTPs9pmBV|IR;Sw9-Wj4QP9yYwvdJitp?&^8lIfK|dkAjI zku|(f?)5JBlP>uwC40O#x#Z2*1_%>}TLYw-hymK--QO9K6Ujxa4Bg7A=q?qB`y`XD z25__hCO3FMCo1XV8_5~rBgiL&C!MIZ9Y$$tlA$-D#A?Dg#%B5|kK!b$JjMl{?d@Up zVGVw6B7B(oNFgS~&f{C@^JLI|327_+w`8ysI;n4^Um?@XGk->AK!a9E#-6VM8+0%n zEO5txj2Du&XbV=$hWrXf{*okALr;c{TIS+mESXW0QJB$|QMH_&j1mIzK1zNR0)oBb z-Hq}jx?$w98>a(c} zl_Q;Dnq!T%ibsMuQfzxX%66SoI2b0k$DUq>~E z)4!qii|UqV_~GCg#P3Ar2ve={7?c}^sufM>%)tu?O6+LJvBH9!E{)$q4&e;pV#miD zhxc@b)^{8M*aeScF7b^Ma9J!j`eaB`NRyX>&d@Xa=KXhdR%nO2Sx#0wiE+cjR?PDj zci9G;KBv!Vwau>Ya56L=oUaJ!c+gdF0sqm9;BgvdsEujt>HR;&=<(K_nld|9nNk3e77st(#qn>S;o(xLYxLGnoKK zf$#Dj2I1Vie>CTPP6&8qL>1#WUxM9Xx5c(GUH@9H4|1h7-K<;i;=Z+J;H1R$4q{X+k%(FX z*T_tVtqzATd^*}v^|B3%hXJxXIt%ma*em{;jF#DVC>G<5<=C;#43A;dp!*2qg6+&Ot)506!MFzsto?H)z~W_B7aQteT6)xq*UV zgqK-G?u+!JgguOHs2CD^Hd!${!2K09fRhRjGGJJ+jlHh^if{`NA-c(ctcS8E?y>b( z)a=r?U-V7TmX!})*>@Ep^Xc!+`qd`;mC}e+T=}F7P=G^bdNUD`bw<23(>c#v8B;Wv zmGPMK!Id#r&8&>aW&Fs7nX5r>P7DGjEMm?UsS51}3*KTQob2?eL9<|((+p7bBXt&| zAFFVf8h8$`!a;|FCw3XsJ5Scv$%(K~a5hX@Wiw$*VeTc;m+Xf24Z*M~uK4b+4EV+sAL{b06UL;_ivD44ca zREmZ1Ao2>wiiPo@)`;5N%+`#9SQzrf!gx?In%#GB3*SL{Bws9y36hjAZX?U%vuo@s z=WYqPGvWwvG!p0jkzSWKN;^EuH9>VldjNAhlUO`Nk=a$+C4&so#ye<_ouO>JLuyF{ z05;&>vUtdin1w_})sP!u4>G<>%qC7-nnZY+^-&aM?g9U#Ycm##a+6LeZkHYR9>D z$4+pIog~Iiieyf~mv>h%Rx9L!u}F_$%vjqQ%sOQ(#9=_qyW=r>(hEJA^b(Vp^eRHO z#e$1qiRq-2h&B5ci+wF^S3-Bf8Ln&E8IoYVfd8f*LBe1f{&+JC_QC;=;~`TDbju&^B5|Tk6|2ZCyDV647WLmua|HAeL~H0nwG& zbkGr9J=Uo_voFYr9w>;`bd)-^+nX)3T6yJUk)HoD{*muzxL;*z#YIxcyYOE$aYT9;hslIs~tEfJZl z1LUma)rm+<5jb{`xy?dg9=8M|JrL@R4s*<4pu+d(@l6p#TwK8gUUZ@7yV!@0pIy2--qUbtfbS!Chsi+>h%X<#`0Ola)3TEsyNIelS$I>0Nk zxY3ptcItsA$zz@&T(ZV_lq5!B7W44Qg%BQ=!*6kJli4=UjFt;=hOrV7KQ$Xrj9^d0 z1xyD;t`s(^g0|TZL%EsZbWH)ERH0>FzP2b>S3IpRo;Y&L@N6uet|^{26;B{lRGe3C z7X_W-=~cxOM~WHJYm2Aril^&~Cj?xfb0*-}v1QZCATAdI#alFRVzeyOzI?4$h(#uX zto5!4+grFr1r?|QQ2I+9?FPP#7Br`B$+r0Kul%?6#&H1 zR-=95xa7p~pJd{wW>*(SSCWZiR?XJm3wULVaa-o{pDJS+@!|_*ti0rnV6rDuWrbRW z*x9bGiQr0>HF>qw#RhMmhV|Yf4D6 z-E2(+xolHgbHEOhWB;=4BOl7yRd+i@DPo{bT@G@+EB1|4LmNgSFKIP`y-7)gWSiFw z9u-9)HNg+DY6FYCL7`PP&-qrc3-92&+ZdLw7V1~L7&KHY0tvaCk#&gjPS}hqN}Tzn zB-KPO=%Qz|PnR!PwCc1nZDn5(xK$hP+PJ-tWRT|)+nJwVpT4T4*J++z@AB>RHp?%$ z{C8dQ9alSRwArc_IF}XD$-k)Z;%o1WbTK}nH%#-re1a?6&)0ihEK+$%T!F9RFl>h2 z1#fMG16*j~=DNl&IR7(dl-n|Pi;3lGv?XkD*c+u^0#f`k5nlG|U;AU`NohwIvC4G(;v@I}Ghw#Dp!|#3$Xr!!hIhob`D5qt=<#d%} zUYCqZ6XD~eR>~&9ayGl}X^5|gb_GvW*xQx#>=UD^@lP83HMCT+e*yd z=9V=;9iwzHmO3V-K^X^kfbt_CFK~kKX(rAM*>;A$sF$P2Kf%GHc82~$dF>26C4hE@ z7IY+OGCCey5b)VP3_QA>p>J1$QlGvql#4gjb@tZNSN3P^?JcLTWu1IsHZz6gMq<}! zEvFX|7%NS`YwO^0`V>NM@B+f*8MpvAWQbkfrfb*%7Z8URcmct1JkF7~y!ZPo9rr(w z<$pO#KWyn9?^m+)BU$>`mrqi)hzudS^8^P`tdCN^(wVJ@u`yVVJg7G z!Z`EH4g?yE>oO2nxE_;(gygBxMDRFBvdwIbD+}|)qHG&}UXSWgl{pE~vm+Rib2?v^ zyqD1M%g7T*VSFEX0zn?icR6`4X&2-(n3!U}J$;s3Z?I2mm!iMbOVLE|gj!uGdGJ2t zaxQ7Ej^<=2Denp{lw=tydVH$H1_6>i@9Tzz7>%KHS4@9oh2~z}dD{XhryurnSDqP- zvO*adjS5Tta{4UOYWfab8@_ycLHVaO<>>o&l$X9=5J37aVc-!(84o@s;AeUm7-gpK z&uj`-_38Uk=}2d_I%Qt*vcwL$a){(aoFT985S`l%IGk@19?=b%Vn0N?BGihsgR?qa z!1^=Gm3?*;=JHH&wLj1N*0Q{kh0wLL6F1$Y$fQ0Ge^)1~5#EsABakdPBwW)kS_mYEjk|iM#AC@p|@UK~%cR3J928zQn2Lj1paZ+ZK zEHRuVZrT+~KDLdrP8!7?f~y%=cht?w%LQ^?UtC6*V^NPYJ%+q2A5f3J^#RnHzV!0| z0I}7T9LOe=olaVXy7x!)>#mU_DWRt~YOF*ZI5v#Tum+IPbZ}gXgR>*k52rf^XZtvV z!*_4VF~Ve~uUB6eG78zMsHqJq=Zs06Nrx*bwtrk36B1YAZsLkOn`UfvzrgWIQ-LxQ zIPQuq1A&XZvK(Ymn+x0m$_yX!GgA5n00VkB1z_X_#$42Rmx|g>UgJy^Ezg)5{dszC zW_cxrs)lKTC5WV|_4VAX(rLIN zV-q&d+F(PKaFr>WHEyA1PgT(5Gg-DLv@lQy+2J$o0(H=I46!U73^;Zuo3VpOCmXbt zN)>Wr@nuI_DE%OC=zKBnJ8*q-b=K(^*qUxh%~N(DUvw5|G&R=;Pf}Iz@FZ1rPrSvG zJ-y;lGnZyI`bEKL?-vCCR~3c8?jLElS9GlS2w zGUcHlqb`@>TzRDAqWenaG3hjV^EM_5=xV?6^lTEeIBRXeM{W(Awa$Z=Oam8k3pgr<~QN02zYaAhxePg!`O{`;|t>BHZ+s3%81Zg&Q+hFq&V>b&a8{z_t1hcH5 zV_IG-RY_(A9ru=%fVq~naaK^&2=gMC7sR{;=P3dX^c<-IFbDP)g3&Gjb6~G2WX&Op z=JFI>*PQngvgQKNyt)iP^I8s|cY2-(yWHWjS7=z+Aoxy}8Kd#Yu;wkyAu~qfk)dB! z!WhLDM`m7Pz38aCC;Qm&oZh@vCi})CNBFW5FxRs-a4#F7Fn7VcKp<*X)iy^U-PUPF2zv_DM6`1VLAM}yoE(zj>4Uhw`tdBiOkcnYJWH%I z`})G_*w_he&Hj5#tanNsN7C*h+PEMxDq72 zEl^w#(2F>fva3*(juO%qnawI?m!SY82yKyhf$QD5(c`68a=E>enD=~`r}t&#hcHwk zfR`jRl3Zx?9+MLXZ6H#h3tdtCss5J)TlwCc^8%SK$gd=1n-UAbQC@-N_5r&;5Q)HX z$AkUcqn+#v#roDnx)^cAfTc7GcqfX1@~9lDrhj%nf$dc8C*W)Bb1wdz?%<<@j)6#+ zwkIG~;XJf*^5BjLI0fMU-}|8{AGsRgTyN>8d+dKT*?;f9HG0`04xIk%#$a{dJDFWJ zYSw5=XHB+x?L?Rvgx=**x>A20bt`9iB}=}mXWGH5Fp1j`s_-ffi@s{U=u0?Jbo)VA zDmFFTK2g71_CLETj%&=+VhRYG6)_q~ZY4nAkS}LlEvS;4%W5TS*ecN)I)J!EtTRY@ z3uXsmT(x3f!+}7;8;Si$7FbIx3Q5-?$<~NQi9jG3C|2FB7D#&gEo(%{B1pPIvgDBT z&e|OaB)yx*4g?ae&-YW()eeLW$KuWouQ=t!>7oUk#2WdrKEnY7l2tm7b(}MvXgEHX zYF1>>Ix&PRkBK4rYA!nN_3lCQ8NjT3@Jbdjx(d$SwXfmX-BWKj$@rilz)UtQvvMt5 zT}{3UoIMpBGr=;qvTXj%pRnK9n|^a7*rF?DKY^%xOQUx?yLaW>x=M_uRzcHHL6c&= zevaf5fTQTK9{_(0D;f!qRn13ybv6rD1fsOUNbC>OiFrqNwU8d}VJn$2V zmEq*QDKt#sH;X{0kqC3P`Qq}gR6{E$r#_*9UIuK{s%w462lcmiJ$PR{VgJ^ap4f27 z+JX~U_AGKv7>Evk6#dJW%->J{qAP_9k@=zZudo+?FZD0@A@nb2+K-}tz1ITz^{-LL zdcXc%!7*ppy(|{%3BVGd%LG6mjLlUH@Bw5hA3@LBUN*@GzK;rCNtW3_Bdbph37izl z^;A)StEnR&vqT-ytIVfe)s=-Pif)piD7AhaQ?(^$tqV(GDh2zhSs9PHdA4c0(vcOG&U_naQQ|^D7wHKUd^{xwNe3=>WW93` zr&~6oxiu~v8E>@rBe~p>HH*1*UJE2WF?Ap)xy)Z&?#Naaj>CB^kgO^^Na4s}64bhJaNXDimqBa2hMFmN)s9flLyv6aZQ0eBOK0efcaiSP1JyDZSw2Dly5D8CD~#Zw|U9 zo_#YQfUVH`oT2#N#V_f`rJ<2)1w$+4HbvCtUh|rSFLk`g z_>wpykm8qgEZfw&Sl@}{YZq)W=<}i6y0@22Z8WgY{1fAlbL8Vgz0v6Cf^})FZ%B~X z8xjl|h5v2@+*hPKyCbYo@FF&&S;d>YeEgET?z-op>rDC#5C48#=OcEMDg2ycW`O|5 z(iwg`7$F*9xur|!Sf6J)>ymYP=`dNFG}gD*g0UnDUgSee7QJ(QWvhK&fU7IUY_Ue) z6l)w+JiK;-pudvv6&!shK%XH1v9oBr*2Y)K__`7nfGa9l>zaBH|Ac`<@7fQ%|5njk zOBH)-$p{nq+?du5Mvowy5ngNv+G}M3UCbfjV)>sdi=Ze=1!!>&P*|86P`7o=NKBV8 z@g|lsh6eBxqAQi4gs{v?mLuM43bxGFOF#M$FF3@q^iGuu#40iE zb|uRZcb%Q$gh%(=Y@32rzvifcm~%l%OB%E;+lz2xw@*83`fN)=2l6h%c7&^7-=cOf zb|orBg+vv;W!DB>DQ^|&>rgJk+eL*Lvkz<(>o2{UQiR^NOOmZ`M8dadmYXP#TUzXT zWMlXE{AllZ(bI<#a;Ic}Ou#eU)Y<&>+d+>;`Kd_gAEy51Bgb5oi-ZMy> zE|Q$*MNFSd@%o7JKDJ`Sk&vw?4NokY&n{estm5Mfod?*;iK7=9lH@VvF8Oy!k@e@tP`3YWZi#nHnL*k2#p zIy?0a{?6IKn`ck!Bfs&;JXM7A0{?`w*KFRuDjgU6m%;X&ACD}7LD)S1a%@Xi$JPyA z2HS^&_>2D7*x0tQ?PEK}c8={D8y}k(n;hG{ZEV}NZQHl)*tT=qu5IJnCbms(+r52k z`?l@dx9`}#bNjCCS?NCRkKCxqB=ftjw@rj9v$%);QW0Tt^w@>bv+&Q^xa(r@Pa&mI_Zb;ls_q##8 zn`XNKp)_I4_sKkO7~b3#WlZ7&Y4ONjpf>`6_VCx%W~WY$ZaI*gIG#);2VQ^T z`1FC~k*T9+rg=ppiidJUC#-?tnJ>kqpSk(wn}62hazV_WnL$A5xK;MN)udhWPzZUS zD#b_biA*90KS;Cv^rxz$e~Fd6d>zLBed~C9#FUSS(7%}YOf?tpXNe=0fDQn?ND8Ms z4{D_?J!-#Fj^}-mp`XNlzdR|b_k-&EAaaF6Z1{e7Fs+r|kbZ|DK|iehP6fr}QF5_J zx?C>aSB*y#pqiF3((6bMkkZ^cNy^y!2L5bSpPK`upTvIu?;p76(L8-WdOW@Z+GEaV z3+8m|Jq0*DH(MycLOqTjcyw+$;SUT>zcb-;iuL%qBK_9X@{g@S$nyF4f~|GOFgM$z80+MXmYQ)v~49d9!bxx9#IQ^fm4X`$rd0FMWC-ZtVs4N-zx-gIpYlSLVh}oSiPjW@#WU=Mr;qAnxQ`=I>5E zG@U$nV%BD2TI^GeDqueKgDMx36Tnw>kH zOwEC3X)taYpAdu$ba62LNzV^UrD{2>MAd4oRbL;kZLDq%53C;KOlEj^MQyderZf_) z4cApR`kM%hZYbRt+!WvJkA>U9?ZK1&Q^6;L--`Zu^}hxGz4W)y_rgmxB%u{w2%dT8 zqwjtHKOKAXTMj(_-uKOK*!cer4Bm6^&0BAK+t2R*y}vyF{ttZUmp}PCpZ-^$|H7C4 z_%B}kt7vF=%eI{pdv1Bn-LL!E^BMy=IFu3XXi47a~|J=L&#c%(&)oZTV zbjO{qzwa$?ecRjLb@ul@|CO(PR^~vA$m^I@e>oTxozL=^B?-&r8hiu=8Io? z;ZMK*fB*aMT|yyO#9ttU+~0ZZo6%#ht*$SXhR!`ba7*dPu4`6zzZtHr`cWsWM?0fR z=vT^>R(${9aOI8FAlgu`h1Iaa-+~MIE`KSE%l^Qs(w=arQZBWsZ{p9Vu4>*N{e$qP z(2s`7gN;4W#%r79dy`|)%(dMwmL9tht}8$Gx8YA$R>f;-D;p~tGv#`DUHL858%lT7 zZ;Tp|A8w0pjMkOou=_Mco6>uuo5H#3t>IvJ>qNEs>hfckhDNGehi(d!R(r7fzUZ-! ztc_Rw;#--yor z`E`{>snq?=p>xMt?;L$~`QE7e{;2!eaD6zaw@p_O3YUGKAqPlyQ0M)rmOm5?msSj~ z3|0l}!VRU3(Pn=pJQ92+_|xDkjjz`JEcjaR=l)XZrQqAi#ptj7|LS9eR>!~MH?F_s z$6kNm2S5JtUoTf`s`-(d1?3;-}8ZwFPGBS-*;&Gt#AAN zKREyMzgC-=|IkyF`Yk_pV*j`Q!&@Kt*soqc`o>>d_>F(|#FI}w^Xbn##~+1QwQ9x_otGmBd-W+X? zMyoqxzdL_!qPDtT?S6E3c(7Ur4LL$>^TaXcR`ZYOS(2xjt&t#=||Ob(N@5 zx&Q8o9nBq;^0@<>&YgRGbj{#D7+71WmtWhszIG*eN($!dLkU5F_;ci;^T|D1PM^$i<$pR3$a-x%I`_e3~Qtv~jOcYo}` zGjaF1U;51Xtsnf;$L{=%fBD$%%B!Q`z;*RI>Z7F>2ZytOs7H@M~_|KEzo{^M^p z&u#jNbMJdkbfnY_Yn9gjaiII3>!+*7*WA(l^qXtL-4C3*GyM5|gR6f2{u_SrtKF|{ zxu;Z*g7XWvmi_3BrS&_5a|1Uf!0x>EsxH*EpFv`i`kbPbUvdCsT>5lDNNN%&T?rNO#clHsBog5}nFR zY~^3(n%BnzcTJss*us_%5X_@Qn7ArbX%ehIsT1?oT-< z`a9K*x?@vw2Oq`_<8D|S96Tcm4-qabZit(wXAf>Y{pjhr>0=i7zp#O5pUz+1hyJGv z^e=6Q%g0X~KRCUIh=H0HA>`)1n){2Ik8g~tEH1dS}6ehl;uvHpaK4 zM~^0bf93gfa{A#DXO13num`7S1LeEJY(>za7;Pxbx`HH00W?qe}cSWz7o zSG$|y4fjvYP9L939@f-mBk8NLu!%U6m5Ie~`wS2Aay$>W%VZy}hb7t$cet zD9b@c$FMo5f!*q#k}+Z$i3KoAEYAz)jN}DFVCV@=q{(^u z+69KX`(Q8V$PgDa68jqRv#EHe`&o&htW3qMkeFj4s3{9tf)c7g3Y5%?MPkLO2&5zN zR`LynDq{p0DieJpZ4*S*C==h%7#c%AOE%)?pgxnjzM;}cMPo^yp=tDsG|Q!wY)_cQ z1da5l3hr1EBB;?8{3LsxHSU=js!!#}>lhi$YM_?B2R4DG=20<$s1z?sypZ`Wa80fr z=T13}Mw%m?1Q%Nl!Xh*50|tZ*K}ed~o`@U~ru#;QN^2w>yN@cXWG{WF1=TqGs*Y7E za4sss@dR*2(?E1%O804IO(glWZ;haSR-iIX?AK75s4A2r!EjdOWdTrmFE~&L%uf+@ zLJSkHI|s%wgjHZ5X3~$e8KfE&#IZ^DF4R%qP?={d!l8wZ4?xD1r%-tiB)|vc16Tpr zQ3KF*4M5j5fJ|lH01BuZpo*vxw_*x<1HywJ$={9u3W%=&lEBbo2B!fV4p5}PO@-bL z4rBouBHJb#p=+=nbjKGETM-ff-srn;uo!{9*if$vwe9?K9hs1!&3=p*l) ziWW_gujKITfF`kx4=d0A4IgkZrs za%_gr)*03tu%~IgO;5I(C%x9Of1C)=G?GsxJ}8y4#ox7qTh+lzJAn?BL3PPN)7NZAfq(gJ3W zko{utoV~ew6W)eL5ki|`)nJ{0kHB{Y=+Sljy*>WFNd%+muKs}@|2@#(v5sHu@jJku zTF3W${1WhCfDNt%ZuJCdF~}5$Gk=Qcr@myPe8R)u^_ihvT*m!YA!k{+O4X^Big~+Q z9tc?JKrmO@-Cs2w&$LQ*!LzJFA@4i{l3l?!rCGgVx>hbR zsFjK(({wDmXl^V8`Ya8WXFlO}dck(xnw7Ikj#nuZ+-h)Z_NZ>=Yfh=`6+PE=iseEz zEbnr$?&V9lV$}hzWtvsD68yN^oXA74!cM!evuf9@nzLj5^E!U->pBtKo;?)Y*(3KC gtX$Q}m2%Ze$uga?Ga07%Q6vyYS?TziV(K@zzfYP|I1sXT&-JShxn#9|RRH_h25K#rGGW#)GOg1*M zleQN~R0&js8kLT?b3hd*sI(FXP5}y{;?U4bxilaoBo0VOoZK7Zk4gq*rFrkoZ~kxQ z?at0T`T)Lu@AfBf>Sz@WA4nktD4juwPjLXi^N`Euc|M;{XW$(MGATeMJ_lRi?p@zS z!W5aJ_hD|iNUo4zbAvSfxD%|Q+4AH5M%QciykMi%X`{I(qE_3BTfN6JIe#^*W{@Z% zA&cC;ki5q2@WPFPC=J1h7sO|g;NB<>-RH%` zFJuAAbgj6`-4W;y#f9|uf{W?>lKRvg!Q_yOj)G9-s$g>ID(C*myRBr1P+y+&pXoY}30>?~hVt z`rydlxgSTe-wMgSnbj#MC&!;JbD0tn#OcI6{(Jt(gUZPctDy_}IabUyy)#_>kx@Ia z_CAbiUo!7OX}37|T*9vz_KMqCp`W5N_W5MrJ`a_+fn@oUT+X8tS2i_F(qW2q=) zT$j}c6mVQtpyP(!>F-1FA_gg)F8x~85*VjQ9BNUb%ZtXzx zjhA(&DZR8Jc`-X1vfTA9H!l$uyy{(kodkoC73qv5ZEA|-N@rK3K~vSC+G8iG)z<8$ zv}yTF!b}i%dYfIs;&1>$NwwYzbca}$hYiQnbuThsl;il)x~!=sMYVNXwG2fyblcRi zN@p9ZqT`uH5Mk_xz83{HPG}bpA|^eas*-46Itr`o5)|pg*1d@Yviz_kjGrYv5m-+!4Y6 diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index b7541aeb582fda727115a070472ae93056f93d90..d4abf81ec65900fa7d1a49f6755f169c6a8b17bb 100755 GIT binary patch delta 2295 zcmZuzU1$_n6u$TF?(FW&PBJkH{?vGPOlg!RncbP}&c@bcf~ysjYWfGIKkUy$cie1b zcT@GnwME3ADCkX9s1(|Vil}Irw<2vH+NY+_w?1g87O_55kQPdzedxI}v-v}Y;ofu3 zcYf|U=bo9F8GQaCzI_#w-i_=U?xCU}h+;T`4`PfXA&k0&IBsF<>A7!_6upcS>=7P+ zS1L?5_fOR;&5GSX{jz=7E=@P>_lwo3(g8Hk-KLxMLak9KH7ip!lU<#F(*%OOiETo+AP_6AUqQpWOqLI-B9I!OU3eUqZ5W8i> zA^t;2tB+`EC=cqPn`4m^YWS)evCu2BK*MA7o7j-hM_(U`x8{zCYoaV7sSw4G=*VL?U0U~I!~SmG9T zAVI`h4c=*VQ^>PLEX7efS~NCK!{Iuq*d~MHK((mM>mw;2^1Mkj!X+pRM~;9+nwTdP zhjz>pF^6`}6VypYXq<+2$|yl33F;NHa8iJ^6^Cr|#xbExm>#F(5U>>kk@l z`{bTte$R*wje_73&;44cwMgTB0C296V-k(u9HS>_ z?B?-g7&N^bCxGTVUP%eubH~5sG~6jEm$^42Sr?ZM2kRH!>{fq!SS**f$C$;uIq@PJ zRbJ+ErlqXIx7<&ZH!;5L{@FE-$v3Ci_U;VG+@sw^OzxdyzpdOsm(3z;&Yy)rFyEhY zcdoi5;K%NRb#Gw&m_1mxhV5DZ5RAE*o?TdZxCH*+S@*(*J5kKsS6|pb;ZH72JU%|OLHlN-h1MzcP}9t{t!s#c1nsk%Mbs8&k$ z;N()%52S+b!4Z&GFy6DPa_CDhY;$4RfcsKUIOnN(7lWJ+XDXIVFWXA z0lzEYe-8M~i}>Y$-vs`^BEA#wbHIn8xnX1*>TbtNT|_bTlwqWbWwV^irZeRN8{E2&$s=8OB};9!SClo=){I<6w+%zr z(`J4ZXS3rYuPH^XWTeu$Qo+b(N_tUazmHt#o-j>ApU7o%nr0fAtd?K9m(h_7`*B2c z=SJVgoApeoG-0HSoM{(zy;RN*RI49M4w&gw#?%Zwmjy3rJvEdXVtdA_ik6-*a|JtV zmr7=~pqJgbu`5`y)45c(Yy#KNv~sD)4sSF1(-4S0l+lOEnF(XU%=a(;zZh8hdojrN njuqKJKE6UXQe`uhOO=Z`Lo*9zUSm7+pFW>@*6&Ap+26Zul!GME!YFAa?=z-m#!!obz-g7{ z80qa}QxQ>_7$#h3)k3JCS`{sXpll%oEh1>qEP}e~Gc;P9`|&&fd(OG{a>j2!J`b~# zz-@0LQ_!$27%VL<4!~&u2owRT3YNeSB;JnAp>XIjgvl4^?kacBx%Qbsx7SMd*{4u* z&FX%z-^>n}_F$?ni-Za4cBjiZgb?)^D`C6v3i8hrtLPKF${cuH{xH`6Uc6Ik?jG7g3fNNyDGR3_Z zCBG^Ugbk`{v!@mCEX2hSVF~tf-8^{)APrskOx~I#hs@QI4OX+14Mhd2adM{+opt z0vt-@HiQdBMK)@c9*{+qH%#eBQ$JyVvmJJ}+ZDwublm{2hzaicrnp z#3y{m{Q&vYUSIJxfG$Ofvu4JH-!&2qCaSntV*Kf(u>L4 zu*a4SSJO zvvo{gblw8q6GW_Ox}Zspg*_~j(+B0KEn8Uh6hjaNOT%8Wd3`^{Sj Date: Fri, 11 May 2018 17:31:27 +0300 Subject: [PATCH 2/9] moved else --- polkadot/cli/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/polkadot/cli/src/lib.rs b/polkadot/cli/src/lib.rs index a30d8ee18f554..0934755f20d5d 100644 --- a/polkadot/cli/src/lib.rs +++ b/polkadot/cli/src/lib.rs @@ -143,12 +143,10 @@ pub fn run(args: I) -> error::Result<()> where if matches.is_present("collator") { info!("Starting collator."); role = service::Role::COLLATOR; - } - else if matches.is_present("validator") { + } else if matches.is_present("validator") { info!("Starting validator."); role = service::Role::VALIDATOR; - } - else if matches.is_present("light") { + } else if matches.is_present("light") { info!("Starting light."); role = service::Role::LIGHT; } From 4c0b5991f6e344c8e885a407747207fdf01134c8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 11 May 2018 19:33:58 +0300 Subject: [PATCH 3/9] Remote + Local Backend/PolkadotApi traits --- polkadot/api/src/lib.rs | 14 ++++++++++++-- polkadot/api/src/light.rs | 12 ++++++++---- polkadot/consensus/src/service.rs | 4 ++-- polkadot/service/src/lib.rs | 28 ++++++++++++++++----------- substrate/client/db/src/lib.rs | 2 ++ substrate/client/src/backend.rs | 6 ++++++ substrate/client/src/call_executor.rs | 4 ++-- substrate/client/src/in_mem.rs | 2 ++ substrate/client/src/light.rs | 2 ++ 9 files changed, 53 insertions(+), 21 deletions(-) diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index c9e1efeced36e..3119c88ddd673 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -36,7 +36,7 @@ extern crate substrate_keyring as keyring; pub mod light; -use client::backend::Backend; +use client::backend::{Backend, LocalBackend}; use client::{Client, LocalCallExecutor}; use polkadot_executor::Executor as LocalDispatch; use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; @@ -145,6 +145,12 @@ pub trait PolkadotApi { fn build_block(&self, parent: &Self::CheckedBlockId, timestamp: u64) -> Result; } +/// Mark for all Polkadot API implementations, that are making use of state data, stored locally. +pub trait LocalPolkadotApi: PolkadotApi {} + +/// Mark for all Polkadot API implementations, that are fetching required state data from remote nodes. +pub trait RemotePolkadotApi: PolkadotApi {} + /// A checked block ID used for the substrate-client implementation of CheckedBlockId; #[derive(Debug, Clone, Copy)] pub struct CheckedId(BlockId); @@ -180,7 +186,7 @@ macro_rules! with_runtime { }} } -impl PolkadotApi for Client>> +impl PolkadotApi for Client>> where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> { type CheckedBlockId = CheckedId; @@ -263,6 +269,10 @@ impl PolkadotApi for Client LocalPolkadotApi for Client>> + where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{} + /// A polkadot block builder. #[derive(Debug, Clone)] pub struct ClientBlockBuilder { diff --git a/polkadot/api/src/light.rs b/polkadot/api/src/light.rs index 5d1195bbc83a4..f543b586059e1 100644 --- a/polkadot/api/src/light.rs +++ b/polkadot/api/src/light.rs @@ -17,22 +17,22 @@ //! Strongly typed API for light Polkadot client. use std::sync::Arc; -use client::backend::Backend; +use client::backend::{Backend, RemoteBackend}; use client::{Client, CallExecutor}; use codec::Slicable; use state_machine; use primitives::{AccountId, BlockId, Hash, Index, SessionKey, Timestamp}; use primitives::parachain::DutyRoster; use runtime::{Block, UncheckedExtrinsic}; -use {PolkadotApi, BlockBuilder, CheckedBlockId, CheckedId, Result, ErrorKind}; +use {PolkadotApi, RemotePolkadotApi, BlockBuilder, CheckedBlockId, CheckedId, Result, ErrorKind}; /// Remote polkadot API implementation. -pub struct RemotePolkadotApi(pub Arc>); +pub struct RemotePolkadotApiWrapper(pub Arc>); /// Block builder for light client. pub struct LightBlockBuilder; -impl PolkadotApi for RemotePolkadotApi +impl PolkadotApi for RemotePolkadotApiWrapper where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> { type CheckedBlockId = CheckedId; @@ -78,6 +78,10 @@ impl PolkadotApi for RemotePolkadotApi } } +impl RemotePolkadotApi for RemotePolkadotApiWrapper + where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{} + impl BlockBuilder for LightBlockBuilder { fn push_extrinsic(&mut self, _extrinsic: UncheckedExtrinsic) -> Result<()> { Err(ErrorKind::UnknownRuntime.into()) diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index b58b2e361b78f..c5e3d8315bd55 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -31,7 +31,7 @@ use runtime_support::Hashable; use primitives::{Hash, AuthorityId}; use primitives::block::{Id as BlockId, HeaderHash, Header}; use polkadot_primitives::parachain::{BlockData, Extrinsic, CandidateReceipt}; -use polkadot_api::PolkadotApi; +use polkadot_api::LocalPolkadotApi; use bft::{self, BftService}; use transaction_pool::TransactionPool; use ed25519; @@ -228,7 +228,7 @@ impl Service { key: ed25519::Pair, ) -> Service where - A: PolkadotApi + Send + Sync + 'static, + A: LocalPolkadotApi + Send + Sync + 'static, C: BlockchainEvents + ChainHead + bft::BlockImport + bft::Authorities + Send + Sync + 'static, { let (signal, exit) = ::exit_future::signal(); diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 95b55118ee8a3..984e73a8c2894 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -286,7 +286,8 @@ pub fn new_light(config: Configuration) -> Result Result Result>, error::Error> { Service::new(|db_settings, executor, genesis_builder: GenesisBuilder| Ok((Arc::new(client_db::new_client(db_settings, executor, genesis_builder)?), None)), - |client| client.clone(), + |client| client, + |client, network, tx_pool, keystore| { + // Load the first available key. Code above makes sure it exisis. + let key = keystore.load(&keystore.contents()?[0], "")?; + info!("Using authority key {:?}", key.public()); + Ok(Some(consensus::Service::new(client.clone(), client.clone(), network, tx_pool, key))) + }, config) } @@ -305,7 +312,7 @@ impl Service client::error::Error: From<<::State as state_machine::backend::Backend>::Error> { /// Creates and register protocol with the network service - fn new(client_creator: F, api_creator: G, mut config: Configuration) -> Result + fn new(client_creator: F, api_creator: G, consensus_creator: C, mut config: Configuration) -> Result where F: FnOnce( client_db::DatabaseSettings, @@ -315,6 +322,12 @@ impl Service G: Fn( Arc>, ) -> Arc, + C: Fn( + Arc>, + Arc, + Arc>, + &Keystore + ) -> Result, error::Error>, A: PolkadotApi + Send + Sync + 'static, { use std::sync::Barrier; @@ -405,14 +418,7 @@ impl Service barrier.wait(); // Spin consensus service if configured - let consensus_service = if config.roles & Role::VALIDATOR == Role::VALIDATOR { - // Load the first available key. Code above makes sure it exisis. - let key = keystore.load(&keystore.contents()?[0], "")?; - info!("Using authority key {:?}", key.public()); - Some(consensus::Service::new(client.clone(), api, network.clone(), transaction_pool.clone(), key)) - } else { - None - }; + let consensus_service = consensus_creator(client.clone(), network.clone(), transaction_pool.clone(), &keystore)?; Ok(Service { thread: Some(thread), diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 40df3b6f49bcf..6ba67621d8ecc 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -535,6 +535,8 @@ impl client::backend::Backend for Backend { } } +impl client::backend::LocalBackend for Backend {} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/client/src/backend.rs b/substrate/client/src/backend.rs index 9788fc2b7c80e..f6c44275f8aed 100644 --- a/substrate/client/src/backend.rs +++ b/substrate/client/src/backend.rs @@ -62,3 +62,9 @@ pub trait Backend: Send + Sync { /// Returns state backend with post-state of given block. fn state_at(&self, block: BlockId) -> error::Result; } + +/// Mark for all Backend implementations, that are making use of state data, stored locally. +pub trait LocalBackend: Backend {} + +/// Mark for all Backend implementations, that are fetching required state data from remote nodes. +pub trait RemoteBackend: Backend {} diff --git a/substrate/client/src/call_executor.rs b/substrate/client/src/call_executor.rs index 3ba06eb138e6c..15599d455d947 100644 --- a/substrate/client/src/call_executor.rs +++ b/substrate/client/src/call_executor.rs @@ -83,7 +83,7 @@ impl Clone for LocalCallExecutor where E: Clone { impl CallExecutor for LocalCallExecutor where - B: backend::Backend, + B: backend::LocalBackend, E: CodeExecutor, error::Error: From<<::State as StateBackend>::Error>, { @@ -115,7 +115,7 @@ impl RemoteCallExecutor { impl CallExecutor for RemoteCallExecutor where - B: backend::Backend, + B: backend::RemoteBackend, E: CodeExecutor, error::Error: From<<::State as StateBackend>::Error>, { diff --git a/substrate/client/src/in_mem.rs b/substrate/client/src/in_mem.rs index 3b29807bd188b..7bf2361804827 100644 --- a/substrate/client/src/in_mem.rs +++ b/substrate/client/src/in_mem.rs @@ -250,3 +250,5 @@ impl backend::Backend for Backend { } } } + +impl backend::LocalBackend for Backend {} diff --git a/substrate/client/src/light.rs b/substrate/client/src/light.rs index 800754321d4ef..9f60ae21c097a 100644 --- a/substrate/client/src/light.rs +++ b/substrate/client/src/light.rs @@ -93,6 +93,8 @@ impl backend::Backend for Backend { } } +impl backend::RemoteBackend for Backend {} + impl backend::BlockImportOperation for BlockImportOperation { type State = OnDemandState; From 218b2ed30e06f05bccf1eee1c4b8b11087f02712 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 18 May 2018 16:03:13 +0300 Subject: [PATCH 4/9] removed redundant header_hash --- substrate/client/src/in_mem.rs | 7 +------ substrate/client/src/light.rs | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/substrate/client/src/in_mem.rs b/substrate/client/src/in_mem.rs index 7bf2361804827..cb9102fb7829c 100644 --- a/substrate/client/src/in_mem.rs +++ b/substrate/client/src/in_mem.rs @@ -26,11 +26,6 @@ use primitives::block::{self, Id as BlockId, HeaderHash}; use blockchain::{self, BlockStatus}; use state_machine::backend::{Backend as StateBackend, InMemory}; -/// Compute block header hash. -pub fn header_hash(header: &block::Header) -> block::HeaderHash { - header.blake2_256().into() -} - struct PendingBlock { block: Block, is_best: bool, @@ -231,7 +226,7 @@ impl backend::Backend for Backend { fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> { if let Some(pending_block) = operation.pending_block { - let hash = header_hash(&pending_block.block.header); + let hash = pending_block.block.header.blake2_256().into(); let old_state = &operation.old_state; self.states.write().insert(hash, operation.new_state.unwrap_or_else(|| old_state.clone())); self.blockchain.insert(hash, pending_block.block.header, pending_block.block.justification, pending_block.block.body, pending_block.is_best); diff --git a/substrate/client/src/light.rs b/substrate/client/src/light.rs index 9f60ae21c097a..0b5c473ceff17 100644 --- a/substrate/client/src/light.rs +++ b/substrate/client/src/light.rs @@ -20,6 +20,7 @@ use std::sync::Arc; use primitives; use primitives::block::{self, Id as BlockId, HeaderHash}; +use runtime_support::Hashable; use state_machine::CodeExecutor; use state_machine::backend::Backend as StateBackend; use blockchain::{self, BlockStatus}; @@ -27,7 +28,7 @@ use backend; use call_executor::RemoteCallExecutor; use client::{Client, GenesisBuilder}; use error; -use in_mem::{header_hash, Blockchain as InMemBlockchain}; +use in_mem::Blockchain as InMemBlockchain; /// Light client data fetcher. pub trait Fetcher: Send + Sync { @@ -76,7 +77,7 @@ impl backend::Backend for Backend { fn commit_operation(&self, operation: Self::BlockImportOperation) -> error::Result<()> { if let Some(pending_block) = operation.pending_block { - let hash = header_hash(&pending_block.header); + let hash = pending_block.header.blake2_256().into(); self.blockchain.storage.insert(hash, pending_block.header, pending_block.justification, None, pending_block.is_best); } Ok(()) From f4070e2b1732353112b3a08382a7f1f05817323a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 May 2018 12:09:39 +0300 Subject: [PATCH 5/9] introduce FetchChecker for Fetcher --- polkadot/service/src/lib.rs | 8 +- substrate/client/src/call_executor.rs | 80 +++++++++------- substrate/client/src/client.rs | 2 +- substrate/client/src/light.rs | 74 ++++++++++++--- substrate/network/src/on_demand.rs | 126 +++++++++++++++++--------- 5 files changed, 196 insertions(+), 94 deletions(-) diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 984e73a8c2894..371d14d2ce476 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -280,10 +280,12 @@ impl client::GenesisBuilder for GenesisBuilder { } /// Creates light client and register protocol with the network service -pub fn new_light(config: Configuration) -> Result>, error::Error> { // TODO: light +pub fn new_light(config: Configuration) -> Result>, error::Error> { Service::new(move |_, executor, genesis_builder: GenesisBuilder| { - let fetcher = Arc::new(network::OnDemand::new()); - let client = client::light::new_light(fetcher.clone(), executor, genesis_builder)?; + let client_backend = client::light::new_light_backend(); + let fetch_checker = Arc::new(client::light::new_fetch_checker(client_backend.clone(), executor)); + let fetcher = Arc::new(network::OnDemand::new(fetch_checker)); + let client = client::light::new_light(client_backend, fetcher.clone(), genesis_builder)?; Ok((Arc::new(client), Some(fetcher))) }, |client| Arc::new(polkadot_api::light::RemotePolkadotApiWrapper(client.clone())), diff --git a/substrate/client/src/call_executor.rs b/substrate/client/src/call_executor.rs index 15599d455d947..0af9d2ceeef37 100644 --- a/substrate/client/src/call_executor.rs +++ b/substrate/client/src/call_executor.rs @@ -23,7 +23,7 @@ use triehash::trie_root; use backend; use blockchain::Backend as ChainBackend; use error; -use light::Fetcher; +use light::{Fetcher, RemoteCallRequest}; /// Information regarding the result of a call. #[derive(Debug)] @@ -59,9 +59,8 @@ pub struct LocalCallExecutor { /// Call executor that executes methods on remote node, querying execution proof /// and checking proof by re-executing locally. -pub struct RemoteCallExecutor { +pub struct RemoteCallExecutor { backend: Arc, - executor: E, fetcher: Arc, } @@ -106,20 +105,19 @@ impl CallExecutor for LocalCallExecutor } } -impl RemoteCallExecutor { +impl RemoteCallExecutor { /// Creates new instance of remote call executor. - pub fn new(backend: Arc, executor: E, fetcher: Arc) -> Self { - RemoteCallExecutor { backend, executor, fetcher } + pub fn new(backend: Arc, fetcher: Arc) -> Self { + RemoteCallExecutor { backend, fetcher } } } -impl CallExecutor for RemoteCallExecutor +impl CallExecutor for RemoteCallExecutor where B: backend::RemoteBackend, - E: CodeExecutor, error::Error: From<<::State as StateBackend>::Error>, { - type Error = E::Error; + type Error = error::Error; fn call(&self, id: &BlockId, method: &str, call_data: &[u8]) -> error::Result { let block_hash = match *id { @@ -128,40 +126,52 @@ impl CallExecutor for RemoteCallExecutor .ok_or_else(|| error::ErrorKind::UnknownBlock(BlockId::Number(number)))?, }; - let (remote_result, remote_proof) = self.fetcher.execution_proof(block_hash, method, call_data)?; - - // code below will be replaced with proper proof check once trie-based proofs will be possible - - let remote_state = state_from_execution_proof(remote_proof); - let remote_state_root = trie_root(remote_state.pairs().into_iter()).0; + self.fetcher.remote_call(RemoteCallRequest { + block: block_hash, + method: method.into(), + call_data: call_data.to_vec(), + }) + } - let local_header = self.backend.blockchain().header(BlockId::Hash(block_hash))?; - let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(BlockId::Hash(block_hash)))?; - let local_state_root = local_header.state_root; + fn call_at_state(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { + Err(error::ErrorKind::NotAvailableOnLightClient.into()) + } +} - if remote_state_root != *local_state_root { - return Err(error::ErrorKind::InvalidExecutionProof.into()); - } +/// Check remote execution proof. +pub fn check_execution_proof(backend: &B, executor: &E, request: &RemoteCallRequest, remote_proof: (Vec, Vec>)) -> Result + where + B: backend::RemoteBackend, + E: CodeExecutor, + error::Error: From<<::State as StateBackend>::Error>, +{ + let (remote_result, remote_proof) = remote_proof; - let mut changes = OverlayedChanges::default(); - let (local_result, _) = state_machine::execute( - &remote_state, - &mut changes, - &self.executor, - method, - call_data, - )?; + let remote_state = state_from_execution_proof(remote_proof); + let remote_state_root = trie_root(remote_state.pairs().into_iter()).0; - if local_result != remote_result { - return Err(error::ErrorKind::InvalidExecutionProof.into()); - } + let local_header = backend.blockchain().header(BlockId::Hash(request.block))?; + let local_header = local_header.ok_or_else(|| error::ErrorKind::UnknownBlock(BlockId::Hash(request.block)))?; + let local_state_root = local_header.state_root; - Ok(CallResult { return_data: local_result, changes }) + if remote_state_root != *local_state_root { + return Err(error::ErrorKind::InvalidExecutionProof.into()); } - fn call_at_state(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { - Err(error::ErrorKind::NotAvailableOnLightClient.into()) + let mut changes = OverlayedChanges::default(); + let (local_result, _) = state_machine::execute( + &remote_state, + &mut changes, + executor, + &request.method, + &request.call_data, + )?; + + if local_result != remote_result { + return Err(error::ErrorKind::InvalidExecutionProof.into()); } + + Ok(CallResult { return_data: local_result, changes }) } /// Convert state to execution proof. Proof is simple the whole state (temporary). diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index 1e68bd251dc33..2e298b6563e7c 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -192,7 +192,7 @@ impl Client where } /// Expose backend reference. To be used in tests only - pub fn backend(&self) -> &B { + pub fn backend(&self) -> &Arc { &self.backend } diff --git a/substrate/client/src/light.rs b/substrate/client/src/light.rs index 0b5c473ceff17..c9a2992fec598 100644 --- a/substrate/client/src/light.rs +++ b/substrate/client/src/light.rs @@ -25,15 +25,32 @@ use state_machine::CodeExecutor; use state_machine::backend::Backend as StateBackend; use blockchain::{self, BlockStatus}; use backend; -use call_executor::RemoteCallExecutor; +use call_executor::{CallResult, RemoteCallExecutor, check_execution_proof}; use client::{Client, GenesisBuilder}; use error; use in_mem::Blockchain as InMemBlockchain; -/// Light client data fetcher. +/// Remote call request. +pub struct RemoteCallRequest { + /// Call at state of given block. + pub block: HeaderHash, + /// Method to call. + pub method: String, + /// Call data. + pub call_data: Vec, +} + +/// Light client data fetcher. Implementations of this trait must check if remote data +/// is correct (see FetchedDataChecker) and return already checked data. pub trait Fetcher: Send + Sync { - /// Fetch method execution proof. - fn execution_proof(&self, block: HeaderHash, method: &str, call_data: &[u8]) -> error::Result<(Vec, Vec>)>; + /// Fetch remote call result. + fn remote_call(&self, request: RemoteCallRequest) -> error::Result; +} + +/// Light client remote data checker. +pub trait FetchChecker: Send + Sync { + /// Check remote method execution proof. + fn check_execution_proof(&self, request: &RemoteCallRequest, remote_proof: (Vec, Vec>)) -> error::Result; } /// Light client backend. @@ -58,6 +75,14 @@ pub struct OnDemandState { _block: HeaderHash, } +/// Remote data checker. +pub struct LightDataChecker { + /// Backend reference. + backend: Arc, + /// Executor. + executor: E, +} + struct PendingBlock { header: block::Header, justification: Option, @@ -172,19 +197,42 @@ impl StateBackend for OnDemandState { } } -/// Create an instance of in-memory client. -pub fn new_light( - fetcher: Arc, - executor: E, - genesis_builder: F -) -> error::Result>> +impl FetchChecker for LightDataChecker where E: CodeExecutor, - F: GenesisBuilder, { + fn check_execution_proof(&self, request: &RemoteCallRequest, remote_proof: (Vec, Vec>)) -> error::Result { + check_execution_proof(&*self.backend, &self.executor, request, remote_proof) + } +} + +/// Create an instance of light client backend. +pub fn new_light_backend() -> Arc { let storage = InMemBlockchain::new(); let blockchain = Blockchain { storage }; - let backend = Arc::new(Backend { blockchain }); - let executor = RemoteCallExecutor::new(backend.clone(), executor, fetcher); + Arc::new(Backend { blockchain }) +} + +/// Create an instance of light client. +pub fn new_light( + backend: Arc, + fetcher: Arc, + genesis_builder: F +) -> error::Result>> + where + F: GenesisBuilder, +{ + let executor = RemoteCallExecutor::new(backend.clone(), fetcher); Client::new(backend, executor, genesis_builder) } + +/// Create an instance of fetch data checker. +pub fn new_fetch_checker( + backend: Arc, + executor: E, +) -> LightDataChecker + where + E: CodeExecutor, +{ + LightDataChecker { backend, executor } +} diff --git a/substrate/network/src/on_demand.rs b/substrate/network/src/on_demand.rs index 645f6ed70f391..9ba360748ae75 100644 --- a/substrate/network/src/on_demand.rs +++ b/substrate/network/src/on_demand.rs @@ -17,15 +17,14 @@ //! On-demand requests service. use std::collections::VecDeque; -use std::sync::Weak; +use std::sync::{Arc, Weak}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::{Instant, Duration}; use linked_hash_map::LinkedHashMap; use linked_hash_map::Entry; use parking_lot::Mutex; -use primitives::block::HeaderHash; use client; -use client::light::Fetcher; +use client::light::{Fetcher, FetchChecker, RemoteCallRequest}; use io::SyncIo; use message; use network::PeerId; @@ -52,11 +51,12 @@ pub trait OnDemandService: Send + Sync { /// On-demand requests service. Dispatches requests to appropriate peers. pub struct OnDemand { core: Mutex>, + checker: Arc, } /// On-demand response. pub struct Response { - receiver: Receiver, + receiver: Receiver, } #[derive(Default)] @@ -71,20 +71,22 @@ struct OnDemandCore { struct Request { id: u64, timestamp: Instant, - sender: Sender, - message: message::RemoteCallRequest, + sender: Sender, + request: RemoteCallRequest, } impl Response { - fn wait(&mut self) -> client::error::Result<(Vec, Vec>)> { - self.receiver.recv().map(|r| (r.value, r.proof)).map_err(|_| unimplemented!()) + fn wait(&mut self) -> client::error::Result { + self.receiver.recv() + .map_err(|_| client::error::ErrorKind::Backend("OnDemand serivice has gone".into()).into()) } } impl OnDemand where E: service::ExecuteInContext { /// Creates new on-demand service. - pub fn new() -> Self { + pub fn new(checker: Arc) -> Self { OnDemand { + checker, core: Mutex::new(OnDemandCore { service: Weak::new(), next_request_id: 0, @@ -101,7 +103,7 @@ impl OnDemand where E: service::ExecuteInContext { } /// Execute method call on remote node, returning execution result and proof. - pub fn remote_call(&self, block: HeaderHash, method: &str, data: &[u8]) -> Response { + pub fn remote_call(&self, request: RemoteCallRequest) -> Response { let (sender, receiver) = channel(); let result = Response { receiver: receiver, @@ -109,12 +111,7 @@ impl OnDemand where E: service::ExecuteInContext { { let mut core = self.core.lock(); - core.insert(sender, message::RemoteCallRequest { - id: 0, - block: block, - method: method.into(), - data: data.to_vec(), - }); + core.insert(sender, request); core.dispatch(); } @@ -151,9 +148,17 @@ impl OnDemandService for OnDemand where E: service::ExecuteInContext { fn on_remote_response(&self, io: &mut SyncIo, peer: PeerId, response: message::RemoteCallResponse) { let mut core = self.core.lock(); match core.remove(peer, response.id) { - Some(request) => { - // we do not bother if receiver has been dropped already - let _ = request.sender.send(response); + Some(request) => match self.checker.check_execution_proof(&request.request, (response.value, response.proof)) { + Ok(response) => { + // we do not bother if receiver has been dropped already + let _ = request.sender.send(response); + }, + Err(error) => { + trace!(target: "sync", "Failed to check remote response from peer {}: {}", peer, error); + io.disconnect_peer(peer); + core.remove_peer(peer); + core.insert(request.sender, request.request); + }, }, None => { trace!(target: "sync", "Invalid remote response from peer {}", peer); @@ -167,8 +172,8 @@ impl OnDemandService for OnDemand where E: service::ExecuteInContext { } impl Fetcher for OnDemand where E: service::ExecuteInContext { - fn execution_proof(&self, block: HeaderHash, method: &str, call_data: &[u8]) -> client::error::Result<(Vec, Vec>)> { - self.remote_call(block, method, call_data).wait().map_err(|_| unimplemented!()) + fn remote_call(&self, request: RemoteCallRequest) -> client::error::Result { + self.remote_call(request).wait() } } @@ -203,14 +208,15 @@ impl OnDemandCore where E: service::ExecuteInContext { } } - pub fn insert(&mut self, sender: Sender, mut message: message::RemoteCallRequest) { - message.id = self.next_request_id; + pub fn insert(&mut self, sender: Sender, request: RemoteCallRequest) { + let request_id = self.next_request_id; self.next_request_id += 1; + self.pending_requests.push_back(Request { - id: message.id, + id: request_id, timestamp: Instant::now(), sender, - message, + request, }); } @@ -243,8 +249,16 @@ impl OnDemandCore where E: service::ExecuteInContext { request.timestamp = Instant::now(); trace!(target: "sync", "Dispatching remote request {} to peer {}", request.id, peer); - service.execute_in_context(|ctx, protocol| - protocol.send_message(ctx, peer, message::Message::RemoteCallRequest(request.message.clone()))); + service.execute_in_context(|ctx, protocol| { + let message = message::RemoteCallRequest { + id: request.id, + block: request.request.block, + method: request.request.method.clone(), + data: request.request.call_data.clone(), + }; + + protocol.send_message(ctx, peer, message::Message::RemoteCallRequest(message)) + }); self.active_peers.insert(peer, request); } } @@ -256,6 +270,8 @@ mod tests { use std::sync::Arc; use std::time::Instant; use parking_lot::RwLock; + use client; + use client::light::{FetchChecker, RemoteCallRequest}; use io::NetSyncIo; use message; use network::PeerId; @@ -265,14 +281,27 @@ mod tests { use super::{REQUEST_TIMEOUT, OnDemand, OnDemandService}; struct DummyExecutor; + struct DummyFetchChecker { ok: bool } impl ExecuteInContext for DummyExecutor { fn execute_in_context(&self, _closure: F) {} } - fn dummy() -> (Arc, Arc>) { + impl FetchChecker for DummyFetchChecker { + fn check_execution_proof(&self, _request: &RemoteCallRequest, remote_proof: (Vec, Vec>)) -> client::error::Result { + match self.ok { + true => Ok(client::CallResult { + return_data: remote_proof.0, + changes: Default::default(), + }), + false => Err(client::error::ErrorKind::Backend("Test error".into()).into()), + } + } + } + + fn dummy(ok: bool) -> (Arc, Arc>) { let executor = Arc::new(DummyExecutor); - let service = Arc::new(OnDemand::new()); + let service = Arc::new(OnDemand::new(Arc::new(DummyFetchChecker { ok }))); service.set_service_link(Arc::downgrade(&executor)); (executor, service) } @@ -292,7 +321,7 @@ mod tests { #[test] fn knows_about_peers_roles() { - let (_, on_demand) = dummy(); + let (_, on_demand) = dummy(true); on_demand.on_connect(0, Role::LIGHT); on_demand.on_connect(1, Role::FULL); on_demand.on_connect(2, Role::COLLATOR); @@ -302,7 +331,7 @@ mod tests { #[test] fn disconnects_from_idle_peer() { - let (_, on_demand) = dummy(); + let (_, on_demand) = dummy(true); on_demand.on_connect(0, Role::FULL); assert_eq!(1, total_peers(&*on_demand)); on_demand.on_disconnect(0); @@ -311,7 +340,7 @@ mod tests { #[test] fn disconnects_from_timeouted_peer() { - let (_x, on_demand) = dummy(); + let (_x, on_demand) = dummy(true); let queue = RwLock::new(VecDeque::new()); let mut network = TestIo::new(&queue, None); @@ -320,7 +349,7 @@ mod tests { assert_eq!(vec![0, 1], on_demand.core.lock().idle_peers.iter().cloned().collect::>()); assert!(on_demand.core.lock().active_peers.is_empty()); - on_demand.remote_call(Default::default(), "test", &[]); + on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); assert_eq!(vec![1], on_demand.core.lock().idle_peers.iter().cloned().collect::>()); assert_eq!(vec![0], on_demand.core.lock().active_peers.keys().cloned().collect::>()); @@ -332,20 +361,34 @@ mod tests { } #[test] - fn disconnects_from_peer_on_incorrect_response() { - let (_x, on_demand) = dummy(); + fn disconnects_from_peer_on_response_with_wrong_id() { + let (_x, on_demand) = dummy(true); let queue = RwLock::new(VecDeque::new()); let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Role::FULL); - on_demand.remote_call(Default::default(), "test", &[]); + on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); receive_response(&*on_demand, &mut network, 0, 1); assert!(network.to_disconnect.contains(&0)); + assert_eq!(on_demand.core.lock().pending_requests.len(), 1); + } + + #[test] + fn disconnects_from_peer_on_incorrect_response() { + let (_x, on_demand) = dummy(false); + let queue = RwLock::new(VecDeque::new()); + let mut network = TestIo::new(&queue, None); + on_demand.on_connect(0, Role::FULL); + + on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + receive_response(&*on_demand, &mut network, 0, 0); + assert!(network.to_disconnect.contains(&0)); + assert_eq!(on_demand.core.lock().pending_requests.len(), 1); } #[test] fn disconnects_from_peer_on_unexpected_response() { - let (_x, on_demand) = dummy(); + let (_x, on_demand) = dummy(true); let queue = RwLock::new(VecDeque::new()); let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Role::FULL); @@ -356,16 +399,15 @@ mod tests { #[test] fn receives_remote_response() { - let (_x, on_demand) = dummy(); + let (_x, on_demand) = dummy(true); let queue = RwLock::new(VecDeque::new()); let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Role::FULL); - let mut response = on_demand.remote_call(Default::default(), "test", &[]); + let mut response = on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); let thread = ::std::thread::spawn(move || { - let (result, proof) = response.wait().unwrap(); - assert_eq!(result, vec![1]); - assert_eq!(proof, vec![vec![2]]); + let result = response.wait().unwrap(); + assert_eq!(result.return_data, vec![1]); }); receive_response(&*on_demand, &mut network, 0, 0); From 6e04fb82b83717391be9b1035f402beb7071cea8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 May 2018 16:15:42 +0300 Subject: [PATCH 6/9] extracted LocalPolkadorApi to full submodule --- polkadot/api/src/full.rs | 239 ++++++++++++++++++++++++++++++++++++++ polkadot/api/src/lib.rs | 219 +--------------------------------- polkadot/api/src/light.rs | 3 +- 3 files changed, 243 insertions(+), 218 deletions(-) create mode 100644 polkadot/api/src/full.rs diff --git a/polkadot/api/src/full.rs b/polkadot/api/src/full.rs new file mode 100644 index 0000000000000..89e5efcfb35e0 --- /dev/null +++ b/polkadot/api/src/full.rs @@ -0,0 +1,239 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Strongly typed API for full Polkadot client. + +use client::backend::{Backend, LocalBackend}; +use client::{self, Client, LocalCallExecutor}; +use polkadot_executor::Executor as LocalDispatch; +use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; +use state_machine::{self, OverlayedChanges}; +use primitives::{AccountId, BlockId, Hash, Index, SessionKey, Timestamp}; +use primitives::parachain::DutyRoster; +use runtime::{self, Block, Header, UncheckedExtrinsic, Extrinsic, Call, TimestampCall}; +use {CheckedBlockId, BlockBuilder, PolkadotApi, LocalPolkadotApi, ErrorKind, Error, Result}; + +/// A checked block ID used for the substrate-client implementation of CheckedBlockId; +#[derive(Debug, Clone, Copy)] +pub struct CheckedId(pub BlockId); + +impl CheckedBlockId for CheckedId { + fn block_id(&self) -> &BlockId { + &self.0 + } +} + +// set up the necessary scaffolding to execute a set of calls to the runtime. +// this creates a new block on top of the given ID and initialises it. +macro_rules! with_runtime { + ($client: ident, $at: expr, $exec: expr) => {{ + let parent = $at.block_id(); + let header = Header { + parent_hash: $client.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?, + number: $client.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1, + state_root: Default::default(), + extrinsics_root: Default::default(), + digest: Default::default(), + }; + + $client.state_at(parent).map_err(Error::from).and_then(|state| { + let mut changes = Default::default(); + let mut ext = state_machine::Ext::new(&mut changes, &state); + + ::substrate_executor::with_native_environment(&mut ext, || { + ::runtime::Executive::initialise_block(&header); + ($exec)() + }).map_err(Into::into) + }) + }} +} + +/// A polkadot block builder. +#[derive(Debug, Clone)] +pub struct ClientBlockBuilder { + parent: BlockId, + changes: OverlayedChanges, + state: S, + header: Header, + timestamp: Timestamp, + extrinsics: Vec, +} + +impl ClientBlockBuilder + where S::Error: Into +{ + // initialises a block ready to allow extrinsics to be applied. + fn initialise_block(&mut self) -> Result<()> { + let result = { + let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); + let h = self.header.clone(); + + ::substrate_executor::with_native_environment( + &mut ext, + || runtime::Executive::initialise_block(&h), + ).map_err(Into::into) + }; + + match result { + Ok(_) => { + self.changes.commit_prospective(); + Ok(()) + } + Err(e) => { + self.changes.discard_prospective(); + Err(e) + } + } + } + + // executes a extrinsic, inherent or otherwise, without appending to the list. + fn apply_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> { + let result = { + let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); + + ::substrate_executor::with_native_environment( + &mut ext, + move || runtime::Executive::apply_extrinsic(extrinsic), + ).map_err(Into::into) + }; + + match result { + Ok(_) => { + self.changes.commit_prospective(); + Ok(()) + } + Err(e) => { + self.changes.discard_prospective(); + Err(e) + } + } + } +} + +impl BlockBuilder for ClientBlockBuilder + where S::Error: Into +{ + fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> { + // Check that this is not an "inherent" extrinsic. + if extrinsic.signature == Default::default() { + bail!(ErrorKind::PushedInherentTransaction(extrinsic)); + } else { + self.apply_extrinsic(extrinsic.clone())?; + self.extrinsics.push(extrinsic); + Ok(()) + } + } + + fn bake(mut self) -> Block { + let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); + + let final_header = ::substrate_executor::with_native_environment( + &mut ext, + move || runtime::Executive::finalise_block() + ).expect("all inherent extrinsics pushed; all other extrinsics executed correctly; qed"); + Block { + header: final_header, + extrinsics: self.extrinsics, + } + } +} + +impl PolkadotApi for Client>> + where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{ + type CheckedBlockId = CheckedId; + type BlockBuilder = ClientBlockBuilder; + + fn check_id(&self, id: BlockId) -> Result { + // bail if the code is not the same as the natively linked. + if self.code_at(&id)? != LocalDispatch::native_equivalent() { + bail!("This node is out of date. Block authoring may not work correctly. Bailing.") + } + + Ok(CheckedId(id)) + } + + fn session_keys(&self, at: &CheckedId) -> Result> { + with_runtime!(self, at, ::runtime::Consensus::authorities) + } + + fn validators(&self, at: &CheckedId) -> Result> { + with_runtime!(self, at, ::runtime::Session::validators) + } + + fn random_seed(&self, at: &CheckedId) -> Result { + with_runtime!(self, at, ::runtime::System::random_seed) + } + + fn duty_roster(&self, at: &CheckedId) -> Result { + with_runtime!(self, at, ::runtime::Parachains::calculate_duty_roster) + } + + fn timestamp(&self, at: &CheckedId) -> Result { + with_runtime!(self, at, ::runtime::Timestamp::now) + } + + fn evaluate_block(&self, at: &CheckedId, block: Block) -> Result<()> { + with_runtime!(self, at, || ::runtime::Executive::execute_block(block)) + } + + fn index(&self, at: &CheckedId, account: AccountId) -> Result { + with_runtime!(self, at, || ::runtime::System::account_index(account)) + } + + fn build_block(&self, parent: &CheckedId, timestamp: Timestamp) -> Result { + let parent = parent.block_id(); + let header = Header { + parent_hash: self.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?, + number: self.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1, + state_root: Default::default(), + extrinsics_root: Default::default(), + digest: Default::default(), + }; + + let extrinsics = vec![ + UncheckedExtrinsic { + extrinsic: Extrinsic { + signed: Default::default(), + index: Default::default(), + function: Call::Timestamp(TimestampCall::set(timestamp)), + }, + signature: Default::default(), + } + ]; + + let mut builder = ClientBlockBuilder { + parent: *parent, + changes: OverlayedChanges::default(), + state: self.state_at(parent)?, + header, + timestamp, + extrinsics: extrinsics.clone(), + }; + + builder.initialise_block()?; + + for inherent in extrinsics { + builder.apply_extrinsic(inherent)?; + } + + Ok(builder) + } +} + +impl LocalPolkadotApi for Client>> + where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> +{} diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 3119c88ddd673..68b42d65751c3 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -34,16 +34,12 @@ extern crate error_chain; #[cfg(test)] extern crate substrate_keyring as keyring; +pub mod full; pub mod light; -use client::backend::{Backend, LocalBackend}; -use client::{Client, LocalCallExecutor}; -use polkadot_executor::Executor as LocalDispatch; -use substrate_executor::{NativeExecutionDispatch, NativeExecutor}; -use state_machine::OverlayedChanges; use primitives::{AccountId, BlockId, Hash, Index, SessionKey, Timestamp}; use primitives::parachain::DutyRoster; -use runtime::{Block, Header, UncheckedExtrinsic, Extrinsic, Call, TimestampCall}; +use runtime::{Block, UncheckedExtrinsic}; error_chain! { errors { @@ -151,217 +147,6 @@ pub trait LocalPolkadotApi: PolkadotApi {} /// Mark for all Polkadot API implementations, that are fetching required state data from remote nodes. pub trait RemotePolkadotApi: PolkadotApi {} -/// A checked block ID used for the substrate-client implementation of CheckedBlockId; -#[derive(Debug, Clone, Copy)] -pub struct CheckedId(BlockId); - -impl CheckedBlockId for CheckedId { - fn block_id(&self) -> &BlockId { - &self.0 - } -} - -// set up the necessary scaffolding to execute a set of calls to the runtime. -// this creates a new block on top of the given ID and initialises it. -macro_rules! with_runtime { - ($client: ident, $at: expr, $exec: expr) => {{ - let parent = $at.block_id(); - let header = Header { - parent_hash: $client.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?, - number: $client.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - }; - - $client.state_at(parent).map_err(Error::from).and_then(|state| { - let mut changes = Default::default(); - let mut ext = state_machine::Ext::new(&mut changes, &state); - - ::substrate_executor::with_native_environment(&mut ext, || { - ::runtime::Executive::initialise_block(&header); - ($exec)() - }).map_err(Into::into) - }) - }} -} - -impl PolkadotApi for Client>> - where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> -{ - type CheckedBlockId = CheckedId; - type BlockBuilder = ClientBlockBuilder; - - fn check_id(&self, id: BlockId) -> Result { - // bail if the code is not the same as the natively linked. - if self.code_at(&id)? != LocalDispatch::native_equivalent() { - bail!("This node is out of date. Block authoring may not work correctly. Bailing.") - } - - Ok(CheckedId(id)) - } - - fn session_keys(&self, at: &CheckedId) -> Result> { - with_runtime!(self, at, ::runtime::Consensus::authorities) - } - - fn validators(&self, at: &CheckedId) -> Result> { - with_runtime!(self, at, ::runtime::Session::validators) - } - - fn random_seed(&self, at: &CheckedId) -> Result { - with_runtime!(self, at, ::runtime::System::random_seed) - } - - fn duty_roster(&self, at: &CheckedId) -> Result { - with_runtime!(self, at, ::runtime::Parachains::calculate_duty_roster) - } - - fn timestamp(&self, at: &CheckedId) -> Result { - with_runtime!(self, at, ::runtime::Timestamp::now) - } - - fn evaluate_block(&self, at: &CheckedId, block: Block) -> Result<()> { - with_runtime!(self, at, || ::runtime::Executive::execute_block(block)) - } - - fn index(&self, at: &CheckedId, account: AccountId) -> Result { - with_runtime!(self, at, || ::runtime::System::account_index(account)) - } - - fn build_block(&self, parent: &CheckedId, timestamp: Timestamp) -> Result { - let parent = parent.block_id(); - let header = Header { - parent_hash: self.block_hash_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))?, - number: self.block_number_from_id(parent)?.ok_or(ErrorKind::UnknownBlock(*parent))? + 1, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - }; - - let extrinsics = vec![ - UncheckedExtrinsic { - extrinsic: Extrinsic { - signed: Default::default(), - index: Default::default(), - function: Call::Timestamp(TimestampCall::set(timestamp)), - }, - signature: Default::default(), - } - ]; - - let mut builder = ClientBlockBuilder { - parent: *parent, - changes: OverlayedChanges::default(), - state: self.state_at(parent)?, - header, - timestamp, - extrinsics: extrinsics.clone(), - }; - - builder.initialise_block()?; - - for inherent in extrinsics { - builder.apply_extrinsic(inherent)?; - } - - Ok(builder) - } -} - -impl LocalPolkadotApi for Client>> - where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> -{} - -/// A polkadot block builder. -#[derive(Debug, Clone)] -pub struct ClientBlockBuilder { - parent: BlockId, - changes: OverlayedChanges, - state: S, - header: Header, - timestamp: Timestamp, - extrinsics: Vec, -} - -impl ClientBlockBuilder - where S::Error: Into -{ - // initialises a block ready to allow extrinsics to be applied. - fn initialise_block(&mut self) -> Result<()> { - let result = { - let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); - let h = self.header.clone(); - - ::substrate_executor::with_native_environment( - &mut ext, - || runtime::Executive::initialise_block(&h), - ).map_err(Into::into) - }; - - match result { - Ok(_) => { - self.changes.commit_prospective(); - Ok(()) - } - Err(e) => { - self.changes.discard_prospective(); - Err(e) - } - } - } - - // executes a extrinsic, inherent or otherwise, without appending to the list. - fn apply_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> { - let result = { - let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); - - ::substrate_executor::with_native_environment( - &mut ext, - move || runtime::Executive::apply_extrinsic(extrinsic), - ).map_err(Into::into) - }; - - match result { - Ok(_) => { - self.changes.commit_prospective(); - Ok(()) - } - Err(e) => { - self.changes.discard_prospective(); - Err(e) - } - } - } -} - -impl BlockBuilder for ClientBlockBuilder - where S::Error: Into -{ - fn push_extrinsic(&mut self, extrinsic: UncheckedExtrinsic) -> Result<()> { - // Check that this is not an "inherent" extrinsic. - if extrinsic.signature == Default::default() { - bail!(ErrorKind::PushedInherentTransaction(extrinsic)); - } else { - self.apply_extrinsic(extrinsic.clone())?; - self.extrinsics.push(extrinsic); - Ok(()) - } - } - - fn bake(mut self) -> Block { - let mut ext = state_machine::Ext::new(&mut self.changes, &self.state); - - let final_header = ::substrate_executor::with_native_environment( - &mut ext, - move || runtime::Executive::finalise_block() - ).expect("all inherent extrinsics pushed; all other extrinsics executed correctly; qed"); - Block { - header: final_header, - extrinsics: self.extrinsics, - } - } -} #[cfg(test)] mod tests { diff --git a/polkadot/api/src/light.rs b/polkadot/api/src/light.rs index f543b586059e1..4a44bd69f25f2 100644 --- a/polkadot/api/src/light.rs +++ b/polkadot/api/src/light.rs @@ -24,7 +24,8 @@ use state_machine; use primitives::{AccountId, BlockId, Hash, Index, SessionKey, Timestamp}; use primitives::parachain::DutyRoster; use runtime::{Block, UncheckedExtrinsic}; -use {PolkadotApi, RemotePolkadotApi, BlockBuilder, CheckedBlockId, CheckedId, Result, ErrorKind}; +use full::CheckedId; +use {PolkadotApi, RemotePolkadotApi, BlockBuilder, CheckedBlockId, Result, ErrorKind}; /// Remote polkadot API implementation. pub struct RemotePolkadotApiWrapper(pub Arc>); From 5901a2ae838f6e4d7fa8323063d0a03fcce86d0b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 May 2018 17:29:57 +0300 Subject: [PATCH 7/9] Future as Fetcher result --- polkadot/service/src/lib.rs | 2 +- substrate/client/src/call_executor.rs | 14 ++++++++------ substrate/client/src/error.rs | 6 ++++++ substrate/client/src/light.rs | 17 +++++++++++------ substrate/network/src/on_demand.rs | 20 +++++++++++++------- 5 files changed, 39 insertions(+), 20 deletions(-) diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 371d14d2ce476..ae6d75954f135 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -280,7 +280,7 @@ impl client::GenesisBuilder for GenesisBuilder { } /// Creates light client and register protocol with the network service -pub fn new_light(config: Configuration) -> Result>, error::Error> { +pub fn new_light(config: Configuration) -> Result>>, error::Error> { Service::new(move |_, executor, genesis_builder: GenesisBuilder| { let client_backend = client::light::new_light_backend(); let fetch_checker = Arc::new(client::light::new_fetch_checker(client_backend.clone(), executor)); diff --git a/substrate/client/src/call_executor.rs b/substrate/client/src/call_executor.rs index 0af9d2ceeef37..b1b4e2a15653c 100644 --- a/substrate/client/src/call_executor.rs +++ b/substrate/client/src/call_executor.rs @@ -15,6 +15,7 @@ // along with Polkadot. If not, see . use std::sync::Arc; +use futures::{IntoFuture, Future}; use primitives::block::Id as BlockId; use state_machine::{self, OverlayedChanges, Backend as StateBackend, CodeExecutor}; use state_machine::backend::InMemory as InMemoryStateBackend; @@ -59,9 +60,9 @@ pub struct LocalCallExecutor { /// Call executor that executes methods on remote node, querying execution proof /// and checking proof by re-executing locally. -pub struct RemoteCallExecutor { +pub struct RemoteCallExecutor { backend: Arc, - fetcher: Arc, + fetcher: Arc, } impl LocalCallExecutor { @@ -105,16 +106,17 @@ impl CallExecutor for LocalCallExecutor } } -impl RemoteCallExecutor { +impl RemoteCallExecutor { /// Creates new instance of remote call executor. - pub fn new(backend: Arc, fetcher: Arc) -> Self { + pub fn new(backend: Arc, fetcher: Arc) -> Self { RemoteCallExecutor { backend, fetcher } } } -impl CallExecutor for RemoteCallExecutor +impl CallExecutor for RemoteCallExecutor where B: backend::RemoteBackend, + F: Fetcher, error::Error: From<<::State as StateBackend>::Error>, { type Error = error::Error; @@ -130,7 +132,7 @@ impl CallExecutor for RemoteCallExecutor block: block_hash, method: method.into(), call_data: call_data.to_vec(), - }) + }).into_future().wait() } fn call_at_state(&self, _state: &S, _changes: &mut OverlayedChanges, _method: &str, _call_data: &[u8]) -> error::Result<(Vec, S::Transaction)> { diff --git a/substrate/client/src/error.rs b/substrate/client/src/error.rs index 7e1a9dd99c00b..9f0e0ac1f8f8e 100644 --- a/substrate/client/src/error.rs +++ b/substrate/client/src/error.rs @@ -93,6 +93,12 @@ error_chain! { description("invalid execution proof"), display("Remote node has responded with invalid execution proof"), } + + /// Invalid remote proof. + RemoteFetchCancelled { + description("remote fetch cancelled"), + display("Remote data fetch has been cancelled"), + } } } diff --git a/substrate/client/src/light.rs b/substrate/client/src/light.rs index c9a2992fec598..0e9b735be8460 100644 --- a/substrate/client/src/light.rs +++ b/substrate/client/src/light.rs @@ -18,6 +18,7 @@ //! Everything else is requested from full nodes on demand. use std::sync::Arc; +use futures::future::IntoFuture; use primitives; use primitives::block::{self, Id as BlockId, HeaderHash}; use runtime_support::Hashable; @@ -43,8 +44,11 @@ pub struct RemoteCallRequest { /// Light client data fetcher. Implementations of this trait must check if remote data /// is correct (see FetchedDataChecker) and return already checked data. pub trait Fetcher: Send + Sync { + /// Remote call result future. + type RemoteCallResult: IntoFuture; + /// Fetch remote call result. - fn remote_call(&self, request: RemoteCallRequest) -> error::Result; + fn remote_call(&self, request: RemoteCallRequest) -> Self::RemoteCallResult; } /// Light client remote data checker. @@ -214,13 +218,14 @@ pub fn new_light_backend() -> Arc { } /// Create an instance of light client. -pub fn new_light( +pub fn new_light( backend: Arc, - fetcher: Arc, - genesis_builder: F -) -> error::Result>> + fetcher: Arc, + genesis_builder: B, +) -> error::Result>> where - F: GenesisBuilder, + F: Fetcher, + B: GenesisBuilder, { let executor = RemoteCallExecutor::new(backend.clone(), fetcher); Client::new(backend, executor, genesis_builder) diff --git a/substrate/network/src/on_demand.rs b/substrate/network/src/on_demand.rs index 9ba360748ae75..cc569f480f7bf 100644 --- a/substrate/network/src/on_demand.rs +++ b/substrate/network/src/on_demand.rs @@ -18,8 +18,9 @@ use std::collections::VecDeque; use std::sync::{Arc, Weak}; -use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::{Instant, Duration}; +use futures::{Future, Poll}; +use futures::sync::oneshot::{channel, Receiver, Sender}; use linked_hash_map::LinkedHashMap; use linked_hash_map::Entry; use parking_lot::Mutex; @@ -75,10 +76,13 @@ struct Request { request: RemoteCallRequest, } -impl Response { - fn wait(&mut self) -> client::error::Result { - self.receiver.recv() - .map_err(|_| client::error::ErrorKind::Backend("OnDemand serivice has gone".into()).into()) +impl Future for Response { + type Item = client::CallResult; + type Error = client::error::Error; + + fn poll(&mut self) -> Poll { + self.receiver.poll() + .map_err(|_| client::error::ErrorKind::RemoteFetchCancelled.into()) } } @@ -172,8 +176,10 @@ impl OnDemandService for OnDemand where E: service::ExecuteInContext { } impl Fetcher for OnDemand where E: service::ExecuteInContext { - fn remote_call(&self, request: RemoteCallRequest) -> client::error::Result { - self.remote_call(request).wait() + type RemoteCallResult = Response; + + fn remote_call(&self, request: RemoteCallRequest) -> Self::RemoteCallResult { + self.remote_call(request) } } From 7661c9f275615dffc5146bd854c0f79ccaa52346 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 May 2018 10:35:10 +0300 Subject: [PATCH 8/9] fixed tests compilation --- polkadot/api/src/full.rs | 82 +++++++++++++++++++++++++++++ polkadot/api/src/lib.rs | 83 ------------------------------ substrate/network/src/on_demand.rs | 3 +- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/polkadot/api/src/full.rs b/polkadot/api/src/full.rs index 89e5efcfb35e0..14ea021922291 100644 --- a/polkadot/api/src/full.rs +++ b/polkadot/api/src/full.rs @@ -237,3 +237,85 @@ impl PolkadotApi for Client LocalPolkadotApi for Client>> where ::client::error::Error: From<<::State as state_machine::backend::Backend>::Error> {} + +#[cfg(test)] +mod tests { + use super::*; + use keyring::Keyring; + use codec::Slicable; + use client::{self, LocalCallExecutor}; + use client::in_mem::Backend as InMemory; + use substrate_executor::NativeExecutionDispatch; + use substrate_primitives::{self, Header}; + use runtime::{GenesisConfig, ConsensusConfig, SessionConfig, BuildExternalities}; + + fn validators() -> Vec { + vec![ + Keyring::One.to_raw_public(), + Keyring::Two.to_raw_public(), + ] + } + + fn client() -> Client>> { + struct GenesisBuilder; + + impl client::GenesisBuilder for GenesisBuilder { + fn build(self) -> (Header, Vec<(Vec, Vec)>) { + let genesis_config = GenesisConfig { + consensus: Some(ConsensusConfig { + code: LocalDispatch::native_equivalent().to_vec(), + authorities: validators(), + }), + system: None, + session: Some(SessionConfig { + validators: validators(), + session_length: 100, + }), + council: Some(Default::default()), + democracy: Some(Default::default()), + parachains: Some(Default::default()), + staking: Some(Default::default()), + }; + + let storage = genesis_config.build_externalities(); + let block = ::client::genesis::construct_genesis_block(&storage); + (substrate_primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) + } + } + + ::client::new_in_mem(LocalDispatch::new(), GenesisBuilder).unwrap() + } + + #[test] + fn gets_session_and_validator_keys() { + let client = client(); + let id = client.check_id(BlockId::Number(0)).unwrap(); + assert_eq!(client.session_keys(&id).unwrap(), validators()); + assert_eq!(client.validators(&id).unwrap(), validators()); + } + + #[test] + fn build_block() { + let client = client(); + + let id = client.check_id(BlockId::Number(0)).unwrap(); + let block_builder = client.build_block(&id, 1_000_000).unwrap(); + let block = block_builder.bake(); + + assert_eq!(block.header.number, 1); + assert!(block.header.extrinsics_root != Default::default()); + } + + #[test] + fn fails_to_check_id_for_unknown_block() { + assert!(client().check_id(BlockId::Number(100)).is_err()); + } + + #[test] + fn gets_random_seed_with_genesis() { + let client = client(); + + let id = client.check_id(BlockId::Number(0)).unwrap(); + assert!(client.random_seed(&id).is_ok()); + } +} diff --git a/polkadot/api/src/lib.rs b/polkadot/api/src/lib.rs index 68b42d65751c3..b75d1fbbd38d9 100644 --- a/polkadot/api/src/lib.rs +++ b/polkadot/api/src/lib.rs @@ -146,86 +146,3 @@ pub trait LocalPolkadotApi: PolkadotApi {} /// Mark for all Polkadot API implementations, that are fetching required state data from remote nodes. pub trait RemotePolkadotApi: PolkadotApi {} - - -#[cfg(test)] -mod tests { - use super::*; - use keyring::Keyring; - use codec::Slicable; - use client::{self, LocalCallExecutor}; - use client::in_mem::Backend as InMemory; - use substrate_executor::NativeExecutionDispatch; - use substrate_primitives::Header; - use runtime::{GenesisConfig, ConsensusConfig, SessionConfig, BuildExternalities}; - - fn validators() -> Vec { - vec![ - Keyring::One.to_raw_public(), - Keyring::Two.to_raw_public(), - ] - } - - fn client() -> Client>> { - struct GenesisBuilder; - - impl client::GenesisBuilder for GenesisBuilder { - fn build(self) -> (Header, Vec<(Vec, Vec)>) { - let genesis_config = GenesisConfig { - consensus: Some(ConsensusConfig { - code: LocalDispatch::native_equivalent().to_vec(), - authorities: validators(), - }), - system: None, - session: Some(SessionConfig { - validators: validators(), - session_length: 100, - }), - council: Some(Default::default()), - democracy: Some(Default::default()), - parachains: Some(Default::default()), - staking: Some(Default::default()), - }; - - let storage = genesis_config.build_externalities(); - let block = ::client::genesis::construct_genesis_block(&storage); - (substrate_primitives::block::Header::decode(&mut block.header.encode().as_ref()).expect("to_vec() always gives a valid serialisation; qed"), storage.into_iter().collect()) - } - } - - ::client::new_in_mem(LocalDispatch::new(), GenesisBuilder).unwrap() - } - - #[test] - fn gets_session_and_validator_keys() { - let client = client(); - let id = client.check_id(BlockId::Number(0)).unwrap(); - assert_eq!(client.session_keys(&id).unwrap(), validators()); - assert_eq!(client.validators(&id).unwrap(), validators()); - } - - #[test] - fn build_block() { - let client = client(); - - let id = client.check_id(BlockId::Number(0)).unwrap(); - let block_builder = client.build_block(&id, 1_000_000).unwrap(); - let block = block_builder.bake(); - - assert_eq!(block.header.number, 1); - assert!(block.header.extrinsics_root != Default::default()); - } - - #[test] - fn fails_to_check_id_for_unknown_block() { - assert!(client().check_id(BlockId::Number(100)).is_err()); - } - - #[test] - fn gets_random_seed_with_genesis() { - let client = client(); - - let id = client.check_id(BlockId::Number(0)).unwrap(); - assert!(client.random_seed(&id).is_ok()); - } -} diff --git a/substrate/network/src/on_demand.rs b/substrate/network/src/on_demand.rs index cc569f480f7bf..76180f616d0bc 100644 --- a/substrate/network/src/on_demand.rs +++ b/substrate/network/src/on_demand.rs @@ -275,6 +275,7 @@ mod tests { use std::collections::VecDeque; use std::sync::Arc; use std::time::Instant; + use futures::Future; use parking_lot::RwLock; use client; use client::light::{FetchChecker, RemoteCallRequest}; @@ -410,7 +411,7 @@ mod tests { let mut network = TestIo::new(&queue, None); on_demand.on_connect(0, Role::FULL); - let mut response = on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); + let response = on_demand.remote_call(RemoteCallRequest { block: Default::default(), method: "test".into(), call_data: vec![] }); let thread = ::std::thread::spawn(move || { let result = response.wait().unwrap(); assert_eq!(result.return_data, vec![1]); From cfe2caefc0dbfbf093583c2c55dddd14f74a3da1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 24 May 2018 11:37:49 +0300 Subject: [PATCH 9/9] fixed tests after merge --- substrate/rpc/src/chain/tests.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/rpc/src/chain/tests.rs b/substrate/rpc/src/chain/tests.rs index 237df1f976936..c0ef5c6a5dcae 100644 --- a/substrate/rpc/src/chain/tests.rs +++ b/substrate/rpc/src/chain/tests.rs @@ -33,7 +33,7 @@ fn should_return_header() { Ok(Some(ref x)) if x == &block::Header { parent_hash: 0.into(), number: 0, - state_root: "ed0758a19e0f35e95de93046def6a8a6da7c816b7c3d15342743dd5e464a87fc".into(), + state_root: "19b81ebb006ad7af92577b26c9604e80406e85b86c4486859836aebc89c22f33".into(), extrinsics_root: "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".into(), digest: Default::default(), } @@ -68,9 +68,8 @@ fn should_notify_about_latest_block() { // assert notification send to transport let (notification, next) = core.run(transport.into_future()).unwrap(); - assert_eq!(notification, Some( - r#"{"jsonrpc":"2.0","method":"test","params":{"result":{"digest":{"logs":[]},"extrinsicsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":1,"parentHash":"0xfc47a48119e6e11992c9aba9d0084398f8789bb79a9390b4b60d6760575bee20","stateRoot":"0x59b17ea618f09dd634e6e0bde2becc8a10348fb35df2163f136b829b28f3751f"},"subscription":0}}"#.to_owned() + r#"{"jsonrpc":"2.0","method":"test","params":{"result":{"digest":{"logs":[]},"extrinsicsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":1,"parentHash":"0x26229bcc75fe587469f5e4c7a0f0966fd2878e6b1c3dbfdcd4bb139b6da655e2","stateRoot":"0xb0eb728eb2944a84c2d9d4509bb5b324709ec73cc7140b522d2d297a2ca9fd2b"},"subscription":0}}"#.to_owned() )); // no more notifications on this channel assert_eq!(core.run(next.into_future()).unwrap().0, None);