client/authority-discovery: Allow to be run by sentry node (#5568)

* client/authority-discovery: Allow to be run by sentry node

When run as a sentry node, the authority discovery module does not
publish any addresses to the dht, but still discovers validators and
sentry nodes of validators.

* client/authority-discovery/src/lib: Wrap lines at 100 characters

* client/authority-discovery: Remove TODO and unused import

* client/authority-discovery: Pass role to new unit tests

* client/authority-discovery: Apply suggestions

Co-Authored-By: André Silva <123550+andresilva@users.noreply.github.com>

* bin/node/cli/src/service: Use expressions instead of statements

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
Max Inden
2020-04-16 19:11:26 +02:00
committed by GitHub
parent 95ee37d242
commit d742e88e79
4 changed files with 77 additions and 37 deletions
@@ -82,13 +82,12 @@ services:
- "--chain=local" - "--chain=local"
- "--port" - "--port"
- "30333" - "30333"
- "--charlie"
- "--sentry" - "--sentry"
- "/dns4/validator-a/tcp/30333/p2p/QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR" - "/dns4/validator-a/tcp/30333/p2p/QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR"
- "--reserved-nodes"
- "/dns4/validator-a/tcp/30333/p2p/QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR"
- "--bootnodes" - "--bootnodes"
- "/dns4/validator-b/tcp/30333/p2p/QmSVnNf9HwVMT1Y4cK1P6aoJcEZjmoTXpjKBmAABLMnZEk" - "/dns4/validator-b/tcp/30333/p2p/QmSVnNf9HwVMT1Y4cK1P6aoJcEZjmoTXpjKBmAABLMnZEk"
- "--reserved-nodes"
- "/dns4/validator-a/tcp/30333/p2p/QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR"
- "--no-telemetry" - "--no-telemetry"
- "--rpc-cors" - "--rpc-cors"
- "all" - "all"
@@ -97,7 +96,6 @@ services:
- "--unsafe-rpc-external" - "--unsafe-rpc-external"
- "--log" - "--log"
- "sub-authority-discovery=trace" - "sub-authority-discovery=trace"
- "--sentry"
- "--prometheus-external" - "--prometheus-external"
validator-b: validator-b:
+20 -3
View File
@@ -146,7 +146,7 @@ macro_rules! new_full {
($with_startup_data)(&block_import, &babe_link); ($with_startup_data)(&block_import, &babe_link);
if let sc_service::config::Role::Authority { sentry_nodes } = &role { if let sc_service::config::Role::Authority { .. } = &role {
let proposer = sc_basic_authorship::ProposerFactory::new( let proposer = sc_basic_authorship::ProposerFactory::new(
service.client(), service.client(),
service.transaction_pool() service.transaction_pool()
@@ -174,6 +174,23 @@ macro_rules! new_full {
let babe = sc_consensus_babe::start_babe(babe_config)?; let babe = sc_consensus_babe::start_babe(babe_config)?;
service.spawn_essential_task("babe-proposer", babe); service.spawn_essential_task("babe-proposer", babe);
}
// Spawn authority discovery module.
if matches!(role, sc_service::config::Role::Authority{..} | sc_service::config::Role::Sentry {..}) {
let (sentries, authority_discovery_role) = match role {
sc_service::config::Role::Authority { ref sentry_nodes } => (
sentry_nodes.clone(),
sc_authority_discovery::Role::Authority (
service.keystore(),
),
),
sc_service::config::Role::Sentry {..} => (
vec![],
sc_authority_discovery::Role::Sentry,
),
_ => unreachable!("Due to outer matches! constraint; qed.")
};
let network = service.network(); let network = service.network();
let dht_event_stream = network.event_stream("authority-discovery").filter_map(|e| async move { match e { let dht_event_stream = network.event_stream("authority-discovery").filter_map(|e| async move { match e {
@@ -183,9 +200,9 @@ macro_rules! new_full {
let authority_discovery = sc_authority_discovery::AuthorityDiscovery::new( let authority_discovery = sc_authority_discovery::AuthorityDiscovery::new(
service.client(), service.client(),
network, network,
sentry_nodes.clone(), sentries,
service.keystore(),
dht_event_stream, dht_event_stream,
authority_discovery_role,
service.prometheus_registry(), service.prometheus_registry(),
); );
+49 -24
View File
@@ -25,13 +25,11 @@
//! //!
//! 1. **Makes itself discoverable** //! 1. **Makes itself discoverable**
//! //!
//! 1. Retrieves its external addresses. //! 1. Retrieves its external addresses (including peer id) or the ones of its sentry nodes.
//! //!
//! 2. Adds its network peer id to the addresses. //! 2. Signs the above.
//! //!
//! 3. Signs the above. //! 3. Puts the signature and the addresses on the libp2p Kademlia DHT.
//!
//! 4. Puts the signature and the addresses on the libp2p Kademlia DHT.
//! //!
//! //!
//! 2. **Discovers other authorities** //! 2. **Discovers other authorities**
@@ -43,6 +41,12 @@
//! 3. Validates the signatures of the retrieved key value pairs. //! 3. Validates the signatures of the retrieved key value pairs.
//! //!
//! 4. Adds the retrieved external addresses as priority nodes to the peerset. //! 4. Adds the retrieved external addresses as priority nodes to the peerset.
//!
//! When run as a sentry node, the authority discovery module does not
//! publish any addresses to the DHT but still discovers validators and
//! sentry nodes of validators, i.e. only step 2 (Discovers other authorities)
//! is executed.
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::TryInto; use std::convert::TryInto;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -62,7 +66,7 @@ use prost::Message;
use sc_client_api::blockchain::HeaderBackend; use sc_client_api::blockchain::HeaderBackend;
use sc_network::{Multiaddr, config::MultiaddrWithPeerId, DhtEvent, ExHashT, NetworkStateInfo}; use sc_network::{Multiaddr, config::MultiaddrWithPeerId, DhtEvent, ExHashT, NetworkStateInfo};
use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId, AuthoritySignature, AuthorityPair}; use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId, AuthoritySignature, AuthorityPair};
use sp_core::crypto::{key_types, CryptoTypePublicPair, Pair}; use sp_core::crypto::{key_types, Pair};
use sp_core::traits::BareCryptoStorePtr; use sp_core::traits::BareCryptoStorePtr;
use sp_runtime::{traits::Block as BlockT, generic::BlockId}; use sp_runtime::{traits::Block as BlockT, generic::BlockId};
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
@@ -89,6 +93,17 @@ const LIBP2P_KADEMLIA_BOOTSTRAP_TIME: Duration = Duration::from_secs(30);
/// discovery module. /// discovery module.
const AUTHORITIES_PRIORITY_GROUP_NAME: &'static str = "authorities"; const AUTHORITIES_PRIORITY_GROUP_NAME: &'static str = "authorities";
/// Role an authority discovery module can run as.
pub enum Role {
/// Actual authority as well as a reference to its key store.
Authority(BareCryptoStorePtr),
/// Sentry node that guards an authority.
///
/// No reference to its key store needed, as sentry nodes don't have an identity to sign
/// addresses with in the first place.
Sentry,
}
/// An `AuthorityDiscovery` makes a given authority discoverable and discovers other authorities. /// An `AuthorityDiscovery` makes a given authority discoverable and discovers other authorities.
pub struct AuthorityDiscovery<Client, Network, Block> pub struct AuthorityDiscovery<Client, Network, Block>
where where
@@ -111,8 +126,6 @@ where
/// Channel we receive Dht events on. /// Channel we receive Dht events on.
dht_event_rx: Pin<Box<dyn Stream<Item = DhtEvent> + Send>>, dht_event_rx: Pin<Box<dyn Stream<Item = DhtEvent> + Send>>,
key_store: BareCryptoStorePtr,
/// Interval to be proactive, publishing own addresses. /// Interval to be proactive, publishing own addresses.
publish_interval: Interval, publish_interval: Interval,
/// Interval on which to query for addresses of other authorities. /// Interval on which to query for addresses of other authorities.
@@ -122,6 +135,8 @@ where
metrics: Option<Metrics>, metrics: Option<Metrics>,
role: Role,
phantom: PhantomData<Block>, phantom: PhantomData<Block>,
} }
@@ -142,8 +157,8 @@ where
client: Arc<Client>, client: Arc<Client>,
network: Arc<Network>, network: Arc<Network>,
sentry_nodes: Vec<MultiaddrWithPeerId>, sentry_nodes: Vec<MultiaddrWithPeerId>,
key_store: BareCryptoStorePtr,
dht_event_rx: Pin<Box<dyn Stream<Item = DhtEvent> + Send>>, dht_event_rx: Pin<Box<dyn Stream<Item = DhtEvent> + Send>>,
role: Role,
prometheus_registry: Option<prometheus_endpoint::Registry>, prometheus_registry: Option<prometheus_endpoint::Registry>,
) -> Self { ) -> Self {
// Kademlia's default time-to-live for Dht records is 36h, republishing records every 24h. // Kademlia's default time-to-live for Dht records is 36h, republishing records every 24h.
@@ -189,10 +204,10 @@ where
network, network,
sentry_nodes, sentry_nodes,
dht_event_rx, dht_event_rx,
key_store,
publish_interval, publish_interval,
query_interval, query_interval,
addr_cache, addr_cache,
role,
metrics, metrics,
phantom: PhantomData, phantom: PhantomData,
} }
@@ -200,6 +215,14 @@ where
/// Publish either our own or if specified the public addresses of our sentry nodes. /// Publish either our own or if specified the public addresses of our sentry nodes.
fn publish_ext_addresses(&mut self) -> Result<()> { fn publish_ext_addresses(&mut self) -> Result<()> {
let key_store = match &self.role {
Role::Authority(key_store) => key_store,
// Only authority nodes can put addresses (their own or the ones of their sentry nodes)
// on the Dht. Sentry nodes don't have a known identity to authenticate such addresses,
// thus `publish_ext_addresses` becomes a no-op.
Role::Sentry => return Ok(()),
};
if let Some(metrics) = &self.metrics { if let Some(metrics) = &self.metrics {
metrics.publish.inc() metrics.publish.inc()
} }
@@ -226,13 +249,12 @@ where
.encode(&mut serialized_addresses) .encode(&mut serialized_addresses)
.map_err(Error::EncodingProto)?; .map_err(Error::EncodingProto)?;
let keys: Vec<CryptoTypePublicPair> = self.get_own_public_keys_within_authority_set()? let keys = AuthorityDiscovery::get_own_public_keys_within_authority_set(
.into_iter() &key_store,
.map(Into::into) &self.client,
.collect(); )?.into_iter().map(Into::into).collect::<Vec<_>>();
let signatures = self.key_store let signatures = key_store.read()
.read()
.sign_with_all( .sign_with_all(
key_types::AUTHORITY_DISCOVERY, key_types::AUTHORITY_DISCOVERY,
keys.clone(), keys.clone(),
@@ -424,17 +446,17 @@ where
// one for the upcoming session. In addition it could be participating in the current authority // 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 // 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. // keys, but only the ones intersecting with the current authority set.
fn get_own_public_keys_within_authority_set(&mut self) -> Result<HashSet<AuthorityId>> { fn get_own_public_keys_within_authority_set(
let local_pub_keys = self.key_store key_store: &BareCryptoStorePtr,
.read() client: &Client,
) -> Result<HashSet<AuthorityId>> {
let local_pub_keys = key_store.read()
.sr25519_public_keys(key_types::AUTHORITY_DISCOVERY) .sr25519_public_keys(key_types::AUTHORITY_DISCOVERY)
.into_iter() .into_iter()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
let id = BlockId::hash(self.client.info().best_hash); let id = BlockId::hash(client.info().best_hash);
let current_authorities = self let current_authorities = client.runtime_api()
.client
.runtime_api()
.authorities(&id) .authorities(&id)
.map_err(Error::CallingRuntime)? .map_err(Error::CallingRuntime)?
.into_iter() .into_iter()
@@ -458,7 +480,10 @@ where
"Applying priority group {:?} to peerset.", addresses, "Applying priority group {:?} to peerset.", addresses,
); );
self.network self.network
.set_priority_group(AUTHORITIES_PRIORITY_GROUP_NAME.to_string(), addresses.into_iter().collect()) .set_priority_group(
AUTHORITIES_PRIORITY_GROUP_NAME.to_string(),
addresses.into_iter().collect(),
)
.map_err(Error::SettingPeersetPriorityGroup)?; .map_err(Error::SettingPeersetPriorityGroup)?;
Ok(()) Ok(())
@@ -216,8 +216,8 @@ fn new_registers_metrics() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
Some(registry.clone()), Some(registry.clone()),
); );
@@ -241,8 +241,8 @@ fn publish_ext_addresses_puts_record_on_dht() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
None, None,
); );
@@ -272,8 +272,8 @@ fn request_addresses_of_others_triggers_dht_get_query() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
None, None,
); );
@@ -301,8 +301,8 @@ fn handle_dht_events_with_value_found_should_call_set_priority_group() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
None, None,
); );
@@ -366,8 +366,8 @@ fn terminate_when_event_stream_terminates() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
None, None,
); );
@@ -407,8 +407,8 @@ fn dont_stop_polling_when_error_is_returned() {
test_api, test_api,
network.clone(), network.clone(),
vec![], vec![],
key_store,
dht_event_rx.boxed(), dht_event_rx.boxed(),
Role::Authority(key_store),
None, None,
); );