diff --git a/Cargo.lock b/Cargo.lock index 9980d04c8d7d5..bda9fe6baf59e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2713,6 +2713,7 @@ dependencies = [ "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0", "sr-primitives 2.0.0", + "srml-authority-discovery 0.1.0", "srml-balances 2.0.0", "srml-contracts 2.0.0", "srml-finality-tracker 2.0.0", @@ -2723,6 +2724,8 @@ dependencies = [ "srml-timestamp 2.0.0", "srml-transaction-payment 2.0.0", "structopt 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-authority-discovery 2.0.0", + "substrate-authority-discovery-primitives 2.0.0", "substrate-basic-authorship 2.0.0", "substrate-build-script-utils 2.0.0", "substrate-chain-spec 2.0.0", @@ -2837,6 +2840,7 @@ dependencies = [ "sr-staking-primitives 2.0.0", "sr-std 2.0.0", "sr-version 2.0.0", + "srml-authority-discovery 0.1.0", "srml-authorship 0.1.0", "srml-babe 2.0.0", "srml-balances 2.0.0", @@ -2866,6 +2870,7 @@ dependencies = [ "srml-transaction-payment-rpc-runtime-api 2.0.0", "srml-treasury 2.0.0", "srml-utility 2.0.0", + "substrate-authority-discovery-primitives 2.0.0", "substrate-block-builder-runtime-api 2.0.0", "substrate-consensus-babe-primitives 2.0.0", "substrate-inherents 2.0.0", @@ -4441,7 +4446,7 @@ dependencies = [ "srml-support 2.0.0", "srml-system 2.0.0", "substrate-application-crypto 2.0.0", - "substrate-consensus-babe-primitives 2.0.0", + "substrate-authority-discovery-primitives 2.0.0", "substrate-primitives 2.0.0", ] @@ -5257,6 +5262,7 @@ dependencies = [ "sr-primitives 2.0.0", "substrate-authority-discovery-primitives 2.0.0", "substrate-client 2.0.0", + "substrate-keystore 2.0.0", "substrate-network 2.0.0", "substrate-peerset 2.0.0", "substrate-primitives 2.0.0", @@ -5271,6 +5277,7 @@ dependencies = [ "sr-api 2.0.0", "sr-primitives 2.0.0", "sr-std 2.0.0", + "substrate-application-crypto 2.0.0", ] [[package]] diff --git a/core/authority-discovery/Cargo.toml b/core/authority-discovery/Cargo.toml index c46b815723f62..5d720c195342a 100644 --- a/core/authority-discovery/Cargo.toml +++ b/core/authority-discovery/Cargo.toml @@ -15,6 +15,8 @@ client = { package = "substrate-client", path = "../../core/client" } codec = { package = "parity-scale-codec", default-features = false, version = "1.0.3" } derive_more = "0.15.0" futures-preview = "0.3.0-alpha.19" +futures-timer = "0.4" +keystore = { package = "substrate-keystore", path = "../keystore" } libp2p = { version = "0.13.0", default-features = false, features = ["secp256k1", "libp2p-websocket"] } log = "0.4.8" network = { package = "substrate-network", path = "../../core/network" } @@ -22,7 +24,6 @@ primitives = { package = "substrate-primitives", path = "../primitives" } prost = "0.5.0" serde_json = "1.0.41" sr-primitives = { path = "../../core/sr-primitives" } -futures-timer = "0.4" [dev-dependencies] parking_lot = "0.9.0" diff --git a/core/authority-discovery/primitives/Cargo.toml b/core/authority-discovery/primitives/Cargo.toml index 41d833f58472f..96df8d3085a0b 100644 --- a/core/authority-discovery/primitives/Cargo.toml +++ b/core/authority-discovery/primitives/Cargo.toml @@ -6,14 +6,16 @@ description = "Authority discovery primitives" edition = "2018" [dependencies] +app-crypto = { package = "substrate-application-crypto", path = "../../application-crypto", default-features = false } codec = { package = "parity-scale-codec", default-features = false, version = "1.0.3" } +rstd = { package = "sr-std", path = "../../sr-std", default-features = false } sr-api = { path = "../../sr-api", default-features = false } sr-primitives = { path = "../../sr-primitives", default-features = false } -rstd = { package = "sr-std", path = "../../sr-std", default-features = false } [features] default = ["std"] std = [ + "app-crypto/std", "rstd/std", "sr-api/std", "codec/std", diff --git a/core/authority-discovery/primitives/src/lib.rs b/core/authority-discovery/primitives/src/lib.rs index dda2cbc68dba8..1a5833f904118 100644 --- a/core/authority-discovery/primitives/src/lib.rs +++ b/core/authority-discovery/primitives/src/lib.rs @@ -19,31 +19,28 @@ #![cfg_attr(not(feature = "std"), no_std)] use rstd::vec::Vec; -use sr_primitives::RuntimeDebug; -#[derive(codec::Encode, codec::Decode, Eq, PartialEq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Hash))] -pub struct Signature(pub Vec); -#[derive(codec::Encode, codec::Decode, Eq, PartialEq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Hash))] -pub struct AuthorityId(pub Vec); +mod app { + use app_crypto::{app_crypto, key_types::AUTHORITY_DISCOVERY, sr25519}; + app_crypto!(sr25519, AUTHORITY_DISCOVERY); +} + +/// An authority discovery authority keypair. +#[cfg(feature = "std")] +pub type AuthorityPair = app::Pair; + +/// An authority discovery authority identifier. +pub type AuthorityId = app::Public; + +/// An authority discovery authority signature. +pub type AuthoritySignature = app::Signature; sr_api::decl_runtime_apis! { /// The authority discovery api. /// - /// This api is used by the `core/authority-discovery` module to retrieve our - /// own authority identifier, to retrieve identifiers of the current authority - /// set, as well as sign and verify Kademlia Dht external address payloads - /// from and to other authorities. + /// This api is used by the `core/authority-discovery` module to retrieve identifiers of the current authority set. pub trait AuthorityDiscoveryApi { /// Retrieve authority identifiers of the current authority set. fn authorities() -> Vec; - - /// Sign the given payload with the private key corresponding to the given authority id. - fn sign(payload: &Vec) -> Option<(Signature, AuthorityId)>; - - /// Verify the given signature for the given payload with the given - /// authority identifier. - fn verify(payload: &Vec, signature: &Signature, authority_id: &AuthorityId) -> bool; } } diff --git a/core/authority-discovery/src/error.rs b/core/authority-discovery/src/error.rs index dca50cc0beb9e..3e50ce54816f6 100644 --- a/core/authority-discovery/src/error.rs +++ b/core/authority-discovery/src/error.rs @@ -28,18 +28,18 @@ pub enum Error { HashingAuthorityId(libp2p::core::multiaddr::multihash::EncodeError), /// Failed calling into the Substrate runtime. CallingRuntime(client::error::Error), - /// Failed signing the dht payload via the Substrate runtime. - SigningDhtPayload, /// From the Dht we only get the hashed authority id. In order to retrieve the actual authority id and to ensure it /// is actually an authority, we match the hash against the hash of the authority id of all other authorities. This /// error is the result of the above failing. MatchingHashedAuthorityIdWithAuthorityId, /// Failed to set the authority discovery peerset priority group in the peerset module. SettingPeersetPriorityGroup(String), - /// Failed to encode a dht payload. - Encoding(prost::EncodeError), - /// Failed to decode a dht payload. - Decoding(prost::DecodeError), + /// Failed to encode a protobuf payload. + EncodingProto(prost::EncodeError), + /// Failed to decode a protobuf payload. + DecodingProto(prost::DecodeError), + /// Failed to encode or decode scale payload + EncodingDecodingScale(codec::Error), /// Failed to parse a libp2p multi address. ParsingMultiaddress(libp2p::core::multiaddr::Error), } diff --git a/core/authority-discovery/src/lib.rs b/core/authority-discovery/src/lib.rs index 13831be76f8ef..76cb069b73e37 100644 --- a/core/authority-discovery/src/lib.rs +++ b/core/authority-discovery/src/lib.rs @@ -48,20 +48,22 @@ use std::iter::FromIterator; use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; -use futures::channel::mpsc::Receiver; -use futures::stream::StreamExt; use futures::task::{Context, Poll}; use futures::Future; use futures_timer::Interval; +use futures::prelude::*; -use authority_discovery_primitives::{AuthorityDiscoveryApi, AuthorityId, Signature}; +use authority_discovery_primitives::{AuthorityDiscoveryApi, AuthorityId, AuthoritySignature, AuthorityPair}; use client::blockchain::HeaderBackend; +use codec::{Decode, Encode}; use error::{Error, Result}; use log::{debug, error, log_enabled, warn}; use network::specialization::NetworkSpecialization; use network::{DhtEvent, ExHashT}; +use primitives::crypto::{key_types, Pair}; +use primitives::traits::BareCryptoStorePtr; use prost::Message; use sr_primitives::generic::BlockId; use sr_primitives::traits::{Block as BlockT, ProvideRuntimeApi}; @@ -72,6 +74,12 @@ mod schema { include!(concat!(env!("OUT_DIR"), "/authority_discovery.rs")); } +/// Upper bound estimation on how long one should wait before accessing the Kademlia DHT. +const LIBP2P_KADEMLIA_BOOTSTRAP_TIME: Duration = Duration::from_secs(30); + +/// Name of the Substrate peerset priority group for authorities discovered through the authority discovery module. +const AUTHORITIES_PRIORITY_GROUP_NAME: &'static str = "authorities"; + /// An `AuthorityDiscovery` makes a given authority discoverable and discovers other authorities. pub struct AuthorityDiscovery where @@ -79,12 +87,15 @@ where Network: NetworkProvider, Client: ProvideRuntimeApi + Send + Sync + 'static + HeaderBackend, ::Api: AuthorityDiscoveryApi, + { client: Arc, network: Arc, /// Channel we receive Dht events on. - dht_event_rx: Receiver, + dht_event_rx: Pin + Send>>, + + key_store: BareCryptoStorePtr, /// Interval to be proactive, publishing own addresses. publish_interval: Interval, @@ -112,16 +123,23 @@ where pub fn new( client: Arc, network: Arc, - dht_event_rx: Receiver, + key_store: BareCryptoStorePtr, + dht_event_rx: Pin + Send>>, ) -> Self { // Kademlia's default time-to-live for Dht records is 36h, republishing records every 24h. Given that a node // could restart at any point in time, one can not depend on the republishing process, thus publishing own // external addresses should happen on an interval < 36h. - let publish_interval = Interval::new(Duration::from_secs(12 * 60 * 60)); + let publish_interval = Interval::new_at( + Instant::now() + LIBP2P_KADEMLIA_BOOTSTRAP_TIME, + Duration::from_secs(12 * 60 * 60), + ); // External addresses of other authorities can change at any given point in time. The interval on which to query // for external addresses of other authorities is a trade off between efficiency and performance. - let query_interval = Interval::new(Duration::from_secs(10 * 60)); + let query_interval = Interval::new_at( + Instant::now() + LIBP2P_KADEMLIA_BOOTSTRAP_TIME, + Duration::from_secs(10 * 60), + ); let address_cache = HashMap::new(); @@ -129,6 +147,7 @@ where client, network, dht_event_rx, + key_store, publish_interval, query_interval, address_cache, @@ -137,8 +156,6 @@ where } fn publish_own_ext_addresses(&mut self) -> Result<()> { - let id = BlockId::hash(self.client.info().best_hash); - let addresses = self .network .external_addresses() @@ -154,27 +171,24 @@ where let mut serialized_addresses = vec![]; schema::AuthorityAddresses { addresses } .encode(&mut serialized_addresses) - .map_err(Error::Encoding)?; + .map_err(Error::EncodingProto)?; - let (signature, authority_id) = self - .client - .runtime_api() - .sign(&id, &serialized_addresses) - .map_err(Error::CallingRuntime)? - .ok_or(Error::SigningDhtPayload)?; + for key in self.get_priv_keys_within_authority_set()?.into_iter() { + let signature = key.sign(&serialized_addresses); - let mut signed_addresses = vec![]; - schema::SignedAuthorityAddresses { - addresses: serialized_addresses, - signature: signature.0, - } - .encode(&mut signed_addresses) - .map_err(Error::Encoding)?; + let mut signed_addresses = vec![]; + schema::SignedAuthorityAddresses { + addresses: serialized_addresses.clone(), + signature: signature.encode(), + } + .encode(&mut signed_addresses) + .map_err(Error::EncodingProto)?; - self.network.put_value( - hash_authority_id(authority_id.0.as_ref())?, - signed_addresses, - ); + self.network.put_value( + hash_authority_id(key.public().as_ref())?, + signed_addresses, + ); + } Ok(()) } @@ -190,7 +204,7 @@ where for authority_id in authorities.iter() { self.network - .get_value(&hash_authority_id(authority_id.0.as_ref())?); + .get_value(&hash_authority_id(authority_id.as_ref())?); } Ok(()) @@ -230,16 +244,16 @@ where ) -> Result<()> { debug!(target: "sub-authority-discovery", "Got Dht value from network."); - let id = BlockId::hash(self.client.info().best_hash); + let block_id = BlockId::hash(self.client.info().best_hash); // From the Dht we only get the hashed authority id. In order to retrieve the actual authority id and to ensure // it is actually an authority, we match the hash against the hash of the authority id of all other authorities. - let authorities = self.client.runtime_api().authorities(&id)?; + let authorities = self.client.runtime_api().authorities(&block_id)?; self.purge_old_authorities_from_cache(&authorities); let authorities = authorities .into_iter() - .map(|a| hash_authority_id(a.0.as_ref()).map(|h| (h, a))) + .map(|id| hash_authority_id(id.as_ref()).map(|h| (h, id))) .collect::>>()?; for (key, value) in values.iter() { @@ -251,22 +265,16 @@ where let schema::SignedAuthorityAddresses { signature, addresses, - } = schema::SignedAuthorityAddresses::decode(value).map_err(Error::Decoding)?; - let signature = Signature(signature); - - let is_verified = self - .client - .runtime_api() - .verify(&id, &addresses, &signature, &authority_id.clone()) - .map_err(Error::CallingRuntime)?; + } = schema::SignedAuthorityAddresses::decode(value).map_err(Error::DecodingProto)?; + let signature = AuthoritySignature::decode(&mut &signature[..]).map_err(Error::EncodingDecodingScale)?; - if !is_verified { + if !AuthorityPair::verify(&signature, &addresses, authority_id) { return Err(Error::VerifyingDhtPayload); } let addresses: Vec = schema::AuthorityAddresses::decode(addresses) .map(|a| a.addresses) - .map_err(Error::Decoding)? + .map_err(Error::DecodingProto)? .into_iter() .map(|a| a.try_into()) .collect::>() @@ -275,7 +283,7 @@ where self.address_cache.insert(authority_id.clone(), addresses); } - // Let's update the peerset priority group with the all the addresses we have in our cache. + // Let's update the peerset priority group with all the addresses we have in our cache. let addresses = HashSet::from_iter( self.address_cache @@ -286,7 +294,7 @@ where debug!(target: "sub-authority-discovery", "Applying priority group {:#?} to peerset.", addresses); self.network - .set_priority_group("authorities".to_string(), addresses) + .set_priority_group(AUTHORITIES_PRIORITY_GROUP_NAME.to_string(), addresses) .map_err(Error::SettingPeersetPriorityGroup)?; Ok(()) @@ -296,6 +304,52 @@ where self.address_cache .retain(|peer_id, _addresses| current_authorities.contains(peer_id)) } + + /// Retrieve all local authority discovery private keys that are within the current authority + /// set. + fn get_priv_keys_within_authority_set(&mut self) -> Result> { + let keys = self.get_own_public_keys_within_authority_set()? + .into_iter() + .map(std::convert::Into::into) + .filter_map(|pub_key| { + self.key_store.read().sr25519_key_pair(key_types::AUTHORITY_DISCOVERY, &pub_key) + }) + .map(std::convert::Into::into) + .collect(); + + Ok(keys) + } + + /// Retrieve our public keys within the current authority set. + // + // A node might have multiple authority discovery keys within its keystore, e.g. an old one and + // one for the upcoming session. In addition it could be participating in the current authority + // set with two keys. The function does not return all of the local authority discovery public + // keys, but only the ones intersecting with the current authority set. + fn get_own_public_keys_within_authority_set(&mut self) -> Result> { + let local_pub_keys = self.key_store + .read() + .sr25519_public_keys(key_types::AUTHORITY_DISCOVERY) + .into_iter() + .collect::>(); + + let id = BlockId::hash(self.client.info().best_hash); + let current_authorities = self + .client + .runtime_api() + .authorities(&id) + .map_err(Error::CallingRuntime)? + .into_iter() + .map(std::convert::Into::into) + .collect::>(); + + let intersection = local_pub_keys.intersection(¤t_authorities) + .cloned() + .map(std::convert::Into::into) + .collect(); + + Ok(intersection) + } } impl Future for AuthorityDiscovery @@ -408,20 +462,22 @@ mod tests { use futures::channel::mpsc::channel; use futures::executor::block_on; use futures::future::poll_fn; - use primitives::{ExecutionContext, NativeOrEncoded}; + use primitives::{ExecutionContext, NativeOrEncoded, testing::KeyStore}; use sr_primitives::traits::Zero; use sr_primitives::traits::{ApiRef, Block as BlockT, NumberFor, ProvideRuntimeApi}; use std::sync::{Arc, Mutex}; use test_client::runtime::Block; #[derive(Clone)] - struct TestApi {} + struct TestApi { + authorities: Vec, + } impl ProvideRuntimeApi for TestApi { type Api = RuntimeApi; fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { - RuntimeApi {}.into() + RuntimeApi{authorities: self.authorities.clone()}.into() } } @@ -466,7 +522,9 @@ mod tests { } } - struct RuntimeApi {} + struct RuntimeApi { + authorities: Vec, + } impl Core for RuntimeApi { fn Core_version_runtime_api_impl( @@ -534,37 +592,7 @@ mod tests { _: Option<()>, _: Vec, ) -> std::result::Result>, client::error::Error> { - return Ok(NativeOrEncoded::Native(vec![ - AuthorityId("test-authority-id-1".as_bytes().to_vec()), - AuthorityId("test-authority-id-2".as_bytes().to_vec()), - ])); - } - fn AuthorityDiscoveryApi_sign_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - _: Option<&std::vec::Vec>, - _: Vec, - ) -> std::result::Result< - NativeOrEncoded>, - client::error::Error, - > { - return Ok(NativeOrEncoded::Native(Some(( - Signature("test-signature-1".as_bytes().to_vec()), - AuthorityId("test-authority-id-1".as_bytes().to_vec()), - )))); - } - fn AuthorityDiscoveryApi_verify_runtime_api_impl( - &self, - _: &BlockId, - _: ExecutionContext, - args: Option<(&Vec, &Signature, &AuthorityId)>, - _: Vec, - ) -> std::result::Result, client::error::Error> { - if *args.unwrap().1 == Signature("test-signature-1".as_bytes().to_vec()) { - return Ok(NativeOrEncoded::Native(true)); - } - return Ok(NativeOrEncoded::Native(false)); + return Ok(NativeOrEncoded::Native(self.authorities.clone())); } } @@ -605,11 +633,13 @@ mod tests { #[test] fn publish_own_ext_addresses_puts_record_on_dht() { let (_dht_event_tx, dht_event_rx) = channel(1000); - let test_api = Arc::new(TestApi {}); let network: Arc = Arc::new(Default::default()); + let key_store = KeyStore::new(); + let public = key_store.write().sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None).unwrap(); + let test_api = Arc::new(TestApi {authorities: vec![public.into()]}); let mut authority_discovery = - AuthorityDiscovery::new(test_api, network.clone(), dht_event_rx); + AuthorityDiscovery::new(test_api, network.clone(), key_store, dht_event_rx.boxed()); authority_discovery.publish_own_ext_addresses().unwrap(); @@ -620,11 +650,20 @@ mod tests { #[test] fn request_addresses_of_others_triggers_dht_get_query() { let (_dht_event_tx, dht_event_rx) = channel(1000); - let test_api = Arc::new(TestApi {}); + + // Generate authority keys + let authority_1_key_pair = AuthorityPair::from_seed_slice(&[1; 32]).unwrap(); + let authority_2_key_pair = AuthorityPair::from_seed_slice(&[2; 32]).unwrap(); + + let test_api = Arc::new(TestApi { + authorities: vec![authority_1_key_pair.public(), authority_2_key_pair.public()], + }); + let network: Arc = Arc::new(Default::default()); + let key_store = KeyStore::new(); let mut authority_discovery = - AuthorityDiscovery::new(test_api, network.clone(), dht_event_rx); + AuthorityDiscovery::new(test_api, network.clone(), key_store, dht_event_rx.boxed()); authority_discovery.request_addresses_of_others().unwrap(); @@ -637,15 +676,17 @@ mod tests { // Create authority discovery. let (mut dht_event_tx, dht_event_rx) = channel(1000); - let test_api = Arc::new(TestApi {}); + let key_pair = AuthorityPair::from_seed_slice(&[1; 32]).unwrap(); + let test_api = Arc::new(TestApi {authorities: vec![key_pair.public()]}); let network: Arc = Arc::new(Default::default()); + let key_store = KeyStore::new(); let mut authority_discovery = - AuthorityDiscovery::new(test_api, network.clone(), dht_event_rx); + AuthorityDiscovery::new(test_api, network.clone(), key_store, dht_event_rx.boxed()); // Create sample dht event. - let authority_id_1 = hash_authority_id("test-authority-id-1".as_bytes()).unwrap(); + let authority_id_1 = hash_authority_id(key_pair.public().as_ref()).unwrap(); let address_1: libp2p::Multiaddr = "/ip6/2001:db8::".parse().unwrap(); let mut serialized_addresses = vec![]; @@ -655,10 +696,11 @@ mod tests { .encode(&mut serialized_addresses) .unwrap(); + let signature = key_pair.sign(serialized_addresses.as_ref()).encode(); let mut signed_addresses = vec![]; schema::SignedAuthorityAddresses { addresses: serialized_addresses, - signature: "test-signature-1".as_bytes().to_vec(), + signature: signature, } .encode(&mut signed_addresses) .unwrap(); diff --git a/core/primitives/src/crypto.rs b/core/primitives/src/crypto.rs index a452e9ce5b3c6..f5484efed7a38 100644 --- a/core/primitives/src/crypto.rs +++ b/core/primitives/src/crypto.rs @@ -916,6 +916,8 @@ pub mod key_types { pub const AURA: KeyTypeId = KeyTypeId(*b"aura"); /// Key type for ImOnline module, built-in. pub const IM_ONLINE: KeyTypeId = KeyTypeId(*b"imon"); + /// Key type for AuthorityDiscovery module, built-in. + pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi"); /// A key type ID useful for tests. pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 20379d52e768d..9ae29bb2b400d 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -26,6 +26,7 @@ crate-type = ["cdylib", "rlib"] codec = { package = "parity-scale-codec", version = "1.0.6" } serde = { version = "1.0.102", features = [ "derive" ] } futures = "0.1.29" +futures03 = { package = "futures-preview", version = "0.3.0-alpha.19", features = ["compat"] } hex-literal = "0.2.1" jsonrpc-core = "14.0.3" log = "0.4.8" @@ -37,6 +38,7 @@ primitives = { package = "substrate-primitives", path = "../../core/primitives" sr-primitives = { path = "../../core/sr-primitives" } babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives" } grandpa_primitives = { package = "substrate-finality-grandpa-primitives", path = "../../core/finality-grandpa/primitives" } +authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives"} # core dependencies runtime-io = { package = "sr-io", path = "../../core/sr-io" } @@ -47,6 +49,7 @@ transaction_pool = { package = "substrate-transaction-pool", path = "../../core/ network = { package = "substrate-network", path = "../../core/network" } babe = { package = "substrate-consensus-babe", path = "../../core/consensus/babe" } grandpa = { package = "substrate-finality-grandpa", path = "../../core/finality-grandpa" } +authority-discovery = { package = "substrate-authority-discovery", path = "../../core/authority-discovery"} keyring = { package = "substrate-keyring", path = "../../core/keyring" } client_db = { package = "substrate-client-db", path = "../../core/client/db", default-features = false } offchain = { package = "substrate-offchain", path = "../../core/offchain" } @@ -65,6 +68,7 @@ balances = { package = "srml-balances", path = "../../srml/balances" } transaction-payment = { package = "srml-transaction-payment", path = "../../srml/transaction-payment" } support = { package = "srml-support", path = "../../srml/support", default-features = false } im_online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false } +sr-authority-discovery = { package = "srml-authority-discovery", path = "../../srml/authority-discovery", default-features = false } # node-specific dependencies node-runtime = { path = "../runtime" } diff --git a/node/cli/src/chain_spec.rs b/node/cli/src/chain_spec.rs index 04fb41c211009..792dd237ac4dc 100644 --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -20,9 +20,9 @@ use chain_spec::ChainSpecExtension; use primitives::{Pair, Public, crypto::UncheckedInto, sr25519}; use serde::{Serialize, Deserialize}; use node_runtime::{ - BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig, DemocracyConfig, GrandpaConfig, - ImOnlineConfig, IndicesConfig, SessionConfig, SessionKeys, StakerStatus, StakingConfig, - SudoConfig, SystemConfig, TechnicalCommitteeConfig, WASM_BINARY, + 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::*; @@ -32,6 +32,7 @@ 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}; @@ -61,8 +62,13 @@ pub fn flaming_fir_config() -> Result { ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..]) } -fn session_keys(grandpa: GrandpaId, babe: BabeId, im_online: ImOnlineId) -> SessionKeys { - SessionKeys { grandpa, babe, im_online, } +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 { @@ -72,7 +78,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig { // 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)> = vec![( + let initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)> = vec![( // 5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy hex!["9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d12"].into(), // 5EnCiV7wSHeNhjW3FSUwiJNkcc2SBkPLn5Nj93FmbLtBjQUq @@ -83,6 +89,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(), // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(), + // 5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8 + hex!["6e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"].unchecked_into(), ),( // 5ERawXCzCWkjVq3xz1W5KGNtVx2VdefvZ62Bw1FEuZW4Vny2 hex!["68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde78"].into(), @@ -94,6 +102,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(), // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(), + // 5DhLtiaQd1L1LU9jaNeeu9HJkP6eyg3BwXA7iNMzKm7qqruQ + hex!["482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e"].unchecked_into(), ),( // 5DyVtKWPidondEu8iHZgi6Ffv9yrJJ1NDNLom3X9cTDi98qp hex!["547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d65"].into(), @@ -105,6 +115,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(), // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(), + // 5DhKqkHRkndJu8vq7pi2Q5S3DfftWJHGxbEUNH43b46qNspH + hex!["482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a"].unchecked_into(), ),( // 5HYZnKWe5FVZQ33ZRJK1rG3WaLMztxWrrNDb1JRwaHHVWyP9 hex!["f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c2663"].into(), @@ -116,6 +128,8 @@ fn staging_testnet_config_genesis() -> GenesisConfig { hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(), // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(), + // 5C4vDQxA8LTck2xJEy4Yg1hM9qjDt4LvTQaMo4Y8ne43aU6x + hex!["00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378"].unchecked_into(), )]; // generated with secret: subkey inspect "$secret"/fir @@ -164,19 +178,27 @@ pub fn get_account_id_from_seed(seed: &str) -> AccountId where } /// 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) { +pub fn get_authority_keys_from_seed(seed: &str) -> ( + AccountId, + AccountId, + GrandpaId, + BabeId, + ImOnlineId, + AuthorityDiscoveryId, +) { ( get_account_id_from_seed::(&format!("{}//stash", seed)), get_account_id_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), get_from_seed::(seed), + get_from_seed::(seed), ) } /// Helper function to create GenesisConfig for testing pub fn testnet_genesis( - initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId)>, + initial_authorities: Vec<(AccountId, AccountId, GrandpaId, BabeId, ImOnlineId, AuthorityDiscoveryId)>, root_key: AccountId, endowed_accounts: Option>, enable_println: bool, @@ -220,7 +242,7 @@ pub fn testnet_genesis( }), session: Some(SessionConfig { keys: initial_authorities.iter().map(|x| { - (x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone())) + (x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone())) }).collect::>(), }), staking: Some(StakingConfig { @@ -259,6 +281,9 @@ pub fn testnet_genesis( im_online: Some(ImOnlineConfig { keys: vec![], }), + authority_discovery: Some(AuthorityDiscoveryConfig { + keys: vec![], + }), grandpa: Some(GrandpaConfig { authorities: vec![], }), diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 52ad35fe7b315..3afa57b59c1c4 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -112,6 +112,11 @@ 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, @@ -136,7 +141,7 @@ macro_rules! new_full { // 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) = + let (dht_event_tx, dht_event_rx) = mpsc::channel::(10_000); let service = builder.with_network_protocol(|_| Ok(crate::service::NodeProtocol::new()))? @@ -175,6 +180,19 @@ macro_rules! new_full { 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(" 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 diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index 2b1c817d7bbc3..c39edaa29d6fc 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -14,6 +14,7 @@ rustc-hex = { version = "2.0", optional = true } serde = { version = "1.0.102", optional = true } # primitives +authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives", default-features = false } babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false } node-primitives = { path = "../primitives", default-features = false } offchain-primitives = { package = "substrate-offchain-primitives", path = "../../core/offchain/primitives", default-features = false } @@ -44,6 +45,7 @@ executive = { package = "srml-executive", path = "../../srml/executive", default finality-tracker = { package = "srml-finality-tracker", path = "../../srml/finality-tracker", default-features = false } grandpa = { package = "srml-grandpa", path = "../../srml/grandpa", default-features = false } im-online = { package = "srml-im-online", path = "../../srml/im-online", default-features = false } +authority-discovery = { package = "srml-authority-discovery", path = "../../srml/authority-discovery", default-features = false } indices = { package = "srml-indices", path = "../../srml/indices", default-features = false } membership = { package = "srml-membership", path = "../../srml/membership", default-features = false } nicks = { package = "srml-nicks", path = "../../srml/nicks", default-features = false } @@ -71,6 +73,8 @@ runtime_io = { package = "sr-io", path = "../../core/sr-io" } [features] default = ["std"] std = [ + "authority-discovery/std", + "authority-discovery-primitives/std", "authorship/std", "babe-primitives/std", "babe/std", diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index c321693f45680..8870db7265625 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -42,6 +42,7 @@ use primitives::OpaqueMetadata; use grandpa::AuthorityList as GrandpaAuthorityList; use grandpa::fg_primitives; use im_online::sr25519::{AuthorityId as ImOnlineId}; +use authority_discovery_primitives::AuthorityId as AuthorityDiscoveryId; use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use contracts_rpc_runtime_api::ContractExecResult; use system::offchain::TransactionSubmitter; @@ -76,8 +77,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 193, - impl_version: 193, + spec_version: 194, + impl_version: 194, apis: RUNTIME_API_VERSIONS, }; @@ -209,6 +210,7 @@ impl_opaque_keys! { pub grandpa: Grandpa, pub babe: Babe, pub im_online: ImOnline, + pub authority_discovery: AuthorityDiscovery, } } @@ -433,6 +435,8 @@ impl offences::Trait for Runtime { type OnOffenceHandler = Staking; } +impl authority_discovery::Trait for Runtime {} + impl grandpa::Trait for Runtime { type Event = Event; } @@ -521,6 +525,7 @@ construct_runtime!( Contracts: contracts, Sudo: sudo, ImOnline: im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, + AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Offences: offences::{Module, Call, Storage, Event}, RandomnessCollectiveFlip: randomness_collective_flip::{Module, Call, Storage}, Nicks: nicks::{Module, Call, Storage, Event}, @@ -635,6 +640,12 @@ impl_runtime_apis! { } } + impl authority_discovery_primitives::AuthorityDiscoveryApi for Runtime { + fn authorities() -> Vec { + AuthorityDiscovery::authorities() + } + } + impl system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Index { System::account_nonce(account) diff --git a/node/testing/src/genesis.rs b/node/testing/src/genesis.rs index 0b99052f25026..1531ba134892a 100644 --- a/node/testing/src/genesis.rs +++ b/node/testing/src/genesis.rs @@ -89,6 +89,7 @@ pub fn config(support_changes_trie: bool, code: Option<&[u8]>) -> GenesisConfig authorities: vec![], }), im_online: Some(Default::default()), + authority_discovery: Some(Default::default()), democracy: Some(Default::default()), collective_Instance1: Some(Default::default()), collective_Instance2: Some(Default::default()), diff --git a/node/testing/src/keyring.rs b/node/testing/src/keyring.rs index 618c813fb529a..9c4f11fcf3110 100644 --- a/node/testing/src/keyring.rs +++ b/node/testing/src/keyring.rs @@ -59,6 +59,7 @@ pub fn to_session_keys( grandpa: ed25519_keyring.to_owned().public().into(), babe: sr25519_keyring.to_owned().public().into(), im_online: sr25519_keyring.to_owned().public().into(), + authority_discovery: sr25519_keyring.to_owned().public().into(), } } @@ -99,4 +100,3 @@ pub fn sign(xt: CheckedExtrinsic, version: u32, genesis_hash: [u8; 32]) -> Unche }, } } - diff --git a/srml/authority-discovery/Cargo.toml b/srml/authority-discovery/Cargo.toml index 3968ab52aec66..6b896e4729ab4 100644 --- a/srml/authority-discovery/Cargo.toml +++ b/srml/authority-discovery/Cargo.toml @@ -6,24 +6,25 @@ edition = "2018" [dependencies] app-crypto = { package = "substrate-application-crypto", path = "../../core/application-crypto", default-features = false } +authority-discovery-primitives = { package = "substrate-authority-discovery-primitives", path = "../../core/authority-discovery/primitives", default-features = false } codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } primitives = { package = "substrate-primitives", path = "../../core/primitives", default-features = false } rstd = { package = "sr-std", path = "../../core/sr-std", default-features = false } -serde = { version = "1.0.101", optional = true } runtime-io = { package = "sr-io", path = "../../core/sr-io", default-features = false } +serde = { version = "1.0.101", optional = true } session = { package = "srml-session", path = "../session", default-features = false } sr-primitives = { path = "../../core/sr-primitives", default-features = false } support = { package = "srml-support", path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } [dev-dependencies] -babe-primitives = { package = "substrate-consensus-babe-primitives", path = "../../core/consensus/babe/primitives", default-features = false } sr-staking-primitives = { path = "../../core/sr-staking-primitives", default-features = false } [features] default = ["std"] std = [ "app-crypto/std", + "authority-discovery-primitives/std", "codec/std", "primitives/std", "rstd/std", diff --git a/srml/authority-discovery/src/lib.rs b/srml/authority-discovery/src/lib.rs index 8c2951f3acbcc..9bae70a797942 100644 --- a/srml/authority-discovery/src/lib.rs +++ b/srml/authority-discovery/src/lib.rs @@ -17,34 +17,25 @@ //! # Authority discovery module. //! //! This module is used by the `core/authority-discovery` to retrieve the -//! current set of authorities, learn its own authority id as well as sign and -//! verify messages to and from other authorities. -//! -//! ## Dependencies -//! -//! This module depends on an externally defined session key type, specified via -//! `Trait::AuthorityId` in the respective node runtime implementation. +//! current set of authorities. // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use app_crypto::RuntimeAppPublic; -use codec::FullCodec; use rstd::prelude::*; use support::{decl_module, decl_storage}; +use authority_discovery_primitives::AuthorityId; /// The module's config trait. -pub trait Trait: system::Trait + session::Trait { - type AuthorityId: RuntimeAppPublic + Default + FullCodec + PartialEq; -} +pub trait Trait: system::Trait + session::Trait {} decl_storage! { trait Store for Module as AuthorityDiscovery { - /// The current set of keys that may issue a heartbeat. - Keys get(fn keys): Vec; + /// Keys of the current authority set. + Keys get(fn keys): Vec; } add_extra_genesis { - config(keys): Vec; + config(keys): Vec; build(|config| Module::::initialize_keys(&config.keys)) } } @@ -55,79 +46,42 @@ decl_module! { } impl Module { - /// Returns own authority identifier iff it is part of the current authority - /// set, otherwise this function returns None. The restriction might be - /// softened in the future in case a consumer needs to learn own authority - /// identifier. - fn authority_id() -> Option { - let authorities = Keys::::get(); - - let local_keys = T::AuthorityId::all(); - - authorities.into_iter().find_map(|authority| { - if local_keys.contains(&authority) { - Some(authority) - } else { - None - } - }) - } - /// Retrieve authority identifiers of the current authority set. - pub fn authorities() -> Vec { - Keys::::get() - } - - /// Sign the given payload with the private key corresponding to the given authority id. - pub fn sign( - payload: &Vec, - ) -> Option<( - <::AuthorityId as RuntimeAppPublic>::Signature, - T::AuthorityId, - )> { - let authority_id = Module::::authority_id()?; - authority_id.sign(payload).map(|s| (s, authority_id)) - } - - /// Verify the given signature for the given payload with the given - /// authority identifier. - pub fn verify( - payload: &Vec, - signature: <::AuthorityId as RuntimeAppPublic>::Signature, - authority_id: T::AuthorityId, - ) -> bool { - authority_id.verify(payload, &signature) + pub fn authorities() -> Vec { + Keys::get() } - fn initialize_keys(keys: &[T::AuthorityId]) { + fn initialize_keys(keys: &[AuthorityId]) { if !keys.is_empty() { - assert!(Keys::::get().is_empty(), "Keys are already initialized!"); - Keys::::put(keys); + assert!(Keys::get().is_empty(), "Keys are already initialized!"); + Keys::put(keys); } } } impl sr_primitives::BoundToRuntimeAppPublic for Module { - type Public = T::AuthorityId; + type Public = AuthorityId; } impl session::OneSessionHandler for Module { - type Key = T::AuthorityId; + type Key = AuthorityId; - fn on_genesis_session<'a, I: 'a>(validators: I) + fn on_genesis_session<'a, I: 'a>(authorities: I) where I: Iterator, { - let keys = validators.map(|x| x.1).collect::>(); + let keys = authorities.map(|x| x.1).collect::>(); Self::initialize_keys(&keys); } - fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, next_validators: I) + fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I) where I: Iterator, { // Remember who the authorities are for the new session. - Keys::::put(next_validators.map(|x| x.1).collect::>()); + if changed { + Keys::put(validators.map(|x| x.1).collect::>()); + } } fn on_disabled(_i: usize) { @@ -138,8 +92,9 @@ impl session::OneSessionHandler for Module { #[cfg(test)] mod tests { use super::*; + use authority_discovery_primitives::{AuthorityPair}; use app_crypto::Pair; - use primitives::{testing::KeyStore, crypto::key_types, sr25519, H256, traits::KeystoreExt}; + use primitives::{crypto::key_types, H256}; use runtime_io::TestExternalities; use sr_primitives::{ testing::{Header, UintAuthorityId}, traits::{ConvertInto, IdentityLookup, OpaqueKeys}, @@ -152,11 +107,7 @@ mod tests { #[derive(Clone, Eq, PartialEq)] pub struct Test; - impl Trait for Test { - type AuthorityId = babe_primitives::AuthorityId; - } - - type AuthorityId = babe_primitives::AuthorityId; + impl Trait for Test {} pub struct TestOnSessionEnding; impl session::OnSessionEnding for TestOnSessionEnding { @@ -237,126 +188,79 @@ mod tests { } #[test] - fn authority_id_fn_returns_intersection_of_current_authorities_and_keys_in_key_store() { - // Create keystore and generate key. - let key_store = KeyStore::new(); - key_store - .write() - .sr25519_generate_new(key_types::BABE, None) - .expect("Generates key."); - - // Retrieve key to later check if we got the right one. - let public_key = key_store - .read() - .sr25519_public_keys(key_types::BABE) - .pop() - .unwrap(); - let authority_id = AuthorityId::from(public_key); - - // Build genesis. - let mut t = system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - GenesisConfig:: { - keys: vec![authority_id.clone()], - } - .assimilate_storage(&mut t) - .unwrap(); - - // Create externalities. - let mut externalities = TestExternalities::new(t); - externalities.register_extension(KeystoreExt(key_store)); - - externalities.execute_with(|| { - assert_eq!( - authority_id, - AuthorityDiscovery::authority_id().expect("Retrieving public key.") - ); - }); - } - - #[test] - fn authority_id_fn_does_not_return_key_outside_current_authority_set() { - // Create keystore and generate key. - let key_store = KeyStore::new(); - key_store - .write() - .sr25519_generate_new(key_types::BABE, None) - .expect("Generates key."); - - // Build genesis. - let mut t = system::GenesisConfig::default() - .build_storage::() - .unwrap(); + fn authorities_returns_current_authority_set() { + // The whole authority discovery module ignores account ids, but we still need it for + // `session::OneSessionHandler::on_new_session`, thus its safe to use the same value everywhere. + let account_id = AuthorityPair::from_seed_slice(vec![10; 32].as_ref()).unwrap().public(); - // Generate random authority set. - let keys = vec![(); 5] - .iter() - .map(|_x| sr25519::Pair::generate_with_phrase(None).0.public()) + let first_authorities: Vec = vec![0, 1].into_iter() + .map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public()) .map(AuthorityId::from) .collect(); - GenesisConfig:: { keys: keys } - .assimilate_storage(&mut t) - .unwrap(); - - // Create externalities. - let mut externalities = TestExternalities::new(t); - externalities.register_extension(KeystoreExt(key_store)); + // Needed for `session::OneSessionHandler::on_new_session`. + let first_authorities_and_account_ids: Vec<(&AuthorityId, AuthorityId)> = first_authorities.clone() + .into_iter() + .map(|id| (&account_id, id)) + .collect(); - externalities.execute_with(|| { - assert_eq!(None, AuthorityDiscovery::authority_id()); - }); - } + let second_authorities: Vec = vec![2, 3].into_iter() + .map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public()) + .map(AuthorityId::from) + .collect(); - #[test] - fn sign_and_verify_workflow() { - // Create keystore and generate key. - let key_store = KeyStore::new(); - key_store - .write() - .sr25519_generate_new(key_types::BABE, None) - .expect("Generates key."); - - // Retrieve key to later check if we got the right one. - let public_key = key_store - .read() - .sr25519_public_keys(key_types::BABE) - .pop() - .unwrap(); - let authority_id = AuthorityId::from(public_key); + // Needed for `session::OneSessionHandler::on_new_session`. + let second_authorities_and_account_ids: Vec<(&AuthorityId, AuthorityId)> = second_authorities.clone() + .into_iter() + .map(|id| (&account_id, id)) + .collect(); // Build genesis. let mut t = system::GenesisConfig::default() .build_storage::() .unwrap(); - GenesisConfig:: { - keys: vec![authority_id.clone()], + GenesisConfig { + keys: vec![], } - .assimilate_storage(&mut t) + .assimilate_storage::(&mut t) .unwrap(); // Create externalities. let mut externalities = TestExternalities::new(t); - externalities.register_extension(KeystoreExt(key_store)); externalities.execute_with(|| { - let payload = String::from("test payload").into_bytes(); - let (sig, authority_id) = AuthorityDiscovery::sign(&payload).expect("signature"); - - assert!(AuthorityDiscovery::verify( - &payload, - sig.clone(), - authority_id.clone(), - )); - - assert!(!AuthorityDiscovery::verify( - &String::from("other payload").into_bytes(), - sig, - authority_id, - )) + use session::OneSessionHandler; + + AuthorityDiscovery::on_genesis_session( + first_authorities.iter().map(|id| (id, id.clone())) + ); + assert_eq!( + first_authorities, + AuthorityDiscovery::authorities() + ); + + // When `changed` set to false, the authority set should not be updated. + AuthorityDiscovery::on_new_session( + false, + second_authorities_and_account_ids.clone().into_iter(), + vec![].into_iter(), + ); + assert_eq!( + first_authorities, + AuthorityDiscovery::authorities() + ); + + // When `changed` set to true, the authority set should be updated. + AuthorityDiscovery::on_new_session( + true, + second_authorities_and_account_ids.into_iter(), + vec![].into_iter(), + ); + assert_eq!( + second_authorities, + AuthorityDiscovery::authorities() + ); }); } } diff --git a/test-utils/chain-spec-builder/src/main.rs b/test-utils/chain-spec-builder/src/main.rs index d46f4b7f0bc98..b277987e1ea5e 100644 --- a/test-utils/chain-spec-builder/src/main.rs +++ b/test-utils/chain-spec-builder/src/main.rs @@ -141,7 +141,7 @@ fn generate_authority_keys_and_store( None, ).map_err(|err| err.to_string())?; - let (_, _, grandpa, babe, im_online) = + let (_, _, grandpa, babe, im_online, authority_discovery) = chain_spec::get_authority_keys_from_seed(seed); let insert_key = |key_type, public| { @@ -166,6 +166,11 @@ fn generate_authority_keys_and_store( primitives::crypto::key_types::IM_ONLINE, im_online.as_slice(), )?; + + insert_key( + primitives::crypto::key_types::AUTHORITY_DISCOVERY, + authority_discovery.as_slice(), + )?; } Ok(())