diff --git a/Cargo.lock b/Cargo.lock index 70b3581bb9045..925c428e2362a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4840,6 +4840,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-node-authorization" +version = "2.0.0-rc6" +dependencies = [ + "frame-support", + "frame-system", + "hex-literal", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-offences" version = "2.0.0-rc6" diff --git a/Cargo.toml b/Cargo.toml index 7589e8d774124..534b71357cc76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ members = [ "frame/metadata", "frame/multisig", "frame/nicks", + "frame/node-authorization", "frame/offences", "frame/proxy", "frame/randomness-collective-flip", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9595ef424d8c1..19963ef6cc9f1 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -109,7 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 257, + spec_version: 258, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 1f2e98c281b24..f02edafdea9fc 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -889,6 +889,12 @@ impl NetworkService { Ok(()) } + /// Sets reserved peers to the new peers + pub fn set_reserved_peers(&self, peer_ids: HashSet, reserved_only: bool) { + self.peerset.set_reserved_peers(peer_ids); + self.peerset.set_reserved_only(reserved_only); + } + /// Configure an explicit fork sync request. /// Note that this function should not be used for recent blocks. /// Sync should be able to download all the recent forks normally. diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs index 6f28dd036a3cc..ff2644396f535 100644 --- a/client/peerset/src/lib.rs +++ b/client/peerset/src/lib.rs @@ -45,6 +45,7 @@ const FORGET_AFTER: Duration = Duration::from_secs(3600); enum Action { AddReservedPeer(PeerId), RemoveReservedPeer(PeerId), + SetReservedPeers(HashSet), SetReservedOnly(bool), ReportPeer(PeerId, ReputationChange), SetPriorityGroup(String, HashSet), @@ -98,6 +99,11 @@ impl PeersetHandle { let _ = self.tx.unbounded_send(Action::RemoveReservedPeer(peer_id)); } + /// Set reserved peers to the new peers. + pub fn set_reserved_peers(&self, peer_ids: HashSet) { + let _ = self.tx.unbounded_send(Action::SetReservedPeers(peer_ids)); + } + /// Sets whether or not the peerset only has connections . pub fn set_reserved_only(&self, reserved: bool) { let _ = self.tx.unbounded_send(Action::SetReservedOnly(reserved)); @@ -247,6 +253,10 @@ impl Peerset { self.on_remove_from_priority_group(RESERVED_NODES, peer_id); } + fn on_set_reserved_peers(&mut self, peer_ids: HashSet) { + self.on_set_priority_group(RESERVED_NODES, peer_ids); + } + fn on_set_reserved_only(&mut self, reserved_only: bool) { self.reserved_only = reserved_only; @@ -655,6 +665,8 @@ impl Stream for Peerset { self.on_add_reserved_peer(peer_id), Action::RemoveReservedPeer(peer_id) => self.on_remove_reserved_peer(peer_id), + Action::SetReservedPeers(peer_ids) => + self.on_set_reserved_peers(peer_ids), Action::SetReservedOnly(reserved) => self.on_set_reserved_only(reserved), Action::ReportPeer(peer_id, score_diff) => diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 8ad95511f77d3..1a9b78ecb2dab 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -56,7 +56,7 @@ use sp_core::traits::{CodeExecutor, SpawnNamed}; use sp_runtime::BuildStorage; use sc_client_api::{ BlockBackend, BlockchainEvents, - backend::StorageProvider, + backend::{StorageProvider, Backend as BackendT}, proof_provider::ProofProvider, execution_extensions::ExecutionExtensions }; @@ -815,7 +815,7 @@ pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> { } /// Build the network service, the network status sinks and an RPC sender. -pub fn build_network( +pub fn build_network( params: BuildNetworkParams ) -> Result< ( @@ -828,9 +828,10 @@ pub fn build_network( > where TBl: BlockT, + TBE: BackendT + 'static, TCl: ProvideRuntimeApi + HeaderMetadata + Chain + BlockBackend + BlockIdTo + ProofProvider + - HeaderBackend + BlockchainEvents + 'static, + HeaderBackend + BlockchainEvents + StorageProvider + 'static, TExPool: MaintainedTransactionPool::Hash> + 'static, TImpQu: ImportQueue + 'static, { diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index d19b9f5ea247d..9d41038c7b694 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -36,19 +36,28 @@ mod task_manager; use std::{io, pin::Pin}; use std::net::SocketAddr; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::time::Duration; use std::task::Poll; +use sc_network::config::identity::{ + ed25519::PublicKey as Ed25519PublicKey, + PublicKey +}; use parking_lot::Mutex; use futures::{Future, FutureExt, Stream, StreamExt, stream, compat::*}; use sc_network::{NetworkStatus, network_state::NetworkState, PeerId}; use log::{warn, debug, error}; -use codec::{Encode, Decode}; +use codec::{Encode, Decode, DecodeAll}; use sp_runtime::generic::BlockId; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use parity_util_mem::MallocSizeOf; use sp_utils::{status_sinks, mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}}; +use sp_core::storage::{StorageKey, well_known_keys}; +use sp_core::NodePublicKey; +use sc_client_api::backend::{Backend as BackendT, StorageProvider}; +use sc_client_api::blockchain::HeaderBackend; +use sc_client_api::BlockchainEvents; pub use self::error::Error; pub use self::builder::{ @@ -80,7 +89,6 @@ pub use sc_tracing::TracingReceiver; pub use task_manager::SpawnTaskHandle; pub use task_manager::TaskManager; pub use sp_consensus::import_queue::ImportQueue; -use sc_client_api::BlockchainEvents; pub use sc_keystore::KeyStorePtr as KeyStore; const DEFAULT_PROTOCOL_ID: &str = "sup"; @@ -186,7 +194,8 @@ pub struct PartialComponents, + BE: BackendT, + C: BlockchainEvents + StorageProvider + HeaderBackend, H: sc_network::ExHashT > ( role: Role, @@ -197,6 +206,8 @@ async fn build_network_future< should_have_peers: bool, announce_imported_blocks: bool, ) { + check_node_allow_list(&network, &client); + let mut imported_blocks_stream = client.import_notification_stream().fuse(); // Stream of finalized blocks reported by the client. @@ -239,6 +250,8 @@ async fn build_network_future< notification.header.number().clone(), ); } + + check_node_allow_list(&network, &client); } // List of blocks that the client has finalized. @@ -338,6 +351,42 @@ async fn build_network_future< } } +/// Set storage `NODE_ALLOW_LIST` means it's a permissioned network, +/// then only connect to these well known peers. +fn check_node_allow_list< + B: BlockT, + BE: BackendT, + C: BlockchainEvents + StorageProvider + HeaderBackend, + H: sc_network::ExHashT +> ( + network: &sc_network::NetworkWorker, + client: &Arc, +) { + let id = BlockId::hash(client.info().best_hash); + let allow_list_storage = client.storage(&id, &StorageKey(well_known_keys::NODE_ALLOW_LIST.to_vec())); + if let Ok(Some(raw_allow_list)) = allow_list_storage { + let node_allow_list = Vec::::decode_all(&mut &raw_allow_list.0[..]); + + if let Ok(node_allow_list) = node_allow_list { + let mut peer_ids: HashSet = node_allow_list.iter() + .filter_map(|node_public_key| { + match node_public_key { + NodePublicKey::Ed25519(pubkey) => Ed25519PublicKey::decode(&pubkey.0).ok() + } + }) + .map(|pubkey| PublicKey::Ed25519(pubkey).into_peer_id()) + .collect(); + peer_ids.remove(network.local_peer_id()); + + // Set only reserved peers are allowed to connect. + network.service().set_reserved_peers(peer_ids, true); + } + } else { + // Note that the situation where the storage entry previously existed but no longer + // does isn't handled at the moment. + } +} + #[cfg(not(target_os = "unknown"))] // Wrapper for HTTP and WS servers that makes sure they are properly shut down. mod waiting { @@ -488,9 +537,9 @@ where impl sc_network::config::TransactionPool for TransactionPoolAdapter where + B: BlockT, C: sc_network::config::Client + Send + Sync, Pool: 'static + TransactionPool, - B: BlockT, H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, E: 'static + IntoPoolError + From, { diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml new file mode 100644 index 0000000000000..2e8863c4b9c66 --- /dev/null +++ b/frame/node-authorization/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-node-authorization" +version = "2.0.0-rc6" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for node authorization" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } +frame-support = { version = "2.0.0-rc6", default-features = false, path = "../support" } +frame-system = { version = "2.0.0-rc6", default-features = false, path = "../system" } +sp-core = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/core" } +sp-io = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "2.0.0-rc6", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +hex-literal = "0.2.1" + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/frame/node-authorization/src/lib.rs b/frame/node-authorization/src/lib.rs new file mode 100644 index 0000000000000..f9fc577ae0c49 --- /dev/null +++ b/frame/node-authorization/src/lib.rs @@ -0,0 +1,458 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Node authorization pallet +//! +//! This pallet manages a configurable set of nodes for a permissioned blockchain. +//! The node set is stored under [`well_known_keys::NODE_ALLOW_LIST`]. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; +use sp_core::{NodePublicKey, storage::well_known_keys}; +use frame_support::{ + decl_module, decl_storage, decl_event, decl_error, + storage, ensure, + weights::{DispatchClass, Weight}, + traits::{Get, EnsureOrigin}, +}; +use codec::Encode; + +pub trait WeightInfo { + fn add_node() -> Weight; + fn remove_node() -> Weight; + fn swap_node() -> Weight; + fn reset_nodes() -> Weight; +} + +impl WeightInfo for () { + fn add_node() -> Weight { 50_000_000 } + fn remove_node() -> Weight { 50_000_000 } + fn swap_node() -> Weight { 50_000_000 } + fn reset_nodes() -> Weight { 50_000_000 } +} + +pub trait Trait: frame_system::Trait { + /// The event type of this module. + type Event: From + Into<::Event>; + + /// The maximum number of authorized nodes that are allowed to set + type MaxAuthorizedNodes: Get; + + /// The origin which can add a authorized node. + type AddOrigin: EnsureOrigin; + + /// The origin which can remove a authorized node. + type RemoveOrigin: EnsureOrigin; + + /// The origin which can swap the authorized nodes. + type SwapOrigin: EnsureOrigin; + + /// The origin which can reset the authorized nodes. + type ResetOrigin: EnsureOrigin; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; +} + +decl_storage! { + trait Store for Module as NodeAuthorization {} + add_extra_genesis { + config(nodes): Vec; + build(|config: &GenesisConfig| { + sp_io::storage::set(well_known_keys::NODE_ALLOW_LIST, &config.nodes.encode()); + }); + } +} + +decl_event!( + pub enum Event { + /// The given node was added. + NodeAdded(NodePublicKey), + /// The given node was removed. + NodeRemoved(NodePublicKey), + /// Two given nodes were swapped; first item is removed, the latter is added. + NodeSwapped(NodePublicKey, NodePublicKey), + /// The given nodes were reset. + NodesReset(Vec), + } +); + +decl_error! { + /// Error for the node authorization module. + pub enum Error for Module { + /// Too many authorized nodes. + TooManyNodes, + /// The node is already joined in the list. + AlreadyJoined, + /// The node doesn't exist in the list. + NotExist, + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + /// The maximum number of authorized nodes + const MaxAuthorizedNodes: u32 = T::MaxAuthorizedNodes::get(); + + type Error = Error; + + fn deposit_event() = default; + + /// Add a node to the allow list. + /// + /// May only be called from `T::AddOrigin`. + /// + /// - `node_public_key`: identifier of the node, it's likely the public key of ed25519 keypair. + #[weight = (T::WeightInfo::add_node(), DispatchClass::Operational)] + pub fn add_node(origin, node_public_key: NodePublicKey) { + T::AddOrigin::ensure_origin(origin)?; + + let mut nodes = Self::get_allow_list(); + ensure!(nodes.len() < T::MaxAuthorizedNodes::get() as usize, Error::::TooManyNodes); + + let location = nodes.binary_search(&node_public_key).err().ok_or(Error::::AlreadyJoined)?; + nodes.insert(location, node_public_key.clone()); + Self::put_allow_list(&nodes); + + Self::deposit_event(Event::NodeAdded(node_public_key)); + } + + /// Remove a node from the allow list. + /// + /// May only be called from `T::RemoveOrigin`. + /// + /// - `node_public_key`: identifier of the node, it's likely the public key of ed25519 keypair. + #[weight = (T::WeightInfo::remove_node(), DispatchClass::Operational)] + pub fn remove_node(origin, node_public_key: NodePublicKey) { + T::RemoveOrigin::ensure_origin(origin)?; + + let mut nodes = Self::get_allow_list(); + + let location = nodes.binary_search(&node_public_key).ok().ok_or(Error::::NotExist)?; + nodes.remove(location); + Self::put_allow_list(&nodes); + + Self::deposit_event(Event::NodeRemoved(node_public_key)); + } + + /// Swap two nodes. + /// + /// May only be called from `T::SwapOrigin`. + /// + /// - `remove`: the node which will be moved out from the list, it's likely the public key + /// of ed25519 keypair. + /// - `add`: the node which will be put in the list, it's likely the public key of ed25519 + /// keypair. + #[weight = (T::WeightInfo::swap_node(), DispatchClass::Operational)] + pub fn swap_node(origin, remove: NodePublicKey, add: NodePublicKey) { + T::SwapOrigin::ensure_origin(origin)?; + + if remove == add { return Ok(()) } + + let mut nodes = Self::get_allow_list(); + let remove_location = nodes.binary_search(&remove).ok().ok_or(Error::::NotExist)?; + nodes.remove(remove_location); + let add_location = nodes.binary_search(&add).err().ok_or(Error::::AlreadyJoined)?; + nodes.insert(add_location, add.clone()); + Self::put_allow_list(&nodes); + + Self::deposit_event(Event::NodeSwapped(remove, add)); + } + + /// Reset all the authorized nodes in the list. + /// + /// May only be called from `T::ResetOrigin`. + /// + /// - `nodes`: the new nodes for the allow list. + #[weight = (T::WeightInfo::reset_nodes(), DispatchClass::Operational)] + pub fn reset_nodes(origin, nodes: Vec) { + T::ResetOrigin::ensure_origin(origin)?; + ensure!(nodes.len() < T::MaxAuthorizedNodes::get() as usize, Error::::TooManyNodes); + + let mut nodes = nodes; + nodes.sort(); + Self::put_allow_list(&nodes); + + Self::deposit_event(Event::NodesReset(nodes)); + } + } +} + +impl Module { + fn get_allow_list() -> Vec { + storage::unhashed::get_or_default(well_known_keys::NODE_ALLOW_LIST) + } + + fn put_allow_list(nodes: &Vec) { + storage::unhashed::put(well_known_keys::NODE_ALLOW_LIST, nodes); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use frame_support::{ + assert_ok, assert_noop, impl_outer_origin, weights::Weight, + parameter_types, ord_parameter_types, + }; + use frame_system::EnsureSignedBy; + use sp_core::{H256, ed25519::Public}; + use sp_runtime::{Perbill, traits::{BlakeTwo256, IdentityLookup, BadOrigin}, testing::Header}; + use hex_literal::hex; + + impl_outer_origin! { + pub enum Origin for Test where system = frame_system {} + } + + #[derive(Clone, Eq, PartialEq)] + pub struct Test; + + parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); + } + impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Call = (); + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + } + + ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + } + parameter_types! { + pub const MaxAuthorizedNodes: u32 = 4; + } + impl Trait for Test { + type Event = (); + type MaxAuthorizedNodes = MaxAuthorizedNodes; + type AddOrigin = EnsureSignedBy; + type RemoveOrigin = EnsureSignedBy; + type SwapOrigin = EnsureSignedBy; + type ResetOrigin = EnsureSignedBy; + type WeightInfo = (); + } + + type NodeAuthorization = Module; + + fn new_test_ext() -> sp_io::TestExternalities { + let pub10: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000009") + )); + let pub20: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000013") + )); + let pub30: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000001d") + )); + + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + GenesisConfig { + nodes: vec![pub10, pub20, pub30], + }.assimilate_storage(&mut t).unwrap(); + t.into() + } + + #[test] + fn add_node_works() { + new_test_ext().execute_with(|| { + let pub10: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000009") + )); + let pub15: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000000e") + )); + let pub20: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000013") + )); + let pub25: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000018") + )); + let pub30: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000001d") + )); + + assert_noop!(NodeAuthorization::add_node(Origin::signed(2), pub15.clone()), BadOrigin); + assert_noop!( + NodeAuthorization::add_node(Origin::signed(1), pub20.clone()), + Error::::AlreadyJoined + ); + + assert_ok!(NodeAuthorization::add_node(Origin::signed(1), pub15.clone())); + assert_eq!( + NodeAuthorization::get_allow_list(), + vec![pub10.clone(), pub15.clone(), pub20.clone(), pub30.clone()] + ); + + assert_noop!( + NodeAuthorization::add_node(Origin::signed(1), pub25.clone()), + Error::::TooManyNodes + ); + }); + } + + #[test] + fn remove_node_works() { + new_test_ext().execute_with(|| { + let pub10: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000009") + )); + let pub20: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000013") + )); + let pub30: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000001d") + )); + let pub40: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000027") + )); + + assert_noop!( + NodeAuthorization::remove_node(Origin::signed(3), pub20.clone()), + BadOrigin + ); + assert_noop!( + NodeAuthorization::remove_node(Origin::signed(2), pub40.clone()), + Error::::NotExist + ); + + assert_ok!(NodeAuthorization::remove_node(Origin::signed(2), pub20.clone())); + assert_eq!(NodeAuthorization::get_allow_list(), vec![pub10.clone(), pub30.clone()]); + }); + } + + #[test] + fn swap_node_works() { + new_test_ext().execute_with(|| { + let pub5: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000004") + )); + let pub10: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000009") + )); + let pub15: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000000e") + )); + let pub20: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000013") + )); + let pub30: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000001d") + )); + + assert_noop!( + NodeAuthorization::swap_node(Origin::signed(4), pub20.clone(), pub5.clone()), + BadOrigin + ); + + assert_ok!(NodeAuthorization::swap_node(Origin::signed(3), pub20.clone(), pub20.clone())); + assert_eq!( + NodeAuthorization::get_allow_list(), + vec![pub10.clone(), pub20.clone(), pub30.clone()] + ); + + assert_noop!( + NodeAuthorization::swap_node(Origin::signed(3), pub15.clone(), pub5.clone()), + Error::::NotExist + ); + assert_noop!( + NodeAuthorization::swap_node(Origin::signed(3), pub20.clone(), pub30.clone()), + Error::::AlreadyJoined + ); + + assert_ok!(NodeAuthorization::swap_node(Origin::signed(3), pub20.clone(), pub5.clone())); + assert_eq!( + NodeAuthorization::get_allow_list(), + vec![pub5.clone(), pub10.clone(), pub30.clone()] + ); + }); + } + + #[test] + fn reset_nodes_works() { + new_test_ext().execute_with(|| { + let pub5: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000004") + )); + let pub15: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("000000000000000000000000000000000000000000000000000000000000000e") + )); + let pub20: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000013") + )); + let pub25: NodePublicKey = NodePublicKey::Ed25519(Public::from_raw( + hex!("0000000000000000000000000000000000000000000000000000000000000018") + )); + + assert_noop!( + NodeAuthorization::reset_nodes( + Origin::signed(3), + vec![pub15.clone(), pub5.clone(), pub20.clone()] + ), + BadOrigin + ); + assert_noop!( + NodeAuthorization::reset_nodes( + Origin::signed(4), + vec![pub15.clone(), pub5.clone(), pub20.clone(), pub25.clone()] + ), + Error::::TooManyNodes + ); + + assert_ok!( + NodeAuthorization::reset_nodes( + Origin::signed(4), + vec![pub15.clone(), pub5.clone(), pub20.clone()] + ) + ); + assert_eq!( + NodeAuthorization::get_allow_list(), + vec![pub5.clone(), pub15.clone(), pub20.clone()] + ); + }); + } +} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index d6f0850d9ed51..4f37ac9c07397 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -698,6 +698,14 @@ impl sp_std::str::FromStr for AccountId32 { } } +/// Public key of node which can be used for network connection +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, crate::RuntimeDebug)] +pub enum NodePublicKey { + /// An Ed25519 public key + Ed25519(ed25519::Public), +} + #[cfg(feature = "std")] pub use self::dummy::*; diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 2a40972166e14..1fa8492c6824a 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -80,6 +80,7 @@ pub use changes_trie::{ChangesTrieConfiguration, ChangesTrieConfigurationRange}; #[cfg(feature = "full_crypto")] pub use crypto::{DeriveJunction, Pair, Public}; +pub use crypto::NodePublicKey; pub use hash_db::Hasher; #[cfg(feature = "std")] pub use self::hasher::blake2::Blake2Hasher; diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index b253733e7b29e..31e631169ec44 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -172,6 +172,9 @@ pub mod well_known_keys { /// Prefix of the default child storage keys in the top trie. pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:default:"; + /// Authorized node list. + pub const NODE_ALLOW_LIST: &'static [u8] = b":node_allow_list"; + /// Whether a key is a child storage key. /// /// This is convenience function which basically checks if the given `key` starts