Reorganising the repository - external renames and moves (#4074)

* Adding first rough ouline of the repository structure

* Remove old CI stuff

* add title

* formatting fixes

* move node-exits job's script to scripts dir

* Move docs into subdir

* move to bin

* move maintainence scripts, configs and helpers into its own dir

* add .local to ignore

* move core->client

* start up 'test' area

* move test client

* move test runtime

* make test move compile

* Add dependencies rule enforcement.

* Fix indexing.

* Update docs to reflect latest changes

* Moving /srml->/paint

* update docs

* move client/sr-* -> primitives/

* clean old readme

* remove old broken code in rhd

* update lock

* Step 1.

* starting to untangle client

* Fix after merge.

* start splitting out client interfaces

* move children and blockchain interfaces

* Move trie and state-machine to primitives.

* Fix WASM builds.

* fixing broken imports

* more interface moves

* move backend and light to interfaces

* move CallExecutor

* move cli off client

* moving around more interfaces

* re-add consensus crates into the mix

* fix subkey path

* relieve client from executor

* starting to pull out client from grandpa

* move is_decendent_of out of client

* grandpa still depends on client directly

* lemme tests pass

* rename srml->paint

* Make it compile.

* rename interfaces->client-api

* Move keyring to primitives.

* fixup libp2p dep

* fix broken use

* allow dependency enforcement to fail

* move fork-tree

* Moving wasm-builder

* make env

* move build-script-utils

* fixup broken crate depdencies and names

* fix imports for authority discovery

* fix typo

* update cargo.lock

* fixing imports

* Fix paths and add missing crates

* re-add missing crates
This commit is contained in:
Benjamin Kampmann
2019-11-14 21:51:17 +01:00
committed by Bastian Köcher
parent becc3b0a4f
commit 60e5011c72
809 changed files with 7801 additions and 6464 deletions
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use crate::ChainSpec;
use futures::{prelude::*, sync::oneshot, sync::mpsc};
use libp2p::wasm_ext;
use log::{debug, info};
use std::sync::Arc;
use substrate_service::{AbstractService, RpcSession, Roles as ServiceRoles, Configuration, config::DatabaseConfig};
use wasm_bindgen::prelude::*;
/// Starts the client.
///
/// You must pass a libp2p transport that supports .
#[wasm_bindgen]
pub fn start_client(wasm_ext: wasm_ext::ffi::Transport) -> Result<Client, JsValue> {
start_inner(wasm_ext)
.map_err(|err| JsValue::from_str(&err.to_string()))
}
fn start_inner(wasm_ext: wasm_ext::ffi::Transport) -> Result<Client, Box<dyn std::error::Error>> {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Info);
// Build the configuration to pass to the service.
let config = {
let wasm_ext = wasm_ext::ExtTransport::new(wasm_ext);
let chain_spec = ChainSpec::FlamingFir.load().map_err(|e| format!("{:?}", e))?;
let mut config = Configuration::<(), _, _>::default_with_spec_and_base_path(chain_spec, None);
config.network.transport = network::config::TransportConfig::Normal {
wasm_external_transport: Some(wasm_ext.clone()),
allow_private_ipv4: true,
enable_mdns: false,
};
config.telemetry_external_transport = Some(wasm_ext);
config.roles = ServiceRoles::LIGHT;
config.name = "Browser node".to_string();
config.database = {
let db = Arc::new(kvdb_memorydb::create(10));
DatabaseConfig::Custom(db)
};
config
};
info!("Substrate browser node");
info!(" version {}", config.full_version());
info!(" by Parity Technologies, 2017-2019");
info!("Chain specification: {}", config.chain_spec.name());
info!("Node name: {}", config.name);
info!("Roles: {:?}", config.roles);
// Create the service. This is the most heavy initialization step.
let mut service = crate::service::new_light(config).map_err(|e| format!("{:?}", e))?;
// We now dispatch a background task responsible for processing the service.
//
// The main action performed by the code below consists in polling the service with
// `service.poll()`.
// The rest consists in handling RPC requests.
let (rpc_send_tx, mut rpc_send_rx) = mpsc::unbounded::<RpcMessage>();
wasm_bindgen_futures::spawn_local(futures::future::poll_fn(move || {
loop {
match rpc_send_rx.poll() {
Ok(Async::Ready(Some(message))) => {
let fut = service.rpc_query(&message.session, &message.rpc_json);
let _ = message.send_back.send(Box::new(fut));
},
Ok(Async::NotReady) => break,
Err(_) | Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
}
}
loop {
match service.poll().map_err(|_| ())? {
Async::Ready(()) => return Ok(Async::Ready(())),
Async::NotReady => break
}
}
Ok(Async::NotReady)
}));
Ok(Client {
rpc_send_tx,
})
}
/// A running client.
#[wasm_bindgen]
pub struct Client {
rpc_send_tx: mpsc::UnboundedSender<RpcMessage>,
}
struct RpcMessage {
rpc_json: String,
session: RpcSession,
send_back: oneshot::Sender<Box<dyn Future<Item = Option<String>, Error = ()>>>,
}
#[wasm_bindgen]
impl Client {
/// Allows starting an RPC request. Returns a `Promise` containing the result of that request.
#[wasm_bindgen(js_name = "rpcSend")]
pub fn rpc_send(&mut self, rpc: &str) -> js_sys::Promise {
let rpc_session = RpcSession::new(mpsc::channel(1).0);
let (tx, rx) = oneshot::channel();
let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
rpc_json: rpc.to_owned(),
session: rpc_session,
send_back: tx,
});
let fut = rx
.map_err(|_| ())
.and_then(|fut| fut)
.map(|s| JsValue::from_str(&s.unwrap_or(String::new())))
.map_err(|_| JsValue::NULL);
wasm_bindgen_futures::future_to_promise(fut)
}
/// Subscribes to an RPC pubsub endpoint.
#[wasm_bindgen(js_name = "rpcSubscribe")]
pub fn rpc_subscribe(&mut self, rpc: &str, callback: js_sys::Function) {
let (tx, rx) = mpsc::channel(4);
let rpc_session = RpcSession::new(tx);
let (fut_tx, fut_rx) = oneshot::channel();
let _ = self.rpc_send_tx.unbounded_send(RpcMessage {
rpc_json: rpc.to_owned(),
session: rpc_session.clone(),
send_back: fut_tx,
});
let fut_rx = fut_rx
.map_err(|_| ())
.and_then(|fut| fut);
wasm_bindgen_futures::spawn_local(fut_rx.then(|_| Ok(())));
wasm_bindgen_futures::spawn_local(rx.for_each(move |s| {
match callback.call1(&callback, &JsValue::from_str(&s)) {
Ok(_) => Ok(()),
Err(_) => Err(()),
}
}).then(move |v| {
// We need to keep `rpc_session` alive.
debug!("RPC subscription has ended");
drop(rpc_session);
v
}));
}
}
+406
View File
@@ -0,0 +1,406 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Substrate chain configurations.
use chain_spec::ChainSpecExtension;
use primitives::{Pair, Public, crypto::UncheckedInto, sr25519};
use serde::{Serialize, Deserialize};
use node_runtime::{
AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig,
GrandpaConfig, ImOnlineConfig, IndicesConfig, SessionConfig, SessionKeys, StakerStatus, StakingConfig, SudoConfig,
SystemConfig, TechnicalCommitteeConfig, WASM_BINARY,
};
use node_runtime::Block;
use node_runtime::constants::currency::*;
use substrate_service;
use hex_literal::hex;
use substrate_telemetry::TelemetryEndpoints;
use grandpa_primitives::{AuthorityId as GrandpaId};
use babe_primitives::{AuthorityId as BabeId};
use im_online::sr25519::{AuthorityId as ImOnlineId};
use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId;
use sr_primitives::{Perbill, traits::{Verify, IdentifyAccount}};
pub use node_primitives::{AccountId, Balance, Signature};
pub use node_runtime::GenesisConfig;
type AccountPublic = <Signature as Verify>::Signer;
const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";
/// Node `ChainSpec` extensions.
///
/// Additional parameters for some Substrate core modules,
/// customizable from the chain spec.
#[derive(Default, Clone, Serialize, Deserialize, ChainSpecExtension)]
pub struct Extensions {
/// Block numbers with known hashes.
pub fork_blocks: client::ForkBlocks<Block>,
}
/// Specialized `ChainSpec`.
pub type ChainSpec = substrate_service::ChainSpec<
GenesisConfig,
Extensions,
>;
/// Flaming Fir testnet generator
pub fn flaming_fir_config() -> Result<ChainSpec, String> {
ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..])
}
fn session_keys(
grandpa: GrandpaId,
babe: BabeId,
im_online: ImOnlineId,
authority_discovery: AuthorityDiscoveryId,
) -> SessionKeys {
SessionKeys { grandpa, babe, im_online, authority_discovery }
}
fn staging_testnet_config_genesis() -> GenesisConfig {
// stash, controller, session-key
// generated with secret:
// for i in 1 2 3 4 ; do for j in stash controller; do subkey inspect "$secret"/fir/$j/$i; done; done
// and
// for i in 1 2 3 4 ; do for j in session; do subkey --ed25519 inspect "$secret"//fir//$j//$i; done; done
let initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)> = vec![(
// 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy
hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].into(),
// 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq
hex!["781ead1e2fa9ccb74b44c19d29cb2a7a4b5be3972927ae98cd3877523976a276"].into(),
// 5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC
hex!["9becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe9699332"].unchecked_into(),
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
// 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8
hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(),
),(
// 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2
hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].into(),
// 5Gc4vr42hH1uDZc93Nayk5G7i687bAQdHHc9unLuyeawHipF
hex!["c8dc79e36b29395413399edaec3e20fcca7205fb19776ed8ddb25d6f427ec40e"].into(),
// 5EockCXN6YkiNCDjpqqnbcqd4ad35nU4RmA1ikM4YeRN4WcE
hex!["7932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f"].unchecked_into(),
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
// 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ
hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(),
),(
// 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp
hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].into(),
// 5FeD54vGVNpFX3PndHPXJ2MDakc462vBCD5mgtWRnWYCpZU9
hex!["9e42241d7cd91d001773b0b616d523dd80e13c6c2cab860b1234ef1b9ffc1526"].into(),
// 5E1jLYfLdUQKrFrtqoKgFrRvxM3oQPMbf6DfcsrugZZ5Bn8d
hex!["5633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440"].unchecked_into(),
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
// 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH
hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(),
),(
// 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9
hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].into(),
// 5EPQdAQ39WQNLCRjWsCk5jErsCitHiY5ZmjfWzzbXDoAoYbn
hex!["66bc1e5d275da50b72b15de072a2468a5ad414919ca9054d2695767cf650012f"].into(),
// 5DMa31Hd5u1dwoRKgC4uvqyrdK45RHv3CpwvpUC1EzuwDit4
hex!["3919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef"].unchecked_into(),
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
// 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x
hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(),
)];
// generated with secret: subkey inspect "$secret"/fir
let root_key: AccountId = hex![
// 5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo
"9ee5e5bdc0ec239eb164f865ecc345ce4c88e76ee002e0f7e318097347471809"
].into();
let endowed_accounts: Vec<AccountId> = vec![root_key.clone()];
testnet_genesis(
initial_authorities,
root_key,
Some(endowed_accounts),
false,
)
}
/// Staging testnet config.
pub fn staging_testnet_config() -> ChainSpec {
let boot_nodes = vec![];
ChainSpec::from_genesis(
"Staging Testnet",
"staging_testnet",
staging_testnet_config_genesis,
boot_nodes,
Some(TelemetryEndpoints::new(vec![(STAGING_TELEMETRY_URL.to_string(), 0)])),
None,
None,
Default::default(),
)
}
/// Helper function to generate a crypto pair from seed
pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
TPublic::Pair::from_string(&format!("//{}", seed), None)
.expect("static values are valid; qed")
.public()
}
/// Helper function to generate an account ID from seed
pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where
AccountPublic: From<<TPublic::Pair as Pair>::Public>
{
AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
}
/// Helper function to generate stash, controller and session key from seed
pub fn get_authority_keys_from_seed(seed: &str) -> (
AccountId,
AccountId,
GrandpaId,
BabeId,
ImOnlineId,
AuthorityDiscoveryId,
) {
(
get_account_id_from_seed::<sr25519::Public>(&format!("{}//stash", seed)),
get_account_id_from_seed::<sr25519::Public>(seed),
get_from_seed::<GrandpaId>(seed),
get_from_seed::<BabeId>(seed),
get_from_seed::<ImOnlineId>(seed),
get_from_seed::<AuthorityDiscoveryId>(seed),
)
}
/// Helper function to create GenesisConfig for testing
pub fn testnet_genesis(
initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>,
root_key: AccountId,
endowed_accounts: Option<Vec<AccountId>>,
enable_println: bool,
) -> GenesisConfig {
let endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(|| {
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
get_account_id_from_seed::<sr25519::Public>("Charlie"),
get_account_id_from_seed::<sr25519::Public>("Dave"),
get_account_id_from_seed::<sr25519::Public>("Eve"),
get_account_id_from_seed::<sr25519::Public>("Ferdie"),
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
get_account_id_from_seed::<sr25519::Public>("Charlie//stash"),
get_account_id_from_seed::<sr25519::Public>("Dave//stash"),
get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
]
});
const ENDOWMENT: Balance = 10_000_000 * DOLLARS;
const STASH: Balance = 100 * DOLLARS;
GenesisConfig {
system: Some(SystemConfig {
code: WASM_BINARY.to_vec(),
changes_trie_config: Default::default(),
}),
balances: Some(BalancesConfig {
balances: endowed_accounts.iter().cloned()
.map(|k| (k, ENDOWMENT))
.chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
.collect(),
vesting: vec![],
}),
indices: Some(IndicesConfig {
ids: endowed_accounts.iter().cloned()
.chain(initial_authorities.iter().map(|x| x.0.clone()))
.collect::<Vec<_>>(),
}),
session: Some(SessionConfig {
keys: initial_authorities.iter().map(|x| {
(x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()))
}).collect::<Vec<_>>(),
}),
staking: Some(StakingConfig {
current_era: 0,
validator_count: initial_authorities.len() as u32 * 2,
minimum_validator_count: initial_authorities.len() as u32,
stakers: initial_authorities.iter().map(|x| {
(x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)
}).collect(),
invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),
slash_reward_fraction: Perbill::from_percent(10),
.. Default::default()
}),
democracy: Some(DemocracyConfig::default()),
collective_Instance1: Some(CouncilConfig {
members: vec![],
phantom: Default::default(),
}),
collective_Instance2: Some(TechnicalCommitteeConfig {
members: vec![],
phantom: Default::default(),
}),
contracts: Some(ContractsConfig {
current_schedule: contracts::Schedule {
enable_println, // this should only be enabled on development chains
..Default::default()
},
gas_price: 1 * MILLICENTS,
}),
sudo: Some(SudoConfig {
key: root_key,
}),
babe: Some(BabeConfig {
authorities: vec![],
}),
im_online: Some(ImOnlineConfig {
keys: vec![],
}),
authority_discovery: Some(AuthorityDiscoveryConfig {
keys: vec![],
}),
grandpa: Some(GrandpaConfig {
authorities: vec![],
}),
membership_Instance1: Some(Default::default()),
treasury: Some(Default::default()),
}
}
fn development_config_genesis() -> GenesisConfig {
testnet_genesis(
vec![
get_authority_keys_from_seed("Alice"),
],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
true,
)
}
/// Development config (single validator Alice)
pub fn development_config() -> ChainSpec {
ChainSpec::from_genesis(
"Development",
"dev",
development_config_genesis,
vec![],
None,
None,
None,
Default::default(),
)
}
fn local_testnet_genesis() -> GenesisConfig {
testnet_genesis(
vec![
get_authority_keys_from_seed("Alice"),
get_authority_keys_from_seed("Bob"),
],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
false,
)
}
/// Local testnet config (multivalidator Alice + Bob)
pub fn local_testnet_config() -> ChainSpec {
ChainSpec::from_genesis(
"Local Testnet",
"local_testnet",
local_testnet_genesis,
vec![],
None,
None,
None,
Default::default(),
)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::service::new_full;
use substrate_service::Roles;
use service_test;
fn local_testnet_genesis_instant_single() -> GenesisConfig {
testnet_genesis(
vec![
get_authority_keys_from_seed("Alice"),
],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
false,
)
}
/// Local testnet config (single validator - Alice)
pub fn integration_test_config_with_single_authority() -> ChainSpec {
ChainSpec::from_genesis(
"Integration Test",
"test",
local_testnet_genesis_instant_single,
vec![],
None,
None,
None,
Default::default(),
)
}
/// Local testnet config (multivalidator Alice + Bob)
pub fn integration_test_config_with_two_authorities() -> ChainSpec {
ChainSpec::from_genesis(
"Integration Test",
"test",
local_testnet_genesis,
vec![],
None,
None,
None,
Default::default(),
)
}
#[test]
#[ignore]
fn test_connectivity() {
service_test::connectivity(
integration_test_config_with_two_authorities(),
|config| new_full(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
true,
);
}
}
+210
View File
@@ -0,0 +1,210 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
pub use substrate_cli::error;
use tokio::prelude::Future;
use tokio::runtime::{Builder as RuntimeBuilder, Runtime};
pub use substrate_cli::{VersionInfo, IntoExit, NoCustom, SharedParams, ExecutionStrategyParam};
use substrate_service::{AbstractService, Roles as ServiceRoles, Configuration};
use log::info;
use structopt::{StructOpt, clap::App};
use substrate_cli::{display_role, parse_and_prepare, AugmentClap, GetLogFilter, ParseAndPrepare};
use crate::{service, ChainSpec, load_spec};
use crate::factory_impl::FactoryState;
use transaction_factory::RuntimeAdapter;
use client::ExecutionStrategies;
/// Custom subcommands.
#[derive(Clone, Debug, StructOpt)]
pub enum CustomSubcommands {
/// The custom factory subcommmand for manufacturing transactions.
#[structopt(
name = "factory",
about = "Manufactures num transactions from Alice to random accounts. \
Only supported for development or local testnet."
)]
Factory(FactoryCmd),
}
impl GetLogFilter for CustomSubcommands {
fn get_log_filter(&self) -> Option<String> {
None
}
}
/// The `factory` command used to generate transactions.
/// Please note: this command currently only works on an empty database!
#[derive(Debug, StructOpt, Clone)]
pub struct FactoryCmd {
/// How often to repeat. This option only has an effect in mode `MasterToNToM`.
#[structopt(long="rounds", default_value = "1")]
pub rounds: u64,
/// MasterToN: Manufacture `num` transactions from the master account
/// to `num` randomly created accounts, one each.
///
/// MasterTo1: Manufacture `num` transactions from the master account
/// to exactly one other randomly created account.
///
/// MasterToNToM: Manufacture `num` transactions from the master account
/// to `num` randomly created accounts.
/// From each of these randomly created accounts manufacture
/// a transaction to another randomly created account.
/// Repeat this `rounds` times. If `rounds` = 1 the behavior
/// is the same as `MasterToN`.{n}
/// A -> B, A -> C, A -> D, ... x `num`{n}
/// B -> E, C -> F, D -> G, ...{n}
/// ... x `rounds`
///
/// These three modes control manufacturing.
#[structopt(long="mode", default_value = "MasterToN")]
pub mode: transaction_factory::Mode,
/// Number of transactions to generate. In mode `MasterNToNToM` this is
/// the number of transactions per round.
#[structopt(long="num", default_value = "8")]
pub num: u64,
#[allow(missing_docs)]
#[structopt(flatten)]
pub shared_params: SharedParams,
/// The means of execution used when calling into the runtime while importing blocks.
#[structopt(
long = "execution",
value_name = "STRATEGY",
possible_values = &ExecutionStrategyParam::variants(),
case_insensitive = true,
default_value = "NativeElseWasm"
)]
pub execution: ExecutionStrategyParam,
}
impl AugmentClap for FactoryCmd {
fn augment_clap<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
FactoryCmd::augment_clap(app)
}
}
/// Parse command line arguments into service configuration.
pub fn run<I, T, E>(args: I, exit: E, version: substrate_cli::VersionInfo) -> error::Result<()> where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
E: IntoExit,
{
type Config<A, B> = Configuration<(), A, B>;
match parse_and_prepare::<CustomSubcommands, NoCustom, _>(&version, "substrate-node", args) {
ParseAndPrepare::Run(cmd) => cmd.run(load_spec, exit,
|exit, _cli_args, _custom_args, config: Config<_, _>| {
info!("{}", version.name);
info!(" version {}", config.full_version());
info!(" by Parity Technologies, 2017-2019");
info!("Chain specification: {}", config.chain_spec.name());
info!("Node name: {}", config.name);
info!("Roles: {}", display_role(&config));
let runtime = RuntimeBuilder::new().name_prefix("main-tokio-").build()
.map_err(|e| format!("{:?}", e))?;
match config.roles {
ServiceRoles::LIGHT => run_until_exit(
runtime,
service::new_light(config)?,
exit
),
_ => run_until_exit(
runtime,
service::new_full(config)?,
exit
),
}
}),
ParseAndPrepare::BuildSpec(cmd) => cmd.run::<NoCustom, _, _, _>(load_spec),
ParseAndPrepare::ExportBlocks(cmd) => cmd.run_with_builder(|config: Config<_, _>|
Ok(new_full_start!(config).0), load_spec, exit),
ParseAndPrepare::ImportBlocks(cmd) => cmd.run_with_builder(|config: Config<_, _>|
Ok(new_full_start!(config).0), load_spec, exit),
ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec),
ParseAndPrepare::RevertChain(cmd) => cmd.run_with_builder(|config: Config<_, _>|
Ok(new_full_start!(config).0), load_spec),
ParseAndPrepare::CustomCommand(CustomSubcommands::Factory(cli_args)) => {
let mut config: Config<_, _> = substrate_cli::create_config_with_db_path(
load_spec,
&cli_args.shared_params,
&version,
)?;
config.execution_strategies = ExecutionStrategies {
importing: cli_args.execution.into(),
block_construction: cli_args.execution.into(),
other: cli_args.execution.into(),
..Default::default()
};
match ChainSpec::from(config.chain_spec.id()) {
Some(ref c) if c == &ChainSpec::Development || c == &ChainSpec::LocalTestnet => {},
_ => panic!("Factory is only supported for development and local testnet."),
}
let factory_state = FactoryState::new(
cli_args.mode.clone(),
cli_args.num,
cli_args.rounds,
);
let service_builder = new_full_start!(config).0;
transaction_factory::factory::<FactoryState<_>, _, _, _, _, _>(
factory_state,
service_builder.client(),
service_builder.select_chain()
.expect("The select_chain is always initialized by new_full_start!; QED")
).map_err(|e| format!("Error in transaction factory: {}", e))?;
Ok(())
}
}
}
fn run_until_exit<T, E>(
mut runtime: Runtime,
service: T,
e: E,
) -> error::Result<()>
where
T: AbstractService,
E: IntoExit,
{
let (exit_send, exit) = exit_future::signal();
let informant = substrate_cli::informant::build(&service);
runtime.executor().spawn(exit.until(informant).map(|_| ()));
// we eagerly drop the service so that the internal exit future is fired,
// but we need to keep holding a reference to the global telemetry guard
let _telemetry = service.telemetry();
let service_res = {
let exit = e.into_exit().map_err(|_| error::Error::Other("Exit future failed.".into()));
let service = service.map_err(|err| error::Error::Service(err));
let select = service.select(exit).map(|_| ()).map_err(|(err, _)| err);
runtime.block_on(select)
};
exit_send.fire();
// TODO [andre]: timeout this future #1318
let _ = runtime.shutdown_on_idle().wait();
service_res
}
+268
View File
@@ -0,0 +1,268 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Implementation of the transaction factory trait, which enables
//! using the cli to manufacture transactions and distribute them
//! to accounts.
use rand::{Rng, SeedableRng};
use rand::rngs::StdRng;
use codec::{Encode, Decode};
use keyring::sr25519::Keyring;
use node_runtime::{
Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall, ExistentialDeposit,
MinimumPeriod
};
use node_primitives::Signature;
use primitives::{sr25519, crypto::Pair};
use sr_primitives::{
generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension, Verify, IdentifyAccount}
};
use transaction_factory::RuntimeAdapter;
use transaction_factory::modes::Mode;
use inherents::InherentData;
use timestamp;
use finality_tracker;
type AccountPublic = <Signature as Verify>::Signer;
pub struct FactoryState<N> {
block_no: N,
mode: Mode,
start_number: u32,
rounds: u32,
round: u32,
block_in_round: u32,
num: u32,
}
type Number = <<node_primitives::Block as BlockT>::Header as HeaderT>::Number;
impl<Number> FactoryState<Number> {
fn build_extra(index: node_primitives::Index, phase: u64) -> node_runtime::SignedExtra {
(
system::CheckVersion::new(),
system::CheckGenesis::new(),
system::CheckEra::from(Era::mortal(256, phase)),
system::CheckNonce::from(index),
system::CheckWeight::new(),
transaction_payment::ChargeTransactionPayment::from(0),
Default::default(),
)
}
}
impl RuntimeAdapter for FactoryState<Number> {
type AccountId = node_primitives::AccountId;
type Balance = node_primitives::Balance;
type Block = node_primitives::Block;
type Phase = sr_primitives::generic::Phase;
type Secret = sr25519::Pair;
type Index = node_primitives::Index;
type Number = Number;
fn new(
mode: Mode,
num: u64,
rounds: u64,
) -> FactoryState<Self::Number> {
FactoryState {
mode,
num: num as u32,
round: 0,
rounds: rounds as u32,
block_in_round: 0,
block_no: 0,
start_number: 0,
}
}
fn block_no(&self) -> Self::Number {
self.block_no
}
fn block_in_round(&self) -> Self::Number {
self.block_in_round
}
fn rounds(&self) -> Self::Number {
self.rounds
}
fn num(&self) -> Self::Number {
self.num
}
fn round(&self) -> Self::Number {
self.round
}
fn start_number(&self) -> Self::Number {
self.start_number
}
fn mode(&self) -> &Mode {
&self.mode
}
fn set_block_no(&mut self, val: Self::Number) {
self.block_no = val;
}
fn set_block_in_round(&mut self, val: Self::Number) {
self.block_in_round = val;
}
fn set_round(&mut self, val: Self::Number) {
self.round = val;
}
fn transfer_extrinsic(
&self,
sender: &Self::AccountId,
key: &Self::Secret,
destination: &Self::AccountId,
amount: &Self::Balance,
version: u32,
genesis_hash: &<Self::Block as BlockT>::Hash,
prior_block_hash: &<Self::Block as BlockT>::Hash,
) -> <Self::Block as BlockT>::Extrinsic {
let index = self.extract_index(&sender, prior_block_hash);
let phase = self.extract_phase(*prior_block_hash);
sign::<Self>(CheckedExtrinsic {
signed: Some((sender.clone(), Self::build_extra(index, phase))),
function: Call::Balances(
BalancesCall::transfer(
indices::address::Address::Id(destination.clone().into()),
(*amount).into()
)
)
}, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ()))
}
fn inherent_extrinsics(&self) -> InherentData {
let timestamp = (self.block_no as u64 + 1) * MinimumPeriod::get();
let mut inherent = InherentData::new();
inherent.put_data(timestamp::INHERENT_IDENTIFIER, &timestamp)
.expect("Failed putting timestamp inherent");
inherent.put_data(finality_tracker::INHERENT_IDENTIFIER, &self.block_no)
.expect("Failed putting finalized number inherent");
inherent
}
fn minimum_balance() -> Self::Balance {
ExistentialDeposit::get()
}
fn master_account_id() -> Self::AccountId {
Keyring::Alice.to_account_id()
}
fn master_account_secret() -> Self::Secret {
Keyring::Alice.pair()
}
/// Generates a random `AccountId` from `seed`.
fn gen_random_account_id(seed: &Self::Number) -> Self::AccountId {
let pair: sr25519::Pair = sr25519::Pair::from_seed(&gen_seed_bytes(*seed));
AccountPublic::from(pair.public()).into_account()
}
/// Generates a random `Secret` from `seed`.
fn gen_random_account_secret(seed: &Self::Number) -> Self::Secret {
let pair: sr25519::Pair = sr25519::Pair::from_seed(&gen_seed_bytes(*seed));
pair
}
fn extract_index(
&self,
_account_id: &Self::AccountId,
_block_hash: &<Self::Block as BlockT>::Hash,
) -> Self::Index {
// TODO get correct index for account via api. See #2587.
// This currently prevents the factory from being used
// without a preceding purge of the database.
if self.mode == Mode::MasterToN || self.mode == Mode::MasterTo1 {
self.block_no() as Self::Index
} else {
match self.round() {
0 =>
// if round is 0 all transactions will be done with master as a sender
self.block_no() as Self::Index,
_ =>
// if round is e.g. 1 every sender account will be new and not yet have
// any transactions done
0
}
}
}
fn extract_phase(
&self,
_block_hash: <Self::Block as BlockT>::Hash
) -> Self::Phase {
// TODO get correct phase via api. See #2587.
// This currently prevents the factory from being used
// without a preceding purge of the database.
self.block_no() as Self::Phase
}
}
fn gen_seed_bytes(seed: u32) -> [u8; 32] {
let mut rng: StdRng = SeedableRng::seed_from_u64(seed as u64);
let mut seed_bytes = [0u8; 32];
for i in 0..32 {
seed_bytes[i] = rng.gen::<u8>();
}
seed_bytes
}
/// Creates an `UncheckedExtrinsic` containing the appropriate signature for
/// a `CheckedExtrinsics`.
fn sign<RA: RuntimeAdapter>(
xt: CheckedExtrinsic,
key: &sr25519::Pair,
additional_signed: <SignedExtra as SignedExtension>::AdditionalSigned,
) -> <RA::Block as BlockT>::Extrinsic {
let s = match xt.signed {
Some((signed, extra)) => {
let payload = (xt.function, extra.clone(), additional_signed);
let signature = payload.using_encoded(|b| {
if b.len() > 256 {
key.sign(&runtime_io::hashing::blake2_256(b))
} else {
key.sign(b)
}
}).into();
UncheckedExtrinsic {
signature: Some((indices::address::Address::Id(signed), signature, extra)),
function: payload.0,
}
}
None => UncheckedExtrinsic {
signature: None,
function: xt.function,
},
};
let e = Encode::encode(&s);
Decode::decode(&mut &e[..]).expect("Failed to decode signed unchecked extrinsic")
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Substrate CLI library.
//!
//! This package has two Cargo features:
//!
//! - `cli` (default): exposes functions that parse command-line options, then start and run the
//! node as a CLI application.
//!
//! - `browser`: exposes the content of the `browser` module, which consists of exported symbols
//! that are meant to be passed through the `wasm-bindgen` utility and called from JavaScript.
//! Despite its name the produced WASM can theoretically also be used from NodeJS, although this
//! hasn't been tested.
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
pub mod chain_spec;
#[macro_use]
mod service;
#[cfg(feature = "browser")]
mod browser;
#[cfg(feature = "cli")]
mod cli;
#[cfg(feature = "cli")]
mod factory_impl;
#[cfg(feature = "browser")]
pub use browser::*;
#[cfg(feature = "cli")]
pub use cli::*;
/// The chain specification option.
#[derive(Clone, Debug, PartialEq)]
pub enum ChainSpec {
/// Whatever the current runtime is, with just Alice as an auth.
Development,
/// Whatever the current runtime is, with simple Alice/Bob auths.
LocalTestnet,
/// The Flaming Fir testnet.
FlamingFir,
/// Whatever the current runtime is with the "global testnet" defaults.
StagingTestnet,
}
/// Get a chain config from a spec setting.
impl ChainSpec {
pub(crate) fn load(self) -> Result<chain_spec::ChainSpec, String> {
Ok(match self {
ChainSpec::FlamingFir => chain_spec::flaming_fir_config()?,
ChainSpec::Development => chain_spec::development_config(),
ChainSpec::LocalTestnet => chain_spec::local_testnet_config(),
ChainSpec::StagingTestnet => chain_spec::staging_testnet_config(),
})
}
pub(crate) fn from(s: &str) -> Option<Self> {
match s {
"dev" => Some(ChainSpec::Development),
"local" => Some(ChainSpec::LocalTestnet),
"" | "fir" | "flaming-fir" => Some(ChainSpec::FlamingFir),
"staging" => Some(ChainSpec::StagingTestnet),
_ => None,
}
}
}
fn load_spec(id: &str) -> Result<Option<chain_spec::ChainSpec>, String> {
Ok(match ChainSpec::from(id) {
Some(spec) => Some(spec.load()?),
None => None,
})
}
+616
View File
@@ -0,0 +1,616 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
#![warn(unused_extern_crates)]
//! Service implementation. Specialized wrapper over substrate service.
use std::sync::Arc;
use babe;
use client::{self, LongestChain};
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider};
use node_executor;
use node_primitives::Block;
use node_runtime::{GenesisConfig, RuntimeApi};
use substrate_service::{
AbstractService, ServiceBuilder, config::Configuration, error::{Error as ServiceError},
};
use transaction_pool::{self, txpool::{Pool as TransactionPool}};
use inherents::InherentDataProviders;
use network::construct_simple_protocol;
use substrate_service::{Service, NetworkStatus};
use client::{Client, LocalCallExecutor};
use client_db::Backend;
use sr_primitives::traits::Block as BlockT;
use node_executor::NativeExecutor;
use network::NetworkService;
use offchain::OffchainWorkers;
use primitives::Blake2Hasher;
construct_simple_protocol! {
/// Demo protocol attachment for substrate.
pub struct NodeProtocol where Block = Block { }
}
/// Starts a `ServiceBuilder` for a full service.
///
/// Use this macro if you don't actually need the full service, but just the builder in order to
/// be able to perform chain operations.
macro_rules! new_full_start {
($config:expr) => {{
type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
let mut import_setup = None;
let inherent_data_providers = inherents::InherentDataProviders::new();
let builder = substrate_service::ServiceBuilder::new_full::<
node_primitives::Block, node_runtime::RuntimeApi, node_executor::Executor
>($config)?
.with_select_chain(|_config, backend| {
Ok(client::LongestChain::new(backend.clone()))
})?
.with_transaction_pool(|config, client|
Ok(transaction_pool::txpool::Pool::new(config, transaction_pool::FullChainApi::new(client)))
)?
.with_import_queue(|_config, client, mut select_chain, _transaction_pool| {
let select_chain = select_chain.take()
.ok_or_else(|| substrate_service::Error::SelectChainRequired)?;
let (grandpa_block_import, grandpa_link) = grandpa::block_import(
client.clone(),
&*client,
select_chain,
)?;
let justification_import = grandpa_block_import.clone();
let (block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link.clone(),
block_import.clone(),
Some(Box::new(justification_import)),
None,
client.clone(),
client,
inherent_data_providers.clone(),
)?;
import_setup = Some((block_import, grandpa_link, babe_link));
Ok(import_queue)
})?
.with_rpc_extensions(|client, pool, _backend| -> RpcExtension {
node_rpc::create(client, pool)
})?;
(builder, import_setup, inherent_data_providers)
}}
}
/// Creates a full service from the configuration.
///
/// We need to use a macro because the test suit doesn't work with an opaque service. It expects
/// concrete types instead.
macro_rules! new_full {
($config:expr, $with_startup_data: expr) => {{
use futures::sync::mpsc;
use network::DhtEvent;
use futures03::{
compat::Stream01CompatExt,
stream::StreamExt,
future::{FutureExt, TryFutureExt},
};
let (
is_authority,
force_authoring,
name,
disable_grandpa
) = (
$config.roles.is_authority(),
$config.force_authoring,
$config.name.clone(),
$config.disable_grandpa
);
// sentry nodes announce themselves as authorities to the network
// and should run the same protocols authorities do, but it should
// never actively participate in any consensus process.
let participates_in_consensus = is_authority && !$config.sentry_mode;
let (builder, mut import_setup, inherent_data_providers) = new_full_start!($config);
// Dht event channel from the network to the authority discovery module. Use bounded channel to ensure
// back-pressure. Authority discovery is triggering one event per authority within the current authority set.
// This estimates the authority set size to be somewhere below 10 000 thereby setting the channel buffer size to
// 10 000.
let (dht_event_tx, dht_event_rx) =
mpsc::channel::<DhtEvent>(10_000);
let service = builder.with_network_protocol(|_| Ok(crate::service::NodeProtocol::new()))?
.with_finality_proof_provider(|client, backend|
Ok(Arc::new(grandpa::FinalityProofProvider::new(backend, client)) as _)
)?
.with_dht_event_tx(dht_event_tx)?
.build()?;
let (block_import, grandpa_link, babe_link) = import_setup.take()
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
($with_startup_data)(&block_import, &babe_link);
if participates_in_consensus {
let proposer = substrate_basic_authorship::ProposerFactory {
client: service.client(),
transaction_pool: service.transaction_pool(),
};
let client = service.client();
let select_chain = service.select_chain()
.ok_or(substrate_service::Error::SelectChainRequired)?;
let babe_config = babe::BabeParams {
keystore: service.keystore(),
client,
select_chain,
env: proposer,
block_import,
sync_oracle: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
force_authoring,
babe_link,
};
let babe = babe::start_babe(babe_config)?;
service.spawn_essential_task(babe);
let future03_dht_event_rx = dht_event_rx.compat()
.map(|x| x.expect("<mpsc::channel::Receiver as Stream> never returns an error; qed"))
.boxed();
let authority_discovery = authority_discovery::AuthorityDiscovery::new(
service.client(),
service.network(),
service.keystore(),
future03_dht_event_rx,
);
let future01_authority_discovery = authority_discovery.map(|x| Ok(x)).compat();
service.spawn_task(future01_authority_discovery);
}
// if the node isn't actively participating in consensus then it doesn't
// need a keystore, regardless of which protocol we use below.
let keystore = if participates_in_consensus {
Some(service.keystore())
} else {
None
};
let config = grandpa::Config {
// FIXME #1578 make this available through chainspec
gossip_duration: std::time::Duration::from_millis(333),
justification_period: 512,
name: Some(name),
observer_enabled: true,
keystore,
is_authority,
};
match (is_authority, disable_grandpa) {
(false, false) => {
// start the lightweight GRANDPA observer
service.spawn_task(grandpa::run_grandpa_observer(
config,
grandpa_link,
service.network(),
service.on_exit(),
)?);
},
(true, false) => {
// start the full GRANDPA voter
let grandpa_config = grandpa::GrandpaParams {
config: config,
link: grandpa_link,
network: service.network(),
inherent_data_providers: inherent_data_providers.clone(),
on_exit: service.on_exit(),
telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
voting_rule: grandpa::VotingRulesBuilder::default().build(),
};
// the GRANDPA voter task is considered infallible, i.e.
// if it fails we take down the service with it.
service.spawn_essential_task(grandpa::run_grandpa_voter(grandpa_config)?);
},
(_, true) => {
grandpa::setup_disabled_grandpa(
service.client(),
&inherent_data_providers,
service.network(),
)?;
},
}
Ok((service, inherent_data_providers))
}};
($config:expr) => {{
new_full!($config, |_, _| {})
}}
}
#[allow(dead_code)]
type ConcreteBlock = node_primitives::Block;
#[allow(dead_code)]
type ConcreteClient =
Client<
Backend<ConcreteBlock>,
LocalCallExecutor<Backend<ConcreteBlock>,
NativeExecutor<node_executor::Executor>>,
ConcreteBlock,
node_runtime::RuntimeApi
>;
#[allow(dead_code)]
type ConcreteBackend = Backend<ConcreteBlock>;
/// A specialized configuration object for setting up the node..
pub type NodeConfiguration<C> = Configuration<C, GenesisConfig, crate::chain_spec::Extensions>;
/// Builds a new service for a full client.
pub fn new_full<C: Send + Default + 'static>(config: NodeConfiguration<C>)
-> Result<
Service<
ConcreteBlock,
ConcreteClient,
LongestChain<ConcreteBackend, ConcreteBlock>,
NetworkStatus<ConcreteBlock>,
NetworkService<ConcreteBlock, crate::service::NodeProtocol, <ConcreteBlock as BlockT>::Hash>,
TransactionPool<transaction_pool::FullChainApi<ConcreteClient, ConcreteBlock>>,
OffchainWorkers<
ConcreteClient,
<ConcreteBackend as client_api::backend::Backend<Block, Blake2Hasher>>::OffchainStorage,
ConcreteBlock,
>
>,
ServiceError,
>
{
new_full!(config).map(|(service, _)| service)
}
/// Builds a new service for a light client.
pub fn new_light<C: Send + Default + 'static>(config: NodeConfiguration<C>)
-> Result<impl AbstractService, ServiceError> {
type RpcExtension = jsonrpc_core::IoHandler<substrate_rpc::Metadata>;
let inherent_data_providers = InherentDataProviders::new();
let service = ServiceBuilder::new_light::<Block, RuntimeApi, node_executor::Executor>(config)?
.with_select_chain(|_config, backend| {
Ok(LongestChain::new(backend.clone()))
})?
.with_transaction_pool(|config, client|
Ok(TransactionPool::new(config, transaction_pool::FullChainApi::new(client)))
)?
.with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, _tx_pool| {
let fetch_checker = fetcher
.map(|fetcher| fetcher.checker().clone())
.ok_or_else(|| "Trying to start light import queue without active fetch checker")?;
let grandpa_block_import = grandpa::light_block_import::<_, _, _, RuntimeApi>(
client.clone(),
backend,
&*client,
Arc::new(fetch_checker),
)?;
let finality_proof_import = grandpa_block_import.clone();
let finality_proof_request_builder =
finality_proof_import.create_finality_proof_request_builder();
let (babe_block_import, babe_link) = babe::block_import(
babe::Config::get_or_compute(&*client)?,
grandpa_block_import,
client.clone(),
client.clone(),
)?;
let import_queue = babe::import_queue(
babe_link,
babe_block_import,
None,
Some(Box::new(finality_proof_import)),
client.clone(),
client,
inherent_data_providers.clone(),
)?;
Ok((import_queue, finality_proof_request_builder))
})?
.with_network_protocol(|_| Ok(NodeProtocol::new()))?
.with_finality_proof_provider(|client, backend|
Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _)
)?
.with_rpc_extensions(|client, pool, _backend| -> RpcExtension {
node_rpc::create(client, pool)
})?
.build()?;
Ok(service)
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use babe::CompatibleDigestItem;
use consensus_common::{
Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport,
};
use node_primitives::{Block, DigestItem, Signature};
use node_runtime::{BalancesCall, Call, UncheckedExtrinsic, Address};
use node_runtime::constants::{currency::CENTS, time::SLOT_DURATION};
use codec::{Encode, Decode};
use primitives::{crypto::Pair as CryptoPair, H256};
use sr_primitives::{
generic::{BlockId, Era, Digest, SignedPayload},
traits::Block as BlockT,
traits::Verify,
OpaqueExtrinsic,
};
use timestamp;
use finality_tracker;
use keyring::AccountKeyring;
use substrate_service::{AbstractService, Roles};
use crate::service::new_full;
use sr_primitives::traits::IdentifyAccount;
type AccountPublic = <Signature as Verify>::Signer;
#[cfg(feature = "rhd")]
fn test_sync() {
use primitives::ed25519::Pair;
use {service_test, Factory};
use client::{BlockImportParams, BlockOrigin};
let alice: Arc<ed25519::Pair> = Arc::new(Keyring::Alice.into());
let bob: Arc<ed25519::Pair> = Arc::new(Keyring::Bob.into());
let validators = vec![alice.public().0.into(), bob.public().0.into()];
let keys: Vec<&ed25519::Pair> = vec![&*alice, &*bob];
let dummy_runtime = ::tokio::runtime::Runtime::new().unwrap();
let block_factory = |service: &<Factory as service::ServiceFactory>::FullService| {
let block_id = BlockId::number(service.client().info().chain.best_number);
let parent_header = service.client().header(&block_id).unwrap().unwrap();
let consensus_net = ConsensusNetwork::new(service.network(), service.client().clone());
let proposer_factory = consensus::ProposerFactory {
client: service.client().clone(),
transaction_pool: service.transaction_pool().clone(),
network: consensus_net,
force_delay: 0,
handle: dummy_runtime.executor(),
};
let (proposer, _, _) = proposer_factory.init(&parent_header, &validators, alice.clone()).unwrap();
let block = proposer.propose().expect("Error making test block");
BlockImportParams {
origin: BlockOrigin::File,
justification: Vec::new(),
internal_justification: Vec::new(),
finalized: false,
body: Some(block.extrinsics),
header: block.header,
auxiliary: Vec::new(),
}
};
let extrinsic_factory =
|service: &SyncService<<Factory as service::ServiceFactory>::FullService>|
{
let payload = (
0,
Call::Balances(BalancesCall::transfer(RawAddress::Id(bob.public().0.into()), 69.into())),
Era::immortal(),
service.client().genesis_hash()
);
let signature = alice.sign(&payload.encode()).into();
let id = alice.public().0.into();
let xt = UncheckedExtrinsic {
signature: Some((RawAddress::Id(id), signature, payload.0, Era::immortal())),
function: payload.1,
}.encode();
let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
OpaqueExtrinsic(v)
};
service_test::sync(
chain_spec::integration_test_config(),
|config| new_full(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
block_factory,
extrinsic_factory,
);
}
#[test]
#[ignore]
fn test_sync() {
let keystore_path = tempfile::tempdir().expect("Creates keystore path");
let keystore = keystore::Store::open(keystore_path.path(), None)
.expect("Creates keystore");
let alice = keystore.write().insert_ephemeral_from_seed::<babe::AuthorityPair>("//Alice")
.expect("Creates authority pair");
let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
// For the block factory
let mut slot_num = 1u64;
// For the extrinsics factory
let bob = Arc::new(AccountKeyring::Bob.pair());
let charlie = Arc::new(AccountKeyring::Charlie.pair());
let mut index = 0;
service_test::sync(
chain_spec,
|config| {
let mut setup_handles = None;
new_full!(config, |
block_import: &babe::BabeBlockImport<_, _, Block, _, _, _>,
babe_link: &babe::BabeLink<Block>,
| {
setup_handles = Some((block_import.clone(), babe_link.clone()));
}).map(move |(node, x)| (node, (x, setup_handles.unwrap())))
},
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
|service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
let mut inherent_data = inherent_data_providers
.create_inherent_data()
.expect("Creates inherent data.");
inherent_data.replace_data(finality_tracker::INHERENT_IDENTIFIER, &1u64);
let parent_id = BlockId::number(service.client().info().chain.best_number);
let parent_header = service.client().header(&parent_id).unwrap().unwrap();
let mut proposer_factory = substrate_basic_authorship::ProposerFactory {
client: service.client(),
transaction_pool: service.transaction_pool(),
};
let mut digest = Digest::<H256>::default();
// even though there's only one authority some slots might be empty,
// so we must keep trying the next slots until we can claim one.
let babe_pre_digest = loop {
inherent_data.replace_data(timestamp::INHERENT_IDENTIFIER, &(slot_num * SLOT_DURATION));
if let Some(babe_pre_digest) = babe::test_helpers::claim_slot(
slot_num,
&parent_header,
&*service.client(),
&keystore,
&babe_link,
) {
break babe_pre_digest;
}
slot_num += 1;
};
digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(babe_pre_digest));
let mut proposer = proposer_factory.init(&parent_header).unwrap();
let new_block = futures03::executor::block_on(proposer.propose(
inherent_data,
digest,
std::time::Duration::from_secs(1),
)).expect("Error making test block");
let (new_header, new_body) = new_block.deconstruct();
let pre_hash = new_header.hash();
// sign the pre-sealed hash of the block and then
// add it to a digest item.
let to_sign = pre_hash.encode();
let signature = alice.sign(&to_sign[..]);
let item = <DigestItem as CompatibleDigestItem>::babe_seal(
signature.into(),
);
slot_num += 1;
let params = BlockImportParams {
origin: BlockOrigin::File,
header: new_header,
justification: None,
post_digests: vec![item],
body: Some(new_body),
finalized: false,
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
allow_missing_state: false,
};
block_import.import_block(params, Default::default())
.expect("error importing test block");
},
|service, _| {
let amount = 5 * CENTS;
let to: Address = AccountPublic::from(bob.public()).into_account().into();
let from: Address = AccountPublic::from(charlie.public()).into_account().into();
let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
let best_block_id = BlockId::number(service.client().info().chain.best_number);
let version = service.client().runtime_version_at(&best_block_id).unwrap().spec_version;
let signer = charlie.clone();
let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
let check_version = system::CheckVersion::new();
let check_genesis = system::CheckGenesis::new();
let check_era = system::CheckEra::from(Era::Immortal);
let check_nonce = system::CheckNonce::from(index);
let check_weight = system::CheckWeight::new();
let payment = transaction_payment::ChargeTransactionPayment::from(0);
let extra = (
check_version,
check_genesis,
check_era,
check_nonce,
check_weight,
payment,
Default::default(),
);
let raw_payload = SignedPayload::from_raw(
function,
extra,
(version, genesis_hash, genesis_hash, (), (), (), ())
);
let signature = raw_payload.using_encoded(|payload| {
signer.sign(payload)
});
let (function, extra, _) = raw_payload.deconstruct();
let xt = UncheckedExtrinsic::new_signed(
function,
from.into(),
signature.into(),
extra,
).encode();
let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
index += 1;
OpaqueExtrinsic(v)
},
);
}
#[test]
#[ignore]
fn test_consensus() {
service_test::consensus(
crate::chain_spec::tests::integration_test_config_with_two_authorities(),
|config| new_full(config),
|mut config| {
// light nodes are unsupported
config.roles = Roles::FULL;
new_full(config)
},
vec![
"//Alice".into(),
"//Bob".into(),
],
)
}
}