Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • fix_picked_up_file_in_runtime_release
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • network/gtest-1000
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • runtime/gtest-1000
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • tuxmain/fix-change-owner-key
  • update-docker-compose-rpc-squid-names
  • upgradable-multisig
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • gtest-1000
  • gtest-1000-0.11.0
  • gtest-1000-0.11.1
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
88 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • archive_upgrade_polkadot_v0.9.42
  • david-wot-scenarios-cucumber
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-fix-idty-post-genesis
  • elois-fix-sufficients-change-owner-key
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-revoc-with-old-key
  • elois-rework-certs
  • elois-smish-members-cant-change-or-rem-idty
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-gtest
  • hugo-remove-duniter-account
  • hugo-rework-genesis
  • hugo-tmp
  • jrx/workspace_tomls
  • master
  • no-bootnodes
  • pallet-benchmark
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • test-gen-new-owner-key-msg
  • ts-types
  • ud-time-64
  • upgrade_polkadot_v0.9.42
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
52 results
Show changes
Showing
with 2101 additions and 1005 deletions
use crate::endpoint_gossip::{
rpc::{
api::{DuniterPeeringRpcApiImpl, DuniterPeeringRpcApiServer},
state::{DuniterPeeringsState, PeeringWithId},
},
well_known_endpoint_types::{RPC, SQUID},
DuniterEndpoint, DuniterEndpoints,
};
use jsonrpsee::RpcModule;
#[tokio::test]
async fn empty_peers_rpc_handler() {
let rpc = setup_io_handler();
let expected_response = r#"{"jsonrpc":"2.0","id":0,"result":{"peerings":[]}}"#.to_string();
let request = r#"{"jsonrpc":"2.0","method":"duniter_peerings","params":[],"id":0}"#;
let (response, _) = rpc.raw_json_request(request, 1).await.unwrap();
assert_eq!(expected_response, response);
}
#[tokio::test]
async fn expose_known_peers() {
let rpc = setup_new_rpc_with_initial_peerings(vec![
PeeringWithId {
peer_id: "12D3KooWRkDXunbB64VegYPCQaitcgtdtEtbsbd7f19nsS7aMjDp".into(),
endpoints: DuniterEndpoints::truncate_from(vec![
DuniterEndpoint {
protocol: RPC.into(),
address: "/rpc/wss://gdev.example.com".into(),
},
DuniterEndpoint {
protocol: SQUID.into(),
address: "/squid/https://squid.gdev.gyroi.de/v1/graphql".into(),
},
]),
},
PeeringWithId {
peer_id: "12D3KooWFiUBo3Kjiryvrpz8b3kfNVk7baezhab7SHdfafgY7nmN".into(),
endpoints: DuniterEndpoints::truncate_from(vec![DuniterEndpoint {
protocol: RPC.into(),
address: "/rpc/ws://gdev.example.com:9944".into(),
}]),
},
]);
let expected_response = r#"{"jsonrpc":"2.0","id":0,"result":{"peerings":[{"peer_id":"12D3KooWRkDXunbB64VegYPCQaitcgtdtEtbsbd7f19nsS7aMjDp","endpoints":[{"protocol":"rpc","address":"/rpc/wss://gdev.example.com"},{"protocol":"squid","address":"/squid/https://squid.gdev.gyroi.de/v1/graphql"}]},{"peer_id":"12D3KooWFiUBo3Kjiryvrpz8b3kfNVk7baezhab7SHdfafgY7nmN","endpoints":[{"protocol":"rpc","address":"/rpc/ws://gdev.example.com:9944"}]}]}}"#.to_string();
let request = r#"{"jsonrpc":"2.0","method":"duniter_peerings","params":[],"id":0}"#;
let (response, _) = rpc.raw_json_request(request, 1).await.unwrap();
assert_eq!(expected_response, response);
}
fn setup_io_handler() -> RpcModule<DuniterPeeringRpcApiImpl> {
DuniterPeeringRpcApiImpl::new(DuniterPeeringsState::empty()).into_rpc()
}
fn setup_new_rpc_with_initial_peerings(
peers: Vec<PeeringWithId>,
) -> RpcModule<DuniterPeeringRpcApiImpl> {
let state = DuniterPeeringsState::empty();
for peer in peers {
state.insert(peer);
}
DuniterPeeringRpcApiImpl::new(state).into_rpc()
}
use crate::{
endpoint_gossip,
endpoint_gossip::{
duniter_peering_protocol_name,
handler::{DuniterPeeringCommand, DuniterPeeringEvent},
well_known_endpoint_types::RPC,
DuniterEndpoint, DuniterEndpoints, Peering,
},
};
use async_channel::Receiver;
use futures::{future, stream, FutureExt, StreamExt};
use log::{debug, warn};
use parking_lot::Mutex;
use sc_consensus::{
BlockCheckParams, BlockImport, BlockImportParams, BoxJustificationImport, ImportResult,
ImportedAux,
};
use sc_network::{NetworkStateInfo, ObservedRole, PeerId};
use sc_network_test::{
Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, PeersClient, TestNetFactory,
};
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender};
use sp_api::__private::BlockT;
use sp_consensus::Error as ConsensusError;
use sp_runtime::traits::Header;
use std::{future::Future, pin::pin, sync::Arc, task::Poll, time::Duration};
#[tokio::test]
async fn peering_is_forwarded_and_only_once_per_connection() {
let _ = env_logger::try_init();
let authorities_count = 3;
let full_count = 1;
let total_peers = authorities_count + full_count;
let mut net = DuniterPeeringTestNet::new(authorities_count, full_count);
tokio::spawn(start_network(&mut net, total_peers));
let net = Arc::new(Mutex::new(net));
// make sure the network is ready (each peering is received by all other peers)
let wait_for_all_peering_notifications =
watch_events_and_wait_for_all_peerings(total_peers, &net);
let wait_for = futures::future::join_all(wait_for_all_peering_notifications).map(|_| ());
tokio::time::timeout(Duration::from_secs(5), run_until_complete(wait_for, &net))
.await
.unwrap();
// rule: only one peering is accepted per connection (disconnecting/restarting allows to change the peering value)
let already_received = ensure_only_one_peering_is_accepted(&net);
tokio::time::timeout(
Duration::from_secs(5),
run_until_complete(already_received, &net),
)
.await
.unwrap();
}
fn ensure_only_one_peering_is_accepted(
net: &Arc<Mutex<DuniterPeeringTestNet>>,
) -> impl Future<Output = ()> {
let command_0 = net.lock().peer_commands[0].clone();
let peer_id_0 = net.lock().peer_ids[0];
let peer_id_1 = net.lock().peer_ids[1];
let stream_1 = net.lock().peer_streams[1].clone();
let already_received = async move {
let mut stream1 = pin!(stream_1);
while let Some(event) = stream1.next().await {
if let DuniterPeeringEvent::AlreadyReceivedPeering(peer) = event {
if peer == peer_id_0 {
// We did receive the peering from peer 0
break;
}
}
}
};
let already_received = futures::future::join_all(vec![already_received]).map(|_| ());
command_0
.unbounded_send(DuniterPeeringCommand::SendPeering(
peer_id_1,
Peering {
endpoints: DuniterEndpoints::truncate_from(vec![DuniterEndpoint {
protocol: RPC.into(),
address: "gdev.example.com:9944".into(),
}]),
},
))
.unwrap();
already_received
}
fn watch_events_and_wait_for_all_peerings(
total_peers: usize,
net: &Arc<Mutex<DuniterPeeringTestNet>>,
) -> Vec<impl Future<Output = ()> + Sized> {
let mut peering_notifications = Vec::new();
for peer_id in 0..total_peers {
let local_peer_id = net.lock().peer_ids[peer_id];
let stream = net.lock().peer_streams[peer_id].clone();
peering_notifications.push(async move {
let mut identified = 0;
let mut stream = pin!(stream);
while let Some(event) = stream.next().await {
debug_event(event.clone(), local_peer_id);
if let DuniterPeeringEvent::GoodPeering(peer, _) = event {
warn!(target: "duniter-libp2p", "[{}] Received peering from {}",local_peer_id, peer);
identified += 1;
if identified == (total_peers - 1) {
// all peers identified
break;
}
}
}
warn!("All peers sent their peering");
})
}
peering_notifications
}
fn debug_event(event: DuniterPeeringEvent, local_peer_id: PeerId) {
match event {
DuniterPeeringEvent::StreamOpened(peer, role) => {
warn!(target: "duniter-libp2p", "[{}] Peer {peer} connected with role {}", local_peer_id, observed_role_to_str(role));
}
DuniterPeeringEvent::StreamValidation(peer, result) => {
warn!(target: "duniter-libp2p", "[{}] Validating inbound substream from {peer} with result {}", local_peer_id, result);
}
DuniterPeeringEvent::StreamClosed(peer) => {
warn!(target: "duniter-libp2p", "[{}] Peer {peer} disconnected", local_peer_id);
}
DuniterPeeringEvent::GossipReceived(peer, success) => {
if success {
warn!(target: "duniter-libp2p", "[{}] Received peering message from {peer}", local_peer_id);
} else {
warn!(target: "duniter-libp2p", "[{}] Failed to receive peering message from {peer}", local_peer_id);
}
}
DuniterPeeringEvent::GoodPeering(peer, _) => {
warn!(target: "duniter-libp2p", "[{}] Received peering from {}", local_peer_id, peer);
}
DuniterPeeringEvent::AlreadyReceivedPeering(peer) => {
warn!(target: "duniter-libp2p", "[{}] Already received peering from {}", local_peer_id, peer);
panic!("Received peering from the same peer twice");
}
DuniterPeeringEvent::SelfPeeringPropagationFailed(peer, _peering, e) => {
warn!(target: "duniter-libp2p", "[{}] Failed to propagate self peering to {}: {}", local_peer_id, peer, e);
panic!("Failed to propagate self peering");
}
DuniterPeeringEvent::SelfPeeringPropagationSuccess(peer, _peering) => {
warn!(target: "duniter-libp2p", "[{}] Successfully propagated self peering to {}", local_peer_id, peer);
}
}
}
fn observed_role_to_str(role: ObservedRole) -> &'static str {
match role {
ObservedRole::Authority => "Authority",
ObservedRole::Full => "Full",
ObservedRole::Light => "Light",
}
}
// Spawns duniter nodes. Returns a future to spawn on the runtime.
fn start_network(net: &mut DuniterPeeringTestNet, peers: usize) -> impl Future<Output = ()> {
let nodes = stream::FuturesUnordered::new();
for peer_id in 0..peers {
let net_service = net.peers[peer_id].network_service().clone();
net.peer_ids.push(net_service.local_peer_id());
let notification_service = net.peers[peer_id]
.take_notification_service(&format!("/{}", duniter_peering_protocol_name::NAME).into())
.unwrap();
let (rpc_sink, mut stream_unbounded) =
tracing_unbounded("mpsc_duniter_gossip_peering_test", 100_000);
let (sink_unbounded, stream) = async_channel::unbounded();
let (command_tx, command_rx) =
tracing_unbounded("mpsc_duniter_gossip_peering_test_command", 100_000);
// mapping from mpsc TracingUnboundedReceiver to mpmc Receiver
tokio::spawn(async move {
// forward the event
while let Some(command) = stream_unbounded.next().await {
sink_unbounded.send(command).await.unwrap();
}
});
let handler = endpoint_gossip::handler::build::<Block, _>(
notification_service,
net_service,
rpc_sink,
Some(command_rx),
DuniterEndpoints::new(),
);
// To send external commands to the handler (for tests or RPC commands).
net.peer_streams.push(stream);
net.peer_commands.push(command_tx);
let node = handler.run();
fn assert_send<T: Send>(_: &T) {}
assert_send(&node);
nodes.push(node);
}
nodes.for_each(|_| async move {})
}
#[derive(Default)]
struct DuniterPeeringTestNet {
// Peers
peers: Vec<DuniterPeeringPeer>,
// IDs of the peers
peer_ids: Vec<PeerId>,
// RX of the gossip events
peer_streams: Vec<Receiver<DuniterPeeringEvent>>,
// TX to drive the handler (for tests or configuration)
peer_commands: Vec<TracingUnboundedSender<DuniterPeeringCommand>>,
}
type DuniterPeeringPeer = sc_network_test::Peer<PeerData, DuniterTestBlockImport>;
impl DuniterPeeringTestNet {
fn new(n_authority: usize, n_full: usize) -> Self {
let mut net = DuniterPeeringTestNet {
peers: Vec::with_capacity(n_authority + n_full),
peer_ids: Vec::new(),
peer_streams: Vec::new(),
peer_commands: Vec::new(),
};
for _ in 0..n_authority {
net.add_authority_peer();
}
for _ in 0..n_full {
net.add_full_peer();
}
net
}
fn add_authority_peer(&mut self) {
self.add_full_peer_with_config(FullPeerConfig {
notifications_protocols: vec![
format!("/{}", duniter_peering_protocol_name::NAME).into()
],
is_authority: true,
..Default::default()
})
}
}
#[derive(Default)]
struct PeerData;
impl TestNetFactory for DuniterPeeringTestNet {
type BlockImport = DuniterTestBlockImport;
type PeerData = PeerData;
type Verifier = PassThroughVerifier;
fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier {
PassThroughVerifier::new(false) // use non-instant finality.
}
fn peer(&mut self, i: usize) -> &mut DuniterPeeringPeer {
&mut self.peers[i]
}
fn peers(&self) -> &Vec<DuniterPeeringPeer> {
&self.peers
}
fn peers_mut(&mut self) -> &mut Vec<DuniterPeeringPeer> {
&mut self.peers
}
fn mut_peers<F: FnOnce(&mut Vec<DuniterPeeringPeer>)>(&mut self, closure: F) {
closure(&mut self.peers);
}
fn make_block_import(
&self,
_client: PeersClient,
) -> (
BlockImportAdapter<Self::BlockImport>,
Option<BoxJustificationImport<Block>>,
Self::PeerData,
) {
(
BlockImportAdapter::new(DuniterTestBlockImport),
None,
PeerData,
)
}
fn add_full_peer(&mut self) {
self.add_full_peer_with_config(FullPeerConfig {
notifications_protocols: vec![
format!("/{}", duniter_peering_protocol_name::NAME).into()
],
is_authority: false,
..Default::default()
})
}
}
async fn run_until_complete(future: impl Future + Unpin, net: &Arc<Mutex<DuniterPeeringTestNet>>) {
let drive_to_completion = futures::future::poll_fn(|cx| {
net.lock().poll(cx);
Poll::<()>::Pending
});
future::select(future, drive_to_completion).await;
}
#[derive(Clone)]
struct DuniterTestBlockImport;
/// Inspired by GrandpaBlockImport
#[async_trait::async_trait]
impl<Block: BlockT> BlockImport<Block> for DuniterTestBlockImport {
type Error = ConsensusError;
/// Fake check block, always succeeds.
async fn check_block(
&self,
_block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
Ok(ImportResult::Imported(ImportedAux {
is_new_best: true,
bad_justification: false,
clear_justification_requests: false,
header_only: false,
needs_justification: false,
}))
}
/// Fake import block, always succeeds.
async fn import_block(
&self,
block: BlockImportParams<Block>,
) -> Result<ImportResult, Self::Error> {
debug!("Importing block #{}", block.header.number());
Ok(ImportResult::Imported(ImportedAux {
is_new_best: true,
bad_justification: false,
clear_justification_requests: false,
header_only: false,
needs_justification: false,
}))
}
}
pub mod validation_result;
use sc_network::service::traits::ValidationResult;
use std::fmt::Display;
/// Clonable version of sc_network::service::traits::ValidationResult
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DuniterStreamValidationResult {
/// Accept inbound substream.
Accept,
/// Reject inbound substream.
Reject,
}
impl Display for DuniterStreamValidationResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DuniterStreamValidationResult::Accept => write!(f, "Accept"),
DuniterStreamValidationResult::Reject => write!(f, "Reject"),
}
}
}
impl From<ValidationResult> for DuniterStreamValidationResult {
fn from(result: ValidationResult) -> Self {
match result {
ValidationResult::Accept => DuniterStreamValidationResult::Accept,
ValidationResult::Reject => DuniterStreamValidationResult::Reject,
}
}
}
impl From<DuniterStreamValidationResult> for ValidationResult {
fn from(result: DuniterStreamValidationResult) -> Self {
match result {
DuniterStreamValidationResult::Accept => ValidationResult::Accept,
DuniterStreamValidationResult::Reject => ValidationResult::Reject,
}
}
}
...@@ -14,8 +14,57 @@ ...@@ -14,8 +14,57 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter v2s Documentation
//!
//! 🆙 A rewriting of [Duniter v1](https://duniter.org) in the [Substrate](https://www.substrate.io/) framework.
//!
//! ⚠️ Duniter-v2s is under active development.
//!
//! 🚧 A test network called "ĞDev" is deployed, allowing testing of wallets and indexers.
//!
//! ## Crate Overview
//!
//! This workspace consists of multiple crates that collaboratively implement the Duniter node, enabling features such as identity management, Web of Trust (WoT) evaluation, universal dividend calculation, and more. Below is a categorized list of crates within this workspace:
//!
//! ### Core Components
//! - [`client/distance`](../dc_distance/index.html): Provides an inherent data provider for distance evaluation in the Web of Trust.
//! - [`distance-oracle`](../distance_oracle/index.html): A standalone tool for performing computationally intensive Web of Trust distance calculations.
//! - [`node`](../duniter/index.html): The main node implementation for running the Duniter blockchain network.
//!
//! ### Testing Utilities
//! - [`end2end-tests`](../duniter_end2end_tests/index.html): End-to-end tests for validating the entire Duniter workflow.
//! - [`live-tests`](../duniter_live_tests/index.html): Live test cases for ensuring the integrity of the chain.
//!
//! ### Pallets (Runtime Modules)
//! - [`pallets/authority-members`](../pallet_authority_members/index.html): Manages the authority members.
//! - [`pallets/certification`](../pallet_certification/index.html): Handles identity certification.
//! - [`pallets/distance`](../pallet_distance/index.html): Implements the storage and logic for WoT distance calculations.
//! - [`pallets/duniter-test-parameters`](../pallet_duniter_test_parameters/index.html): Provides runtime testing parameters.
//! - [`pallets/duniter-test-parameters/macro`](../pallet_duniter_test_parameters_macro/index.html): Macros to simplify testing configurations.
//! - [`pallets/duniter-wot`](../pallet_duniter_wot/index.html): Core logic for managing the WoT.
//! - [`pallets/identity`](../pallet_identity/index.html): Implements identity management.
//! - [`pallets/membership`](../pallet_membership/index.html): Manages memberships.
//! - [`pallets/oneshot-account`](../pallet_oneshot_account/index.html): Manages one-shot accounts.
//! - [`pallets/quota`](../pallet_quota/index.html): Manages users quotas.
//! - [`pallets/smith-members`](../pallet_smith_members/index.html): Manages smiths.
//! - [`pallets/universal-dividend`](../pallet_universal_dividend/index.html): Handles the logic for distributing universal dividends.
//! - [`pallets/upgrade-origin`](../pallet_upgrade_origin/index.html): Ensures secure origins for runtime upgrades.
//!
//! ### Shared Primitives
//! - [`primitives/distance`](../sp_distance/index.html): Shared types and logic for distance evaluations.
//! - [`primitives/membership`](../sp_membership/index.html): Shared primitives for membership-related operations.
//!
//! ### Tooling and Utilities
//! - [`resources/weight_analyzer`](../weightanalyzer/index.html): Provides tools for analyzing runtime weights.
//! - [`runtime/common`](../common_runtime/index.html): Shared components and utilities used across multiple runtimes.
//! - [`runtime/gdev`](../gdev_runtime/index.html): The runtime implementation for the GDEV test network.
//! - [`runtime/g1`](../g1_runtime/index.html): The runtime implementation for the G1 test network.
//! - [`runtime/gtest`](../gtest_runtime/index.html): The runtime implementation for the GTEST test network.
//! - [`xtask`](../xtask/index.html): A custom xtask runner to automate release and testing.
pub mod chain_spec; pub mod chain_spec;
pub mod cli; pub mod cli;
pub mod command; pub mod command;
pub mod endpoint_gossip;
pub mod rpc; pub mod rpc;
pub mod service; pub mod service;
...@@ -18,11 +18,13 @@ ...@@ -18,11 +18,13 @@
#![warn(missing_docs)] #![warn(missing_docs)]
//mod benchmarking;
mod chain_spec; mod chain_spec;
#[macro_use] #[macro_use]
mod service; mod service;
pub(crate) mod cli; pub(crate) mod cli;
mod command; mod command;
mod endpoint_gossip;
mod rpc; mod rpc;
fn main() -> sc_cli::Result<()> { fn main() -> sc_cli::Result<()> {
......
...@@ -15,39 +15,83 @@ ...@@ -15,39 +15,83 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! A collection of node-specific RPC methods. //! A collection of node-specific RPC methods.
//!
//! Substrate provides the `sc-rpc` crate, which defines the core RPC layer //! Substrate provides the `sc-rpc` crate, which defines the core RPC layer
//! used by Substrate nodes. This file extends those RPC definitions with //! used by Substrate nodes. This file extends those RPC definitions with
//! capabilities that are specific to this project's runtime configuration. //! capabilities that are specific to this project's runtime configuration.
#![warn(missing_docs)] #![warn(missing_docs)]
pub use sc_rpc_api::DenyUnsafe; use crate::endpoint_gossip::rpc::{api::DuniterPeeringRpcApiServer, state::DuniterPeeringsState};
use common_runtime::{AccountId, Balance, Block, BlockNumber, Hash, Index};
use common_runtime::Block;
use common_runtime::{AccountId, Balance, Index};
use jsonrpsee::RpcModule; use jsonrpsee::RpcModule;
use sc_consensus_babe::{BabeApi, BabeWorkerHandle};
use sc_consensus_grandpa::{
self, FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState,
};
use sc_rpc::SubscriptionTaskExecutor;
use sc_transaction_pool_api::TransactionPool; use sc_transaction_pool_api::TransactionPool;
use sp_api::ProvideRuntimeApi; use sp_api::ProvideRuntimeApi;
use sp_block_builder::BlockBuilder; use sp_block_builder::BlockBuilder;
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
use sp_consensus::SelectChain;
use sp_keystore::KeystorePtr;
use std::sync::Arc; use std::sync::Arc;
/// Extra dependencies for BABE.
#[derive(Clone)]
pub struct BabeDeps {
/// A handle to the BABE worker for issuing requests.
pub babe_worker_handle: BabeWorkerHandle<Block>,
/// The keystore that manages the keys of the node.
pub keystore: KeystorePtr,
}
/// Dependencies for GRANDPA
#[derive(Clone)]
pub struct GrandpaDeps<B> {
/// Voting round info.
pub shared_voter_state: SharedVoterState,
/// Authority set info.
pub shared_authority_set: SharedAuthoritySet<Hash, BlockNumber>,
/// Receives notifications about justification events from Grandpa.
pub justification_stream: GrandpaJustificationStream<Block>,
/// Executor to drive the subscription manager in the Grandpa RPC handler.
pub subscription_executor: SubscriptionTaskExecutor,
/// Finality proof provider.
pub finality_provider: Arc<FinalityProofProvider<B, Block>>,
}
/// Dependencies for DuniterPeering
#[derive(Clone)]
pub struct DuniterPeeringRpcModuleDeps {
/// The state of the DuniterPeering RPC module which will be exposed.
pub state: DuniterPeeringsState,
}
/// Full client dependencies. /// Full client dependencies.
pub struct FullDeps<C, P> { pub struct FullDeps<C, P, SC, B> {
/// The client instance to use. /// The client instance to use.
pub client: Arc<C>, pub client: Arc<C>,
/// Transaction pool instance. /// Transaction pool instance.
pub pool: Arc<P>, pub pool: Arc<P>,
/// Whether to deny unsafe calls /// The SelectChain Strategy
pub deny_unsafe: DenyUnsafe, pub select_chain: SC,
/// Manual seal command sink /// Manual seal command sink
pub command_sink_opt: pub command_sink_opt: Option<
Option<futures::channel::mpsc::Sender<manual_seal::EngineCommand<sp_core::H256>>>, futures::channel::mpsc::Sender<sc_consensus_manual_seal::EngineCommand<sp_core::H256>>,
>,
/// BABE specific dependencies.
pub babe: Option<BabeDeps>,
/// GRANDPA specific dependencies.
pub grandpa: GrandpaDeps<B>,
/// DuniterPeering specific dependencies.
pub duniter_peering: DuniterPeeringRpcModuleDeps,
} }
/// Instantiate all full RPC extensions. /// Instantiate all full RPC extensions.
pub fn create_full<C, P>( pub fn create_full<C, P, SC, B>(
deps: FullDeps<C, P>, deps: FullDeps<C, P, SC, B>,
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>> ) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
where where
C: ProvideRuntimeApi<Block>, C: ProvideRuntimeApi<Block>,
...@@ -55,24 +99,59 @@ where ...@@ -55,24 +99,59 @@ where
C: Send + Sync + 'static, C: Send + Sync + 'static,
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>, C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>,
C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>,
C::Api: BabeApi<Block>,
C::Api: BlockBuilder<Block>, C::Api: BlockBuilder<Block>,
P: TransactionPool + 'static, P: TransactionPool + 'static,
SC: SelectChain<Block> + 'static,
B: sc_client_api::Backend<Block> + 'static,
{ {
use manual_seal::rpc::{ManualSeal, ManualSealApiServer};
use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer};
use sc_consensus_babe_rpc::{Babe, BabeApiServer};
use sc_consensus_grandpa_rpc::{Grandpa, GrandpaApiServer};
use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer};
use substrate_frame_rpc_system::{System, SystemApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer};
let mut module = RpcModule::new(()); let mut module = RpcModule::new(());
let FullDeps { let FullDeps {
client, client,
pool, pool,
deny_unsafe, select_chain,
command_sink_opt, command_sink_opt,
babe,
grandpa,
duniter_peering: endpoint_gossip,
} = deps; } = deps;
module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; if let Some(babe) = babe {
module.merge(TransactionPayment::new(client).into_rpc())?; let BabeDeps {
babe_worker_handle,
keystore,
} = babe;
module.merge(
Babe::new(client.clone(), babe_worker_handle, keystore, select_chain).into_rpc(),
)?;
}
let GrandpaDeps {
shared_voter_state,
shared_authority_set,
justification_stream,
subscription_executor,
finality_provider,
} = grandpa;
module.merge(
Grandpa::new(
subscription_executor,
shared_authority_set,
shared_voter_state,
justification_stream,
finality_provider,
)
.into_rpc(),
)?;
module.merge(System::new(client.clone(), pool).into_rpc())?;
module.merge(TransactionPayment::new(client.clone()).into_rpc())?;
if let Some(command_sink) = command_sink_opt { if let Some(command_sink) = command_sink_opt {
// We provide the rpc handler with the sending end of the channel to allow the rpc // We provide the rpc handler with the sending end of the channel to allow the rpc
// send EngineCommands to the background block authorship task. // send EngineCommands to the background block authorship task.
...@@ -83,6 +162,10 @@ where ...@@ -83,6 +162,10 @@ where
// `YourRpcStruct` should have a reference to a client, which is needed // `YourRpcStruct` should have a reference to a client, which is needed
// to call into the runtime. // to call into the runtime.
// `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;`
module.merge(
crate::endpoint_gossip::rpc::api::DuniterPeeringRpcApiImpl::new(endpoint_gossip.state)
.into_rpc(),
)?;
Ok(module) Ok(module)
} }
...@@ -19,182 +19,138 @@ ...@@ -19,182 +19,138 @@
pub mod client; pub mod client;
use self::client::{Client, ClientHandle, RuntimeApiCollection}; use self::client::{Client, ClientHandle, RuntimeApiCollection};
use crate::{
endpoint_gossip::{
rpc::state::DuniterPeeringsState,
well_known_endpoint_types::{RPC, SQUID},
DuniterEndpoint, DuniterEndpoints, Peering,
},
rpc::DuniterPeeringRpcModuleDeps,
};
use async_io::Timer; use async_io::Timer;
use common_runtime::Block; use common_runtime::Block;
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use manual_seal::{run_manual_seal, EngineCommand, ManualSealParams}; use log::error;
use sc_client_api::BlockBackend; use sc_client_api::{client::BlockBackend, Backend};
pub use sc_executor::NativeElseWasmExecutor; use sc_consensus_grandpa::{FinalityProofProvider, SharedVoterState};
use sc_finality_grandpa::SharedVoterState; use sc_consensus_manual_seal::{run_manual_seal, EngineCommand, ManualSealParams};
use sc_keystore::LocalKeystore; use sc_rpc::SubscriptionTaskExecutor;
use sc_service::{error::Error as ServiceError, Configuration, PartialComponents, TaskManager}; use sc_service::{
error::Error as ServiceError, Configuration, PartialComponents, TaskManager, WarpSyncConfig,
};
use sc_telemetry::{Telemetry, TelemetryWorker}; use sc_telemetry::{Telemetry, TelemetryWorker};
use sc_transaction_pool_api::TransactionPool;
use sp_consensus_babe::inherents::InherentDataProvider;
use sp_core::H256; use sp_core::H256;
use sp_runtime::traits::BlakeTwo256; use sp_runtime::traits::BlakeTwo256;
use std::{sync::Arc, time::Duration}; use std::{fs, sync::Arc, time::Duration};
#[cfg(not(feature = "runtime-benchmarks"))]
type HostFunctions = sp_io::SubstrateHostFunctions;
#[cfg(feature = "runtime-benchmarks")]
type HostFunctions = (
sp_io::SubstrateHostFunctions,
frame_benchmarking::benchmarking::HostFunctions,
);
// Allow to use native Runtime for debugging/development purposes
#[cfg(feature = "native")]
type FullClient<RuntimeApi, Executor> =
sc_service::TFullClient<Block, RuntimeApi, sc_executor::NativeElseWasmExecutor<Executor>>;
// By default, WASM only Runtime
#[cfg(not(feature = "native"))]
type FullClient<RuntimeApi, Executor> = type FullClient<RuntimeApi, Executor> =
sc_service::TFullClient<Block, RuntimeApi, NativeElseWasmExecutor<Executor>>; sc_service::TFullClient<Block, RuntimeApi, sc_executor::WasmExecutor<Executor>>;
type FullBackend = sc_service::TFullBackend<Block>; type FullBackend = sc_service::TFullBackend<Block>;
type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>; type FullSelectChain = sc_consensus::LongestChain<FullBackend, Block>;
pub mod runtime_executor {
use crate::service::HostFunctions;
#[cfg(feature = "g1")]
pub use g1_runtime as runtime;
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
pub struct GDevExecutor; pub use gdev_runtime as runtime;
#[cfg(feature = "gdev")] #[cfg(feature = "gtest")]
impl sc_executor::NativeExecutionDispatch for GDevExecutor { pub use gtest_runtime as runtime;
/// Only enable the benchmarking host functions when we actually want to benchmark.
#[cfg(feature = "runtime-benchmarks")]
type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;
/// Otherwise we only use the default Substrate host functions.
#[cfg(not(feature = "runtime-benchmarks"))]
type ExtendHostFunctions = ();
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
gdev_runtime::api::dispatch(method, data)
}
fn native_version() -> sc_executor::NativeVersion { use sc_executor::sp_wasm_interface::{Function, HostFunctionRegistry};
gdev_runtime::native_version()
}
}
#[cfg(feature = "gtest")] pub struct Executor;
pub struct GTestExecutor; impl sc_executor::NativeExecutionDispatch for Executor {
#[cfg(feature = "gtest")]
impl sc_executor::NativeExecutionDispatch for GTestExecutor {
/// Only enable the benchmarking host functions when we actually want to benchmark.
#[cfg(feature = "runtime-benchmarks")]
type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;
/// Otherwise we only use the default Substrate host functions.
#[cfg(not(feature = "runtime-benchmarks"))]
type ExtendHostFunctions = ();
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> { fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
gtest_runtime::api::dispatch(method, data) runtime::api::dispatch(method, data)
} }
fn native_version() -> sc_executor::NativeVersion { fn native_version() -> sc_executor::NativeVersion {
gtest_runtime::native_version() runtime::native_version()
} }
} }
impl sc_executor::sp_wasm_interface::HostFunctions for Executor {
#[cfg(feature = "g1")] fn host_functions() -> Vec<&'static dyn Function> {
pub struct G1Executor; HostFunctions::host_functions()
#[cfg(feature = "g1")]
impl sc_executor::NativeExecutionDispatch for G1Executor {
/// Only enable the benchmarking host functions when we actually want to benchmark.
#[cfg(feature = "runtime-benchmarks")]
type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions;
/// Otherwise we only use the default Substrate host functions.
#[cfg(not(feature = "runtime-benchmarks"))]
type ExtendHostFunctions = ();
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
g1_runtime::api::dispatch(method, data)
} }
fn native_version() -> sc_executor::NativeVersion { fn register_static<T>(registry: &mut T) -> Result<(), T::Error>
g1_runtime::native_version() where
T: HostFunctionRegistry,
{
HostFunctions::register_static(registry)
}
} }
} }
///
/// The minimum period of blocks on which justifications will be
/// imported and generated.
const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512;
#[derive(Debug)]
pub enum RuntimeType { pub enum RuntimeType {
G1, G1,
GDev, GDev,
GTest, GTest,
} }
/// Can be called for a `Configuration` to check if it is a configuration for
/// a particular runtime type.
pub trait IdentifyRuntimeType {
/// Returns the runtime type
fn runtime_type(&self) -> RuntimeType;
}
impl IdentifyRuntimeType for Box<dyn sc_chain_spec::ChainSpec> {
fn runtime_type(&self) -> RuntimeType {
if self.id().starts_with("g1") {
RuntimeType::G1
} else if self.id().starts_with("gdem") {
RuntimeType::GTest
} else if self.id().starts_with("dev") || self.id().starts_with("gdev") {
RuntimeType::GDev
} else if self.id().starts_with("gt") {
RuntimeType::GTest
} else {
panic!("unknown runtime")
}
}
}
/// Builds a new object suitable for chain operations. /// Builds a new object suitable for chain operations.
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub fn new_chain_ops( pub fn new_chain_ops(
config: &mut Configuration, config: &Configuration,
manual_consensus: bool, manual_consensus: bool,
) -> Result< ) -> Result<
( (
Arc<Client>, Arc<Client>,
Arc<FullBackend>, Arc<FullBackend>,
sc_consensus::BasicQueue<Block, sp_trie::PrefixedMemoryDB<BlakeTwo256>>, sc_consensus::BasicQueue<Block>,
TaskManager, TaskManager,
), ),
ServiceError, ServiceError,
> { > {
match config.chain_spec.runtime_type() { let (
#[cfg(feature = "g1")] PartialComponents {
RuntimeType::G1::G1 => {
let PartialComponents {
client, client,
backend, backend,
import_queue, import_queue,
task_manager, task_manager,
.. ..
} = new_partial::<g1_runtime::RuntimeApi, G1Executor>(config, manual_consensus)?; },
Ok(( _duniter_config,
Arc::new(Client::G1(client)), ) = new_partial::<runtime_executor::runtime::RuntimeApi, runtime_executor::Executor>(
backend, config,
import_queue, manual_consensus,
task_manager, Default::default(),
)) )?;
}
#[cfg(feature = "gtest")]
RuntimeType::GTest => {
let PartialComponents {
client,
backend,
import_queue,
task_manager,
..
} = new_partial::<gtest_runtime::RuntimeApi, GTestExecutor>(config, manual_consensus)?;
Ok((
Arc::new(Client::GTest(client)),
backend,
import_queue,
task_manager,
))
}
#[cfg(feature = "gdev")]
RuntimeType::GDev => {
let PartialComponents {
client,
backend,
import_queue,
task_manager,
..
} = new_partial::<gdev_runtime::RuntimeApi, GDevExecutor>(config, manual_consensus)?;
Ok(( Ok((
Arc::new(Client::GDev(client)), Arc::new(Client::Client(client)),
backend, backend,
import_queue, import_queue,
task_manager, task_manager,
)) ))
} }
_ => panic!("unknown runtime"),
}
}
type FullGrandpaBlockImport<RuntimeApi, Executor> = sc_finality_grandpa::GrandpaBlockImport< type FullGrandpaBlockImport<RuntimeApi, Executor> = sc_consensus_grandpa::GrandpaBlockImport<
FullBackend, FullBackend,
Block, Block,
FullClient<RuntimeApi, Executor>, FullClient<RuntimeApi, Executor>,
...@@ -205,24 +161,33 @@ type FullGrandpaBlockImport<RuntimeApi, Executor> = sc_finality_grandpa::Grandpa ...@@ -205,24 +161,33 @@ type FullGrandpaBlockImport<RuntimeApi, Executor> = sc_finality_grandpa::Grandpa
pub fn new_partial<RuntimeApi, Executor>( pub fn new_partial<RuntimeApi, Executor>(
config: &Configuration, config: &Configuration,
consensus_manual: bool, consensus_manual: bool,
duniter_options: crate::cli::DuniterConfigExtension,
) -> Result< ) -> Result<
(
sc_service::PartialComponents< sc_service::PartialComponents<
FullClient<RuntimeApi, Executor>, FullClient<RuntimeApi, Executor>,
FullBackend, FullBackend,
FullSelectChain, FullSelectChain,
sc_consensus::DefaultImportQueue<Block, FullClient<RuntimeApi, Executor>>, sc_consensus::DefaultImportQueue<Block>,
sc_transaction_pool::FullPool<Block, FullClient<RuntimeApi, Executor>>, sc_transaction_pool::TransactionPoolWrapper<Block, FullClient<RuntimeApi, Executor>>,
( (
babe::BabeBlockImport< sc_consensus_babe::BabeBlockImport<
Block, Block,
FullClient<RuntimeApi, Executor>, FullClient<RuntimeApi, Executor>,
FullGrandpaBlockImport<RuntimeApi, Executor>, FullGrandpaBlockImport<RuntimeApi, Executor>,
>, >,
babe::BabeLink<Block>, sc_consensus_babe::BabeLink<Block>,
sc_finality_grandpa::LinkHalf<Block, FullClient<RuntimeApi, Executor>, FullSelectChain>, Option<sc_consensus_babe::BabeWorkerHandle<Block>>,
sc_consensus_grandpa::LinkHalf<
Block,
FullClient<RuntimeApi, Executor>,
FullSelectChain,
>,
Option<Telemetry>, Option<Telemetry>,
), ),
>, >,
crate::cli::DuniterConfigExtension,
),
ServiceError, ServiceError,
> >
where where
...@@ -230,16 +195,10 @@ where ...@@ -230,16 +195,10 @@ where
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
RuntimeApi::RuntimeApi: RuntimeApi::RuntimeApi: RuntimeApiCollection,
RuntimeApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
Executor: sc_executor::NativeExecutionDispatch + 'static, Executor: sc_executor::NativeExecutionDispatch + 'static,
Executor: sc_executor::sp_wasm_interface::HostFunctions + 'static,
{ {
if config.keystore_remote.is_some() {
return Err(ServiceError::Other(
"Remote Keystores are not supported.".to_owned(),
));
}
let telemetry = config let telemetry = config
.telemetry_endpoints .telemetry_endpoints
.clone() .clone()
...@@ -251,12 +210,10 @@ where ...@@ -251,12 +210,10 @@ where
}) })
.transpose()?; .transpose()?;
let executor = NativeElseWasmExecutor::<Executor>::new( #[cfg(feature = "native")]
config.wasm_method, let executor = sc_service::new_native_or_wasm_executor(&config);
config.default_heap_pages, #[cfg(not(feature = "native"))]
config.max_runtime_instances, let executor = sc_service::new_wasm_executor(&config.executor);
config.runtime_cache_size,
);
let (client, backend, keystore_container, task_manager) = let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, _>( sc_service::new_full_parts::<Block, RuntimeApi, _>(
...@@ -275,59 +232,74 @@ where ...@@ -275,59 +232,74 @@ where
let select_chain = sc_consensus::LongestChain::new(backend.clone()); let select_chain = sc_consensus::LongestChain::new(backend.clone());
let transaction_pool = sc_transaction_pool::BasicPool::new_full( let transaction_pool = Arc::from(
config.transaction_pool.clone(), sc_transaction_pool::Builder::new(
config.role.is_authority().into(),
config.prometheus_registry(),
task_manager.spawn_essential_handle(), task_manager.spawn_essential_handle(),
client.clone(), client.clone(),
config.role.is_authority().into(),
)
.with_options(config.transaction_pool.clone())
.with_prometheus(config.prometheus_registry())
.build(),
); );
let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import( let client_ = client.clone();
let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import(
client.clone(), client.clone(),
&(client.clone() as Arc<_>), GRANDPA_JUSTIFICATION_PERIOD,
&(client_ as Arc<_>),
select_chain.clone(), select_chain.clone(),
telemetry.as_ref().map(|x| x.handle()), telemetry.as_ref().map(|x| x.handle()),
)?; )?;
let justification_import = grandpa_block_import.clone(); let justification_import = grandpa_block_import.clone();
let babe_config = babe::configuration(&*client)?; let (babe_block_import, babe_link) = sc_consensus_babe::block_import(
let (babe_block_import, babe_link) = sc_consensus_babe::configuration(&*client)?,
babe::block_import(babe_config, grandpa_block_import, client.clone())?; grandpa_block_import,
client.clone(),
)?;
let import_queue = if consensus_manual { let (import_queue, babe_worker_handle) = if consensus_manual {
manual_seal::import_queue( let import_queue = sc_consensus_manual_seal::import_queue(
Box::new(babe_block_import.clone()), Box::new(babe_block_import.clone()),
&task_manager.spawn_essential_handle(), &task_manager.spawn_essential_handle(),
config.prometheus_registry(), config.prometheus_registry(),
) );
(import_queue, None)
} else { } else {
let slot_duration = babe_link.config().slot_duration(); let slot_duration = babe_link.config().slot_duration();
babe::import_queue( let (queue, handle) =
babe_link.clone(), sc_consensus_babe::import_queue(sc_consensus_babe::ImportQueueParams {
babe_block_import.clone(), link: babe_link.clone(),
Some(Box::new(justification_import)), block_import: babe_block_import.clone(),
client.clone(), justification_import: Some(Box::new(justification_import)),
select_chain.clone(), client: client.clone(),
move |_, ()| async move { select_chain: select_chain.clone(),
create_inherent_data_providers: move |_, ()| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot = let slot = InherentDataProvider::from_timestamp_and_slot_duration(
sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp, *timestamp,
slot_duration, slot_duration,
); );
Ok((slot, timestamp)) Ok((slot, timestamp))
}, },
&task_manager.spawn_essential_handle(), spawner: &task_manager.spawn_essential_handle(),
config.prometheus_registry(), registry: config.prometheus_registry(),
telemetry.as_ref().map(|x| x.handle()), telemetry: telemetry.as_ref().map(|x| x.handle()),
)? offchain_tx_pool_factory:
sc_transaction_pool_api::OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
),
})?;
(queue, Some(handle))
}; };
Ok(sc_service::PartialComponents { Ok((
sc_service::PartialComponents {
client, client,
backend, backend,
task_manager, task_manager,
...@@ -335,103 +307,153 @@ where ...@@ -335,103 +307,153 @@ where
keystore_container, keystore_container,
select_chain, select_chain,
transaction_pool, transaction_pool,
other: (babe_block_import, babe_link, grandpa_link, telemetry), other: (
}) babe_block_import,
} babe_link,
babe_worker_handle,
fn remote_keystore(_url: &str) -> Result<Arc<LocalKeystore>, &'static str> { grandpa_link,
// FIXME: here would the concrete keystore be built, telemetry,
// must return a concrete type (NOT `LocalKeystore`) that ),
// implements `CryptoStore` and `SyncCryptoStore` },
Err("Remote Keystore not supported.") duniter_options,
))
} }
/// Builds a new service for a full client. /// Builds a new service for a full client.
pub fn new_full<RuntimeApi, Executor>( pub fn new_full<
mut config: Configuration, RuntimeApi,
Executor,
N: sc_network::NetworkBackend<Block, <Block as sp_runtime::traits::Block>::Hash>,
>(
config: Configuration,
sealing: crate::cli::Sealing, sealing: crate::cli::Sealing,
duniter_options: crate::cli::DuniterConfigExtension,
) -> Result<TaskManager, ServiceError> ) -> Result<TaskManager, ServiceError>
where where
RuntimeApi: sp_api::ConstructRuntimeApi<Block, FullClient<RuntimeApi, Executor>> RuntimeApi: sp_api::ConstructRuntimeApi<Block, FullClient<RuntimeApi, Executor>>
+ Send + Send
+ Sync + Sync
+ 'static, + 'static,
RuntimeApi::RuntimeApi: RuntimeApi::RuntimeApi: RuntimeApiCollection,
RuntimeApiCollection<StateBackend = sc_client_api::StateBackendFor<FullBackend, Block>>,
Executor: sc_executor::NativeExecutionDispatch + 'static, Executor: sc_executor::NativeExecutionDispatch + 'static,
Executor: sc_executor::sp_wasm_interface::HostFunctions + 'static,
{ {
let sc_service::PartialComponents { let (
sc_service::PartialComponents {
client, client,
backend, backend,
mut task_manager, mut task_manager,
import_queue, import_queue,
mut keystore_container, keystore_container,
select_chain, select_chain,
transaction_pool, transaction_pool,
other: (block_import, babe_link, grandpa_link, mut telemetry), other: (block_import, babe_link, babe_worker_handle, grandpa_link, mut telemetry),
} = new_partial::<RuntimeApi, Executor>(&config, sealing.is_manual_consensus())?; },
duniter_options,
if let Some(url) = &config.keystore_remote { ) = new_partial::<RuntimeApi, Executor>(
match remote_keystore(url) { &config,
Ok(k) => keystore_container.set_remote_keystore(k), sealing.is_manual_consensus(),
Err(e) => { duniter_options,
return Err(ServiceError::Other(format!( )?;
"Error hooking up remote keystore for {}: {}",
url, e
)))
}
};
}
let grandpa_protocol_name = sc_finality_grandpa::protocol_standard_name( // genesis hash used in protocol names
&client let genesis_hash = client
.block_hash(0) .block_hash(0)
.ok() .ok()
.flatten() .flatten()
.expect("Genesis block exists; qed"), .expect("Genesis block exists; qed");
&config.chain_spec,
); // shared network config
config let mut net_config = sc_network::config::FullNetworkConfiguration::<
.network Block,
.extra_sets <Block as sp_runtime::traits::Block>::Hash,
.push(sc_finality_grandpa::grandpa_peers_set_config( N,
>::new(&config.network, config.prometheus_registry().cloned());
let metrics = N::register_notification_metrics(config.prometheus_registry());
let peer_store_handle = net_config.peer_store_handle();
// grandpa network config
let grandpa_protocol_name =
sc_consensus_grandpa::protocol_standard_name(&genesis_hash, &config.chain_spec);
let (grandpa_protocol_config, grandpa_notification_service) =
sc_consensus_grandpa::grandpa_peers_set_config::<_, N>(
grandpa_protocol_name.clone(), grandpa_protocol_name.clone(),
)); metrics.clone(),
let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new( peer_store_handle.clone(),
);
net_config.add_notification_protocol(grandpa_protocol_config);
let (duniter_peering_params, duniter_peering_config) =
crate::endpoint_gossip::DuniterPeeringParams::new::<_, Block, N>(
genesis_hash,
config.chain_spec.fork_id(),
metrics.clone(),
Arc::clone(&peer_store_handle),
);
net_config.add_notification_protocol(duniter_peering_config);
// warp sync network provider
let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new(
backend.clone(), backend.clone(),
grandpa_link.shared_authority_set().clone(), grandpa_link.shared_authority_set().clone(),
Vec::default(), Vec::default(),
)); ));
let (network, system_rpc_tx, tx_handler_controller, network_starter) = // build network service from params
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
sc_service::build_network(sc_service::BuildNetworkParams { sc_service::build_network(sc_service::BuildNetworkParams {
config: &config, config: &config,
net_config,
client: client.clone(), client: client.clone(),
transaction_pool: transaction_pool.clone(), transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(), spawn_handle: task_manager.spawn_handle(),
import_queue, import_queue,
block_announce_validator_builder: None, block_announce_validator_builder: None,
warp_sync: Some(warp_sync), warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)),
block_relay: None,
metrics,
})?; })?;
if config.offchain_worker.enabled { // aliases
sc_service::build_offchain_workers( let role = config.role;
&config,
task_manager.spawn_handle(),
client.clone(),
network.clone(),
);
}
let role = config.role.clone();
let force_authoring = config.force_authoring; let force_authoring = config.force_authoring;
let backoff_authoring_blocks: Option<()> = None; let backoff_authoring_blocks: Option<()> = None;
let name = config.network.node_name.clone(); let name = config.network.node_name.clone();
let enable_grandpa = !config.disable_grandpa; let enable_grandpa = !config.disable_grandpa;
let prometheus_registry = config.prometheus_registry().cloned(); let prometheus_registry = config.prometheus_registry().cloned();
if config.offchain_worker.enabled {
use futures::FutureExt;
task_manager.spawn_handle().spawn(
"offchain-workers-runner",
"offchain-work",
sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
runtime_api_provider: client.clone(),
keystore: Some(keystore_container.keystore()),
offchain_db: backend.offchain_storage(),
transaction_pool: Some(
sc_transaction_pool_api::OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
),
),
network_provider: Arc::new(network.clone()),
is_validator: role.is_authority(),
enable_http_requests: false,
custom_extensions: move |_| vec![],
})?
.run(client.clone(), task_manager.spawn_handle())
.boxed(),
);
}
let mut command_sink_opt = None; let mut command_sink_opt = None;
if role.is_authority() { if role.is_authority() {
let distance_dir = config
.base_path
.config_dir(config.chain_spec.id())
.join("distance");
let proposer_factory = sc_basic_authorship::ProposerFactory::new( let proposer_factory = sc_basic_authorship::ProposerFactory::new(
task_manager.spawn_handle(), task_manager.spawn_handle(),
client.clone(), client.clone(),
...@@ -440,21 +462,22 @@ where ...@@ -440,21 +462,22 @@ where
telemetry.as_ref().map(|x| x.handle()), telemetry.as_ref().map(|x| x.handle()),
); );
let keystore_ptr = keystore_container.keystore();
let client = client.clone();
if sealing.is_manual_consensus() { if sealing.is_manual_consensus() {
let commands_stream: Box<dyn Stream<Item = EngineCommand<H256>> + Send + Sync + Unpin> = let commands_stream: Box<dyn Stream<Item = EngineCommand<H256>> + Send + Sync + Unpin> =
match sealing { match sealing {
crate::cli::Sealing::Instant => { crate::cli::Sealing::Instant => {
Box::new( Box::new(
// This bit cribbed from the implementation of instant seal. // This bit cribbed from the implementation of instant seal.
transaction_pool transaction_pool.import_notification_stream().map(|_| {
.pool() EngineCommand::SealNewBlock {
.validated_pool()
.import_notification_stream()
.map(|_| EngineCommand::SealNewBlock {
create_empty: false, create_empty: false,
finalize: false, finalize: false,
parent_hash: None, parent_hash: None,
sender: None, sender: None,
}
}), }),
) )
} }
...@@ -477,9 +500,9 @@ where ...@@ -477,9 +500,9 @@ where
}; };
let babe_consensus_data_provider = let babe_consensus_data_provider =
manual_seal::consensus::babe::BabeConsensusDataProvider::new( sc_consensus_manual_seal::consensus::babe::BabeConsensusDataProvider::new(
client.clone(), client.clone(),
keystore_container.sync_keystore(), keystore_container.keystore(),
babe_link.epoch_changes().clone(), babe_link.epoch_changes().clone(),
vec![( vec![(
sp_consensus_babe::AuthorityId::from( sp_consensus_babe::AuthorityId::from(
...@@ -490,7 +513,6 @@ where ...@@ -490,7 +513,6 @@ where
) )
.expect("failed to create BabeConsensusDataProvider"); .expect("failed to create BabeConsensusDataProvider");
let client_clone = client.clone();
task_manager.spawn_essential_handle().spawn_blocking( task_manager.spawn_essential_handle().spawn_blocking(
"manual-seal", "manual-seal",
Some("block-authoring"), Some("block-authoring"),
...@@ -500,108 +522,158 @@ where ...@@ -500,108 +522,158 @@ where
client: client.clone(), client: client.clone(),
pool: transaction_pool.clone(), pool: transaction_pool.clone(),
commands_stream, commands_stream,
select_chain, select_chain: select_chain.clone(),
consensus_data_provider: Some(Box::new(babe_consensus_data_provider)), consensus_data_provider: Some(Box::new(babe_consensus_data_provider)),
create_inherent_data_providers: move |_, _| { create_inherent_data_providers: move |parent, _| {
let client = client_clone.clone(); let client = client.clone();
let distance_dir = distance_dir.clone();
let babe_owner_keys =
std::sync::Arc::new(sp_keystore::Keystore::sr25519_public_keys(
keystore_ptr.as_ref(),
sp_runtime::KeyTypeId(*b"babe"),
));
async move { async move {
let timestamp = let timestamp =
manual_seal::consensus::timestamp::SlotTimestampProvider::new_babe( sc_consensus_manual_seal::consensus::timestamp::SlotTimestampProvider::new_babe(
client.clone(), client.clone(),
) )
.map_err(|err| format!("{:?}", err))?; .map_err(|err| format!("{:?}", err))?;
let babe = sp_consensus_babe::inherents::InherentDataProvider::new( let babe = InherentDataProvider::new(
timestamp.slot(), timestamp.slot(),
); );
Ok((timestamp, babe)) let distance =
dc_distance::create_distance_inherent_data_provider::<
Block,
FullClient<RuntimeApi, Executor>,
FullBackend,
>(
&*client, parent, distance_dir, &babe_owner_keys.clone()
);
Ok((timestamp, babe, distance))
} }
}, },
}), }),
); );
} else { } else {
let client_clone = client.clone();
let slot_duration = babe_link.config().slot_duration(); let slot_duration = babe_link.config().slot_duration();
let babe_config = babe::BabeParams { let babe_config = sc_consensus_babe::BabeParams {
keystore: keystore_container.sync_keystore(), keystore: keystore_container.keystore(),
client: client.clone(), client: client.clone(),
select_chain, select_chain: select_chain.clone(),
block_import, block_import,
env: proposer_factory, env: proposer_factory,
sync_oracle: network.clone(), sync_oracle: sync_service.clone(),
justification_sync_link: network.clone(), justification_sync_link: sync_service.clone(),
create_inherent_data_providers: move |parent, ()| { create_inherent_data_providers: move |parent, ()| {
let client_clone = client_clone.clone(); // This closure is called during each block generation.
async move { let client = client.clone();
let uncles = sc_consensus_uncles::create_uncles_inherent_data_provider( let distance_dir = distance_dir.clone();
&*client_clone, let babe_owner_keys =
parent, std::sync::Arc::new(sp_keystore::Keystore::sr25519_public_keys(
)?; keystore_ptr.as_ref(),
sp_runtime::KeyTypeId(*b"babe"),
));
async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot = let slot = InherentDataProvider::from_timestamp_and_slot_duration(
sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp, *timestamp,
slot_duration, slot_duration,
); );
Ok((slot, timestamp, uncles)) let storage_proof =
sp_transaction_storage_proof::registration::new_data_provider(
&*client, &parent,
)?;
let distance = dc_distance::create_distance_inherent_data_provider::<
Block,
FullClient<RuntimeApi, Executor>,
FullBackend,
>(
&*client, parent, distance_dir, &babe_owner_keys.clone()
);
Ok((slot, timestamp, storage_proof, distance))
} }
}, },
force_authoring, force_authoring,
backoff_authoring_blocks, backoff_authoring_blocks,
babe_link, babe_link,
block_proposal_slot_portion: babe::SlotProportion::new(2f32 / 3f32), block_proposal_slot_portion: sc_consensus_babe::SlotProportion::new(2f32 / 3f32),
max_block_proposal_slot_portion: None, max_block_proposal_slot_portion: None,
telemetry: telemetry.as_ref().map(|x| x.handle()), telemetry: telemetry.as_ref().map(|x| x.handle()),
}; };
let babe = babe::start_babe(babe_config)?; let babe = sc_consensus_babe::start_babe(babe_config)?;
// the BABE authoring task is considered essential, i.e. if it // the BABE authoring task is considered essential, i.e. if it
// fails we take down the service with it. // fails we take down the service with it.
task_manager.spawn_essential_handle().spawn_blocking( task_manager.spawn_essential_handle().spawn_blocking(
"babe", "babe-proposer",
Some("block-authoring"), Some("block-authoring"),
babe, babe,
); );
} }
} }
let justification_stream = grandpa_link.justification_stream();
let shared_authority_set = grandpa_link.shared_authority_set().clone();
let shared_voter_state = SharedVoterState::empty();
let finality_proof_provider =
FinalityProofProvider::new_for_service(backend.clone(), Some(shared_authority_set.clone()));
let shared_duniter_peerings_state = DuniterPeeringsState::empty();
let rpc_extensions_builder = { let rpc_extensions_builder = {
let client = client.clone(); let client = client.clone();
//let keystore = keystore_container.sync_keystore();
let pool = transaction_pool.clone(); let pool = transaction_pool.clone();
//let select_chain = select_chain.clone(); let select_chain = select_chain;
//let chain_spec = config.chain_spec.cloned_box(); let keystore = keystore_container.keystore().clone();
let babe_deps = babe_worker_handle.map(|babe_worker_handle| crate::rpc::BabeDeps {
babe_worker_handle,
keystore: keystore.clone(),
});
let rpc_setup = shared_voter_state.clone();
let state_clone = shared_duniter_peerings_state.clone();
Box::new(
move |subscription_task_executor: SubscriptionTaskExecutor| {
let grandpa_deps = crate::rpc::GrandpaDeps {
shared_voter_state: rpc_setup.clone(),
shared_authority_set: shared_authority_set.clone(),
justification_stream: justification_stream.clone(),
subscription_executor: subscription_task_executor.clone(),
finality_provider: finality_proof_provider.clone(),
};
let endpoint_gossip_deps = DuniterPeeringRpcModuleDeps {
state: state_clone.clone(),
};
Box::new(move |deny_unsafe, _| {
let deps = crate::rpc::FullDeps { let deps = crate::rpc::FullDeps {
client: client.clone(), client: client.clone(),
pool: pool.clone(), pool: pool.clone(),
//select_chain: select_chain.clone(), select_chain: select_chain.clone(),
//chain_spec: chain_spec.cloned_box(), babe: babe_deps.clone(),
deny_unsafe, grandpa: grandpa_deps,
/*babe: crate::rpc::BabeDeps { duniter_peering: endpoint_gossip_deps,
babe_config: babe_config.clone(),
shared_epoch_changes: shared_epoch_changes.clone(),
keystore: keystore.clone(),
},*/
command_sink_opt: command_sink_opt.clone(), command_sink_opt: command_sink_opt.clone(),
}; };
crate::rpc::create_full(deps).map_err(Into::into) crate::rpc::create_full(deps).map_err(Into::into)
}) },
)
}; };
let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
config, config,
backend, backend,
network: network.clone(), network: network.clone(),
sync_service: sync_service.clone(),
client, client,
keystore: keystore_container.sync_keystore(), keystore: keystore_container.keystore(),
task_manager: &mut task_manager, task_manager: &mut task_manager,
transaction_pool, transaction_pool: transaction_pool.clone(),
rpc_builder: rpc_extensions_builder, rpc_builder: rpc_extensions_builder,
system_rpc_tx, system_rpc_tx,
tx_handler_controller, tx_handler_controller,
...@@ -611,15 +683,14 @@ where ...@@ -611,15 +683,14 @@ where
// if the node isn't actively participating in consensus then it doesn't // if the node isn't actively participating in consensus then it doesn't
// need a keystore, regardless of which protocol we use below. // need a keystore, regardless of which protocol we use below.
let keystore = if role.is_authority() { let keystore = if role.is_authority() {
Some(keystore_container.sync_keystore()) Some(keystore_container.keystore())
} else { } else {
None None
}; };
let grandpa_config = sc_finality_grandpa::Config { let grandpa_config = sc_consensus_grandpa::Config {
// FIXME #1578 make this available through chainspec
gossip_duration: Duration::from_millis(333), gossip_duration: Duration::from_millis(333),
justification_period: 512, justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD,
name: Some(name), name: Some(name),
observer_enabled: false, observer_enabled: false,
keystore, keystore,
...@@ -635,14 +706,19 @@ where ...@@ -635,14 +706,19 @@ where
// and vote data availability than the observer. The observer has not // and vote data availability than the observer. The observer has not
// been tested extensively yet and having most nodes in a network run it // been tested extensively yet and having most nodes in a network run it
// could lead to finality stalls. // could lead to finality stalls.
let grandpa_config = sc_finality_grandpa::GrandpaParams { let grandpa_config = sc_consensus_grandpa::GrandpaParams {
config: grandpa_config, config: grandpa_config,
link: grandpa_link, link: grandpa_link,
network, sync: sync_service.clone(),
voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(), network: network.clone(),
voting_rule: sc_consensus_grandpa::VotingRulesBuilder::default().build(),
prometheus_registry, prometheus_registry,
shared_voter_state: SharedVoterState::empty(), shared_voter_state,
telemetry: telemetry.as_ref().map(|x| x.handle()), telemetry: telemetry.as_ref().map(|x| x.handle()),
notification_service: grandpa_notification_service,
offchain_tx_pool_factory: sc_transaction_pool_api::OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
),
}; };
// the GRANDPA voter task is considered infallible, i.e. // the GRANDPA voter task is considered infallible, i.e.
...@@ -650,11 +726,56 @@ where ...@@ -650,11 +726,56 @@ where
task_manager.spawn_essential_handle().spawn_blocking( task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter", "grandpa-voter",
None, None,
sc_finality_grandpa::run_grandpa_voter(grandpa_config)?, sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?,
); );
} }
network_starter.start_network(); // Get the endpoint list from file first
let mut duniter_endpoints: DuniterEndpoints = match duniter_options.public_endpoints {
None => DuniterEndpoints::truncate_from(vec![]),
Some(path) => fs::read_to_string(path)
.map(|s| {
serde_json::from_str::<Peering>(&s).expect("Failed to parse Duniter endpoints")
})
.map(|p| p.endpoints)
.expect("Failed to read Duniter endpoints"),
};
// Then add through the specific options
if let Some(rpc_endoint) = duniter_options.public_rpc {
if duniter_endpoints
.try_push(DuniterEndpoint {
protocol: RPC.into(),
address: rpc_endoint,
})
.is_err()
{
error!("Could not add RPC endpoint, too much endpoints already");
}
}
if let Some(squid_endoint) = duniter_options.public_squid {
if duniter_endpoints
.try_push(DuniterEndpoint {
protocol: SQUID.into(),
address: squid_endoint,
})
.is_err()
{
error!("Could not add SQUID endpoint, too much endpoints already");
}
}
task_manager.spawn_handle().spawn_blocking(
"duniter-endpoint-gossip-handler",
Some("networking"),
crate::endpoint_gossip::handler::build::<Block, _>(
duniter_peering_params.notification_service,
network.clone(),
shared_duniter_peerings_state.listen(),
None, // We don't send command for now
duniter_endpoints,
)
.run(),
);
log::info!("***** Duniter has fully started *****"); log::info!("***** Duniter has fully started *****");
...@@ -685,16 +806,15 @@ impl client::ExecuteWithClient for RevertConsensus { ...@@ -685,16 +806,15 @@ impl client::ExecuteWithClient for RevertConsensus {
fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output
where where
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>, Backend: sc_client_api::Backend<Block>,
Backend: sc_client_api::Backend<Block> + 'static, Backend::State: sc_client_api::StateBackend<BlakeTwo256>,
Backend::State: sp_api::StateBackend<BlakeTwo256>, Api: RuntimeApiCollection,
Api: RuntimeApiCollection<StateBackend = Backend::State>,
Client: client::AbstractClient<Block, Backend, Api = Api> + 'static, Client: client::AbstractClient<Block, Backend, Api = Api> + 'static,
{ {
// Revert consensus-related components. // Revert consensus-related components.
// The operations are not correlated, thus call order is not relevant. // The operations are not correlated, thus call order is not relevant.
babe::revert(client.clone(), self.backend, self.blocks)?; sc_consensus_babe::revert(client.clone(), self.backend, self.blocks)?;
sc_finality_grandpa::revert(client, self.blocks)?; sc_consensus_grandpa::revert(client, self.blocks)?;
Ok(()) Ok(())
} }
} }
...@@ -15,15 +15,18 @@ ...@@ -15,15 +15,18 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use common_runtime::{AccountId, Balance, Block, BlockNumber, Hash, Header, Index}; use common_runtime::{AccountId, Balance, Block, BlockNumber, Hash, Header, Index};
use sc_client_api::{AuxStore, Backend as BackendT, BlockchainEvents, KeyIterator, UsageProvider}; use sc_client_api::{
use sp_api::{CallApiAt, NumberFor, ProvideRuntimeApi}; AuxStore, Backend as BackendT, BlockBackend, BlockchainEvents, KeysIter, MerkleValue,
PairsIter, UsageProvider,
};
use sp_api::{CallApiAt, ProvideRuntimeApi};
use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_blockchain::{HeaderBackend, HeaderMetadata};
use sp_consensus::BlockStatus; use sp_consensus::BlockStatus;
use sp_core::{Encode, Pair}; use sp_core::{Encode, Pair};
use sp_runtime::{ use sp_runtime::{
generic::{BlockId, SignedBlock}, generic::SignedBlock,
traits::{BlakeTwo256, Block as BlockT}, traits::{BlakeTwo256, Block as BlockT},
Justifications, Justifications, SaturatedConversion,
}; };
use sp_storage::{ChildInfo, StorageData, StorageKey}; use sp_storage::{ChildInfo, StorageData, StorageKey};
use std::sync::Arc; use std::sync::Arc;
...@@ -38,15 +41,15 @@ pub trait AbstractClient<Block, Backend>: ...@@ -38,15 +41,15 @@ pub trait AbstractClient<Block, Backend>:
+ Sync + Sync
+ ProvideRuntimeApi<Block> + ProvideRuntimeApi<Block>
+ HeaderBackend<Block> + HeaderBackend<Block>
+ CallApiAt<Block, StateBackend = Backend::State> + CallApiAt<Block>
+ AuxStore + AuxStore
+ UsageProvider<Block> + UsageProvider<Block>
+ HeaderMetadata<Block, Error = sp_blockchain::Error> + HeaderMetadata<Block, Error = sp_blockchain::Error>
where where
Block: BlockT, Block: BlockT,
Backend: BackendT<Block>, Backend: BackendT<Block>,
Backend::State: sp_api::StateBackend<BlakeTwo256>, Backend::State: sc_client_api::StateBackend<BlakeTwo256>,
Self::Api: RuntimeApiCollection<StateBackend = Backend::State>, Self::Api: RuntimeApiCollection,
{ {
} }
...@@ -54,7 +57,7 @@ impl<Block, Backend, Client> AbstractClient<Block, Backend> for Client ...@@ -54,7 +57,7 @@ impl<Block, Backend, Client> AbstractClient<Block, Backend> for Client
where where
Block: BlockT, Block: BlockT,
Backend: BackendT<Block>, Backend: BackendT<Block>,
Backend::State: sp_api::StateBackend<BlakeTwo256>, Backend::State: sc_client_api::StateBackend<BlakeTwo256>,
Client: BlockchainEvents<Block> Client: BlockchainEvents<Block>
+ ProvideRuntimeApi<Block> + ProvideRuntimeApi<Block>
+ HeaderBackend<Block> + HeaderBackend<Block>
...@@ -63,9 +66,9 @@ where ...@@ -63,9 +66,9 @@ where
+ Sized + Sized
+ Send + Send
+ Sync + Sync
+ CallApiAt<Block, StateBackend = Backend::State> + CallApiAt<Block>
+ HeaderMetadata<Block, Error = sp_blockchain::Error>, + HeaderMetadata<Block, Error = sp_blockchain::Error>,
Client::Api: RuntimeApiCollection<StateBackend = Backend::State>, Client::Api: RuntimeApiCollection,
{ {
} }
...@@ -100,10 +103,9 @@ pub trait ExecuteWithClient { ...@@ -100,10 +103,9 @@ pub trait ExecuteWithClient {
/// Execute whatever should be executed with the given client instance. /// Execute whatever should be executed with the given client instance.
fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output
where where
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
Backend: sc_client_api::Backend<Block> + 'static, Backend: sc_client_api::Backend<Block> + 'static,
Backend::State: sp_api::StateBackend<BlakeTwo256>, Backend::State: sc_client_api::StateBackend<BlakeTwo256>,
Api: crate::service::RuntimeApiCollection<StateBackend = Backend::State>, Api: crate::service::RuntimeApiCollection,
Client: AbstractClient<Block, Backend, Api = Api> + 'static; Client: AbstractClient<Block, Backend, Api = Api> + 'static;
} }
...@@ -123,12 +125,9 @@ pub trait RuntimeApiCollection: ...@@ -123,12 +125,9 @@ pub trait RuntimeApiCollection:
+ sp_session::SessionKeys<Block> + sp_session::SessionKeys<Block>
+ sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> + sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index> + substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>
where
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
{ {
} }
impl<Api> RuntimeApiCollection for Api impl<Api> RuntimeApiCollection for Api where
where
Api: pallet_grandpa::fg_primitives::GrandpaApi<Block> Api: pallet_grandpa::fg_primitives::GrandpaApi<Block>
+ pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance>
+ sp_api::ApiExt<Block> + sp_api::ApiExt<Block>
...@@ -139,20 +138,21 @@ where ...@@ -139,20 +138,21 @@ where
+ sp_offchain::OffchainWorkerApi<Block> + sp_offchain::OffchainWorkerApi<Block>
+ sp_session::SessionKeys<Block> + sp_session::SessionKeys<Block>
+ sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> + sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block>
+ substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>, + substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>
<Self as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
{ {
} }
/// A client instance. /// A client instance.
#[derive(Clone)] #[derive(Clone)]
pub enum Client { pub enum Client {
#[cfg(feature = "g1")] Client(
G1(Arc<super::FullClient<g1_runtime::RuntimeApi, super::G1Executor>>), Arc<
#[cfg(feature = "gtest")] super::FullClient<
GTest(Arc<super::FullClient<gtest_runtime::RuntimeApi, super::GTestExecutor>>), super::runtime_executor::runtime::RuntimeApi,
#[cfg(feature = "gdev")] super::runtime_executor::Executor,
GDev(Arc<super::FullClient<gdev_runtime::RuntimeApi, super::GDevExecutor>>), >,
>,
),
} }
macro_rules! with_client { macro_rules! with_client {
...@@ -164,22 +164,8 @@ macro_rules! with_client { ...@@ -164,22 +164,8 @@ macro_rules! with_client {
} }
} => { } => {
match $self { match $self {
#[cfg(feature = "g1")] Self::Client($client) => {
Self::G1($client) => {
#[allow(unused_imports)]
use g1_runtime as runtime;
$( $code )*
}
#[cfg(feature = "gtest")]
Self::GTest($client) => {
#[allow(unused_imports)]
use gtest_runtime as runtime;
$( $code )*
}
#[cfg(feature = "gdev")]
Self::GDev($client) => {
#[allow(unused_imports)] #[allow(unused_imports)]
use gdev_runtime as runtime;
$( $code )* $( $code )*
} }
} }
...@@ -198,38 +184,32 @@ impl ClientHandle for Client { ...@@ -198,38 +184,32 @@ impl ClientHandle for Client {
} }
} }
#[cfg(feature = "g1")] impl
impl From<Arc<super::FullClient<g1_runtime::RuntimeApi, super::G1Executor>>> for Client { From<
fn from(client: Arc<super::FullClient<g1_runtime::RuntimeApi, super::G1Executor>>) -> Self { Arc<
Self::G1(client) super::FullClient<
} super::runtime_executor::runtime::RuntimeApi,
} super::runtime_executor::Executor,
>,
#[cfg(feature = "gtest")] >,
impl From<Arc<super::FullClient<gtest_runtime::RuntimeApi, super::GTestExecutor>>> for Client { > for Client
{
fn from( fn from(
client: Arc<super::FullClient<gtest_runtime::RuntimeApi, super::GTestExecutor>>, client: Arc<
super::FullClient<
super::runtime_executor::runtime::RuntimeApi,
super::runtime_executor::Executor,
>,
>,
) -> Self { ) -> Self {
Self::GTest(client) Self::Client(client)
}
}
#[cfg(feature = "gdev")]
impl From<Arc<super::FullClient<gdev_runtime::RuntimeApi, super::GDevExecutor>>> for Client {
fn from(client: Arc<super::FullClient<gdev_runtime::RuntimeApi, super::GDevExecutor>>) -> Self {
Self::GDev(client)
} }
} }
macro_rules! match_client { macro_rules! match_client {
($self:ident, $method:ident($($param:ident),*)) => { ($self:ident, $method:ident($($param:ident),*)) => {
match $self { match $self {
#[cfg(feature = "g1")] Self::Client(client) => client.$method($($param),*),
Self::G1(client) => client.$method($($param),*),
#[cfg(feature = "gtest")]
Self::GTest(client) => client.$method($($param),*),
#[cfg(feature = "gdev")]
Self::GDev(client) => client.$method($($param),*),
} }
}; };
} }
...@@ -237,50 +217,56 @@ macro_rules! match_client { ...@@ -237,50 +217,56 @@ macro_rules! match_client {
impl sc_client_api::BlockBackend<Block> for Client { impl sc_client_api::BlockBackend<Block> for Client {
fn block_body( fn block_body(
&self, &self,
id: &BlockId<Block>, hash: <Block as BlockT>::Hash,
) -> sp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> { ) -> sp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
match_client!(self, block_body(id)) match_client!(self, block_body(hash))
} }
fn block_indexed_body( fn block_indexed_body(
&self, &self,
id: &BlockId<Block>, hash: <Block as BlockT>::Hash,
) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> { ) -> sp_blockchain::Result<Option<Vec<Vec<u8>>>> {
match_client!(self, block_indexed_body(id)) match_client!(self, block_indexed_body(hash))
} }
fn block(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<SignedBlock<Block>>> { fn block(
match_client!(self, block(id)) &self,
hash: <Block as BlockT>::Hash,
) -> sp_blockchain::Result<Option<SignedBlock<Block>>> {
match_client!(self, block(hash))
} }
fn block_status(&self, id: &BlockId<Block>) -> sp_blockchain::Result<BlockStatus> { fn block_status(&self, hash: <Block as BlockT>::Hash) -> sp_blockchain::Result<BlockStatus> {
match_client!(self, block_status(id)) match_client!(self, block_status(hash))
} }
fn justifications(&self, id: &BlockId<Block>) -> sp_blockchain::Result<Option<Justifications>> { fn justifications(
match_client!(self, justifications(id)) &self,
hash: <Block as BlockT>::Hash,
) -> sp_blockchain::Result<Option<Justifications>> {
match_client!(self, justifications(hash))
} }
fn block_hash( fn block_hash(
&self, &self,
number: NumberFor<Block>, number: sp_runtime::traits::NumberFor<Block>,
) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> { ) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> {
match_client!(self, block_hash(number)) match_client!(self, block_hash(number))
} }
fn indexed_transaction( fn indexed_transaction(
&self, &self,
hash: &<Block as BlockT>::Hash, id: <Block as BlockT>::Hash,
) -> sp_blockchain::Result<Option<Vec<u8>>> { ) -> sp_blockchain::Result<Option<Vec<u8>>> {
match_client!(self, indexed_transaction(hash)) match_client!(self, indexed_transaction(id))
} }
fn has_indexed_transaction( /*fn has_indexed_transaction(
&self, &self,
hash: &<Block as BlockT>::Hash, hash: &<Block as BlockT>::Hash,
) -> sp_blockchain::Result<bool> { ) -> sp_blockchain::Result<bool> {
match_client!(self, has_indexed_transaction(hash)) match_client!(self, has_indexed_transaction(hash))
} }*/
fn requires_full_sync(&self) -> bool { fn requires_full_sync(&self) -> bool {
match_client!(self, requires_full_sync()) match_client!(self, requires_full_sync())
...@@ -307,55 +293,77 @@ trait BenchmarkCallSigner<RuntimeCall: Encode + Clone, Signer: Pair> { ...@@ -307,55 +293,77 @@ trait BenchmarkCallSigner<RuntimeCall: Encode + Clone, Signer: Pair> {
) -> sp_runtime::OpaqueExtrinsic; ) -> sp_runtime::OpaqueExtrinsic;
} }
#[cfg(feature = "g1")]
type FullClient = super::FullClient<
super::runtime_executor::runtime::RuntimeApi,
super::runtime_executor::Executor,
>;
#[cfg(feature = "gdev")] #[cfg(feature = "gdev")]
impl BenchmarkCallSigner<gdev_runtime::RuntimeCall, sp_core::sr25519::Pair> type FullClient = super::FullClient<
for super::FullClient<gdev_runtime::RuntimeApi, super::GDevExecutor> super::runtime_executor::runtime::RuntimeApi,
super::runtime_executor::Executor,
>;
#[cfg(feature = "gtest")]
type FullClient = super::FullClient<
super::runtime_executor::runtime::RuntimeApi,
super::runtime_executor::Executor,
>;
#[cfg(any(feature = "gdev", feature = "gtest", feature = "g1"))]
impl BenchmarkCallSigner<super::runtime_executor::runtime::RuntimeCall, sp_core::sr25519::Pair>
for FullClient
{ {
fn sign_call( fn sign_call(
&self, &self,
call: gdev_runtime::RuntimeCall, call: super::runtime_executor::runtime::RuntimeCall,
nonce: u32, nonce: u32,
current_block: u64, current_block: u64,
period: u64, period: u64,
genesis: sp_core::H256, genesis: sp_core::H256,
acc: sp_core::sr25519::Pair, acc: sp_core::sr25519::Pair,
) -> sp_runtime::OpaqueExtrinsic { ) -> sp_runtime::OpaqueExtrinsic {
use gdev_runtime as runtime; let tx_ext: super::runtime_executor::runtime::TxExtension = (
frame_system::CheckNonZeroSender::<super::runtime_executor::runtime::Runtime>::new(),
let extra: runtime::SignedExtra = ( frame_system::CheckSpecVersion::<super::runtime_executor::runtime::Runtime>::new(),
frame_system::CheckNonZeroSender::<runtime::Runtime>::new(), frame_system::CheckTxVersion::<super::runtime_executor::runtime::Runtime>::new(),
frame_system::CheckSpecVersion::<runtime::Runtime>::new(), frame_system::CheckGenesis::<super::runtime_executor::runtime::Runtime>::new(),
frame_system::CheckTxVersion::<runtime::Runtime>::new(), frame_system::CheckEra::<super::runtime_executor::runtime::Runtime>::from(
frame_system::CheckGenesis::<runtime::Runtime>::new(),
frame_system::CheckMortality::<runtime::Runtime>::from(
sp_runtime::generic::Era::mortal(period, current_block), sp_runtime::generic::Era::mortal(period, current_block),
), ),
frame_system::CheckNonce::<runtime::Runtime>::from(nonce).into(), pallet_oneshot_account::CheckNonce::<super::runtime_executor::runtime::Runtime>::from(
frame_system::CheckWeight::<runtime::Runtime>::new(), frame_system::CheckNonce::<super::runtime_executor::runtime::Runtime>::from(nonce),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0), ),
frame_system::CheckWeight::<super::runtime_executor::runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<
super::runtime_executor::runtime::Runtime,
>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<
super::runtime_executor::runtime::Runtime,
>::new(false),
); );
let payload = sp_runtime::generic::SignedPayload::from_raw( let payload = sp_runtime::generic::SignedPayload::from_raw(
call.clone(), call.clone(),
extra.clone(), tx_ext.clone(),
( (
(), (),
runtime::VERSION.spec_version, super::runtime_executor::runtime::VERSION.spec_version,
runtime::VERSION.transaction_version, super::runtime_executor::runtime::VERSION.transaction_version,
genesis,
genesis, genesis,
self.chain_info().best_hash,
(), (),
(), (),
(), (),
None,
), ),
); );
let signature = payload.using_encoded(|p| acc.sign(p)); let signature = payload.using_encoded(|p| acc.sign(p));
runtime::UncheckedExtrinsic::new_signed( super::runtime_executor::runtime::UncheckedExtrinsic::new_signed(
call, call,
sp_runtime::AccountId32::from(acc.public()).into(), sp_runtime::AccountId32::from(acc.public()).into(),
common_runtime::Signature::Sr25519(signature), common_runtime::Signature::Sr25519(signature),
extra, tx_ext,
) )
.into() .into()
} }
...@@ -365,35 +373,38 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for Client { ...@@ -365,35 +373,38 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for Client {
fn pallet(&self) -> &str { fn pallet(&self) -> &str {
"system" "system"
} }
fn extrinsic(&self) -> &str { fn extrinsic(&self) -> &str {
"remark" "remark"
} }
fn build(&self, nonce: u32) -> std::result::Result<sp_runtime::OpaqueExtrinsic, &'static str> { fn build(&self, nonce: u32) -> std::result::Result<sp_runtime::OpaqueExtrinsic, &'static str> {
with_client! { with_client! {
self, client, { self, client, {
let call = runtime::RuntimeCall::System(runtime::SystemCall::remark { remark: vec![] }); let call = super::runtime_executor::runtime::RuntimeCall::System(super::runtime_executor::runtime::SystemCall::remark { remark: vec![] });
let signer = sp_keyring::Sr25519Keyring::Bob.pair(); let signer = sp_keyring::Sr25519Keyring::Bob.pair();
let period = runtime::BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; let period = super::runtime_executor::runtime::BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;
let genesis = client.usage_info().chain.best_hash; let genesis = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed");
let best_block = client.chain_info().best_number;
Ok(client.sign_call(call, nonce, 0, period, genesis, signer)) Ok(client.sign_call(call, nonce, best_block.saturated_into(), period, genesis, signer))
} }
} }
} }
} }
impl sp_blockchain::HeaderBackend<Block> for Client { impl sp_blockchain::HeaderBackend<Block> for Client {
fn header(&self, id: BlockId<Block>) -> sp_blockchain::Result<Option<Header>> { fn header(&self, hash: Hash) -> sp_blockchain::Result<Option<Header>> {
let id = &id; match_client!(self, header(hash))
match_client!(self, header(id))
} }
fn info(&self) -> sp_blockchain::Info<Block> { fn info(&self) -> sp_blockchain::Info<Block> {
match_client!(self, info()) match_client!(self, info())
} }
fn status(&self, id: BlockId<Block>) -> sp_blockchain::Result<sp_blockchain::BlockStatus> { fn status(&self, hash: Hash) -> sp_blockchain::Result<sp_blockchain::BlockStatus> {
match_client!(self, status(id)) match_client!(self, status(hash))
} }
fn number(&self, hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> { fn number(&self, hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> {
...@@ -408,87 +419,92 @@ impl sp_blockchain::HeaderBackend<Block> for Client { ...@@ -408,87 +419,92 @@ impl sp_blockchain::HeaderBackend<Block> for Client {
impl sc_client_api::StorageProvider<Block, super::FullBackend> for Client { impl sc_client_api::StorageProvider<Block, super::FullBackend> for Client {
fn storage( fn storage(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
key: &StorageKey, key: &StorageKey,
) -> sp_blockchain::Result<Option<StorageData>> { ) -> sp_blockchain::Result<Option<StorageData>> {
match_client!(self, storage(id, key)) match_client!(self, storage(hash, key))
} }
fn storage_keys( fn storage_keys(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
key_prefix: &StorageKey, key_prefix: Option<&StorageKey>,
) -> sp_blockchain::Result<Vec<StorageKey>> { start_key: Option<&StorageKey>,
match_client!(self, storage_keys(id, key_prefix)) ) -> sp_blockchain::Result<
KeysIter<<super::FullBackend as sc_client_api::Backend<Block>>::State, Block>,
> {
match_client!(self, storage_keys(hash, key_prefix, start_key))
} }
fn storage_hash( fn storage_hash(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
key: &StorageKey, key: &StorageKey,
) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> { ) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> {
match_client!(self, storage_hash(id, key)) match_client!(self, storage_hash(hash, key))
} }
fn storage_pairs( fn storage_pairs(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
key_prefix: &StorageKey, key_prefix: Option<&StorageKey>,
) -> sp_blockchain::Result<Vec<(StorageKey, StorageData)>> {
match_client!(self, storage_pairs(id, key_prefix))
}
fn storage_keys_iter<'a>(
&self,
id: &<Block as BlockT>::Hash,
prefix: Option<&'a StorageKey>,
start_key: Option<&StorageKey>, start_key: Option<&StorageKey>,
) -> sp_blockchain::Result< ) -> sp_blockchain::Result<
KeyIterator<'a, <super::FullBackend as sc_client_api::Backend<Block>>::State, Block>, PairsIter<<super::FullBackend as sc_client_api::Backend<Block>>::State, Block>,
> { > {
match_client!(self, storage_keys_iter(id, prefix, start_key)) match_client!(self, storage_pairs(hash, key_prefix, start_key))
} }
fn child_storage( fn child_storage(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
child_info: &ChildInfo, child_info: &ChildInfo,
key: &StorageKey, key: &StorageKey,
) -> sp_blockchain::Result<Option<StorageData>> { ) -> sp_blockchain::Result<Option<StorageData>> {
match_client!(self, child_storage(id, child_info, key)) match_client!(self, child_storage(hash, child_info, key))
} }
fn child_storage_keys( fn child_storage_keys(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
child_info: &ChildInfo,
key_prefix: &StorageKey,
) -> sp_blockchain::Result<Vec<StorageKey>> {
match_client!(self, child_storage_keys(id, child_info, key_prefix))
}
fn child_storage_keys_iter<'a>(
&self,
id: &<Block as BlockT>::Hash,
child_info: ChildInfo, child_info: ChildInfo,
prefix: Option<&'a StorageKey>, key_prefix: Option<&StorageKey>,
start_key: Option<&StorageKey>, start_key: Option<&StorageKey>,
) -> sp_blockchain::Result< ) -> sp_blockchain::Result<
KeyIterator<'a, <super::FullBackend as sc_client_api::Backend<Block>>::State, Block>, KeysIter<<super::FullBackend as sc_client_api::Backend<Block>>::State, Block>,
> { > {
match_client!( match_client!(
self, self,
child_storage_keys_iter(id, child_info, prefix, start_key) child_storage_keys(hash, child_info, key_prefix, start_key)
) )
} }
fn child_storage_hash( fn child_storage_hash(
&self, &self,
id: &<Block as BlockT>::Hash, hash: <Block as BlockT>::Hash,
child_info: &ChildInfo, child_info: &ChildInfo,
key: &StorageKey, key: &StorageKey,
) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> { ) -> sp_blockchain::Result<Option<<Block as BlockT>::Hash>> {
match_client!(self, child_storage_hash(id, child_info, key)) match_client!(self, child_storage_hash(hash, child_info, key))
}
// Given a block's hash and a key, return the closest merkle value.
fn closest_merkle_value(
&self,
hash: <Block as BlockT>::Hash,
key: &StorageKey,
) -> sp_blockchain::Result<Option<MerkleValue<<Block as BlockT>::Hash>>> {
match_client!(self, closest_merkle_value(hash, key))
}
// Given a block's hash and a key and a child storage key, return the closest merkle value.
fn child_closest_merkle_value(
&self,
hash: <Block as BlockT>::Hash,
child_info: &ChildInfo,
key: &StorageKey,
) -> sp_blockchain::Result<Option<MerkleValue<<Block as BlockT>::Hash>>> {
match_client!(self, child_closest_merkle_value(hash, child_info, key))
} }
} }
...@@ -510,7 +526,7 @@ pub fn benchmark_inherent_data( ...@@ -510,7 +526,7 @@ pub fn benchmark_inherent_data(
// Assume that all runtimes have the `timestamp` pallet. // Assume that all runtimes have the `timestamp` pallet.
let d = std::time::Duration::from_millis(0); let d = std::time::Duration::from_millis(0);
let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); let timestamp = sp_timestamp::InherentDataProvider::new(d.into());
timestamp.provide_inherent_data(&mut inherent_data)?; futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data))?;
Ok(inherent_data) Ok(inherent_data)
} }
...@@ -6,18 +6,20 @@ Duniter uses some [parity pallets](https://github.com/duniter/substrate/tree/mas ...@@ -6,18 +6,20 @@ Duniter uses some [parity pallets](https://github.com/duniter/substrate/tree/mas
These pallets are at the core of Duniter/Ğ1 currency These pallets are at the core of Duniter/Ğ1 currency
- **`authority-members`** Duniter authorities are not selected with staking but through a smith web of trust. - **[`authority-members`](https://doc-duniter-org.ipns.pagu.re/pallet_authority_members/index.html)** Duniter authorities are not selected with staking but through a smith web of trust.
- **`certification`** Certifications are the "edges" of Duniter's dynamic directed graph. They mean the acceptation of a Licence. - **[`certification`](https://doc-duniter-org.ipns.pagu.re/pallet_certification/index.html)** Certifications are the "edges" of Duniter's dynamic directed graph. They mean the acceptation of a Licence.
- **`duniter-account`** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `RandomId`. - **[`duniter-account`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_account/index.html)** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `linked_idty`.
- **`duniter-wot`** Merges identities, membership, certifications and distance pallets to implement Duniter Web of Trust. - **[`duniter-wot`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_wot/index.html)** Merges identities, membership, certifications and distance pallets to implement Duniter Web of Trust.
- **`duniter-distance`** Offchain worker used to compute distance criterion. - **[`distance`](https://doc-duniter-org.ipns.pagu.re/pallet_distance/index.html)** Publishes median of distance computation results provided by inherents coming from `distance-oracle` workers.
- **`identity`** Identities are the "nodes" of Duniter's dynamic directed graph. They are one-to-one mapping to human being. - **[`identity`](https://doc-duniter-org.ipns.pagu.re/pallet_identity/index.html)** Identities are the "nodes" of Duniter's dynamic directed graph. They are one-to-one mapping to human being.
- **`membership`** Membership defines the state of identities. They can be member or not of the different WoTs. - **[`membership`](https://doc-duniter-org.ipns.pagu.re/pallet_membership/index.html)** Membership defines the state of identities. They can be member or not of the different WoTs.
- **`universal-dividend`** UD is at the basis of Ğ1 "libre currency". It is both a kind of "basic income" and a measure unit. - **[`universal-dividend`](https://doc-duniter-org.ipns.pagu.re/pallet_universal_dividend/index.html)** UD is at the basis of Ğ1 "libre currency". It is both a kind of "basic income" and a measure unit.
## Functional pallets ## Functional pallets
- **`duniter-test-parameters`** Test parameters only used in ĞDev to allow tweaking parameters more easily. - **[`duniter-test-parameters`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_test_parameters/index.html)** Test parameters only used in ĞDev to allow tweaking parameters more easily.
- **`oneshot-account`** Oneshot accounts are light accounts only used once for anonimity or convenience use case. - **[`offences`](https://doc-duniter-org.ipns.pagu.re/pallet_offences/index.html)** Sorts offences that will be executed by the `authority-members` pallet.
- **`provide-randomness`** Lets blockchain users ask for a verifiable random number. - **[`oneshot-account`](https://doc-duniter-org.ipns.pagu.re/pallet_oneshot_account/index.html)** Oneshot accounts are light accounts only used once for anonymity or convenience use case.
- **`upgrade-origin`** Allows some origins to dispatch a call as root. - **[`provide-randomness`](https://doc-duniter-org.ipns.pagu.re/pallet_provide_randomness/index.html)** Lets blockchain users ask for a verifiable random number.
\ No newline at end of file - **[`session-benchmarking`](https://doc-duniter-org.ipns.pagu.re/pallet_session_benchmarking/index.html)** Benchmarks the session pallet.
- **[`upgrade-origin`](https://doc-duniter-org.ipns.pagu.re/pallet_upgrade_origin/index.html)** Allows some origins to dispatch a call as root.
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet authority members.' description = "duniter pallet authority members"
edition = "2021" edition.workspace = true
homepage = 'https://duniter.org' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-authority-members' name = "pallet-authority-members"
readme = 'README.md' repository.workspace = true
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' version.workspace = true
version = '3.0.0'
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-offences/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-staking/runtime-benchmarks",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "frame-benchmarking?/std",
'frame-system/std', "frame-support/std",
'frame-benchmarking/std', "frame-system/std",
'log/std', "log/std",
'pallet-session/std', "pallet-offences/std",
'serde', "pallet-session/std",
'sp-core/std', "scale-info/std",
'sp-membership/std', "serde/std",
'sp-runtime/std', "sp-core/std",
'sp-staking/std', "sp-io/std",
'sp-std/std', "sp-runtime/std",
"sp-staking/std",
"sp-state-machine/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-offences/try-runtime",
"pallet-session/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# local
sp-membership = { path = "../../primitives/membership", default-features = false }
# crates.io
log = { version = "0.4.14", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = "3.1.5"
# substrate
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/duniter/substrate'
optional = true
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.pallet-session]
default-features = false
features = ["historical"]
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-staking]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] targets = ["x86_64-unknown-linux-gnu"]
### DEV ### [dependencies]
codec = { workspace = true, features = ["derive"] }
[dev-dependencies.maplit] frame-benchmarking = { workspace = true, optional = true }
version = '1.0.2' frame-support = { workspace = true }
frame-system = { workspace = true }
[dev-dependencies.serde] log = { workspace = true }
version = '1.0.119' pallet-offences = { workspace = true }
pallet-session = { workspace = true, features = ["historical"] }
[dev-dependencies.sp-io] scale-info = { workspace = true, features = ["derive"] }
git = 'https://github.com/duniter/substrate' serde = { workspace = true, features = ["derive"] }
branch = 'duniter-substrate-v0.9.32' sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
sp-state-machine = { workspace = true, default-features = true }
# Duniter authority members pallet
In a permissioned network, we have to define the set of authorities, and among these authorities, the ones taking part in the next session. That's what authority members pallet does. In practice:
- it manages a `Members` set with some custom rules
- it implements the `SessionManager` trait from the session frame pallet
## Entering the set of authorities
To become part of Duniter authorities, one has to complete these steps:
1. become member of the main web of trust
1. request membership to the smith sub wot
1. get enough certs to get smith membership
1. claim membership to the set of authorities
Then one can "go online" and "go offline" to enter or leave two sessions after.
## Staying in the set of authorities
If a smith is offline more than `MaxOfflineSessions`, he leaves the set of authorities.
\ No newline at end of file
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use crate::Pallet;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
#[benchmarks(
where
<T as Config>::MemberId: From<u32>,
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
#[benchmark]
fn go_offline() {
let id: T::MemberId = OnlineAuthorities::<T>::get()[0];
let caller: T::AccountId = Members::<T>::get(id).unwrap().owner_key;
#[extrinsic_call]
_(RawOrigin::Signed(caller));
assert_has_event::<T>(Event::<T>::MemberGoOffline { member: id }.into());
}
#[benchmark]
fn go_online() {
let id: T::MemberId = OnlineAuthorities::<T>::get()[0];
let caller: T::AccountId = Members::<T>::get(id).unwrap().owner_key;
OnlineAuthorities::<T>::mutate(|ids| {
ids.retain(|&x| x != id);
});
OutgoingAuthorities::<T>::mutate(|ids| {
ids.retain(|&x| x != id);
});
#[extrinsic_call]
_(RawOrigin::Signed(caller));
assert_has_event::<T>(Event::<T>::MemberGoOnline { member: id }.into());
}
#[benchmark]
fn set_session_keys() {
let id: T::MemberId = OnlineAuthorities::<T>::get()[0];
let caller: T::AccountId = Members::<T>::get(id).unwrap().owner_key;
let validator_id = T::ValidatorIdOf::convert(caller.clone()).unwrap();
let session_keys: T::Keys = pallet_session::NextKeys::<T>::get(validator_id).unwrap();
#[extrinsic_call]
_(RawOrigin::Signed(caller), session_keys);
}
#[benchmark]
fn remove_member() {
let id: T::MemberId = OnlineAuthorities::<T>::get()[0];
#[extrinsic_call]
_(RawOrigin::Root, id);
assert_has_event::<T>(Event::<T>::MemberRemoved { member: id }.into());
}
#[benchmark]
fn remove_member_from_blacklist() {
let id: T::MemberId = OnlineAuthorities::<T>::get()[0];
Blacklist::<T>::mutate(|blacklist| {
blacklist.push(id);
});
#[extrinsic_call]
_(RawOrigin::Root, id);
assert_has_event::<T>(Event::<T>::MemberRemovedFromBlacklist { member: id }.into());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(2), crate::mock::Test);
}
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the Slashing execution logic.
//!
//! Offences are sorted in the `offences` pallet.
//! The offences are executed here based. The offenders are disconnected and
//! can be added to a blacklist to avoid futur connection.
#![allow(clippy::type_complexity)]
use super::pallet::*;
use frame_support::{pallet_prelude::Weight, traits::Get};
use pallet_offences::{traits::OnOffenceHandler, SlashStrategy};
use sp_runtime::traits::Convert;
use sp_staking::{offence::OffenceDetails, SessionIndex};
impl<T: Config>
OnOffenceHandler<T::AccountId, pallet_session::historical::IdentificationTuple<T>, Weight>
for Pallet<T>
where
T: pallet_session::Config<ValidatorId = <T as frame_system::Config>::AccountId>,
{
fn on_offence(
offenders: &[OffenceDetails<
T::AccountId,
pallet_session::historical::IdentificationTuple<T>,
>],
strategy: SlashStrategy,
_slash_session: SessionIndex,
) -> Weight {
let mut consumed_weight = Weight::from_parts(0, 0);
let mut add_db_reads_writes = |reads, writes| {
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
};
match strategy {
SlashStrategy::Blacklist => {
for offender in offenders {
if let Some(member_id) = T::MemberIdOf::convert(offender.offender.0.clone()) {
Blacklist::<T>::mutate(|blacklist| {
if let Err(index) = blacklist.binary_search(&member_id) {
blacklist.insert(index, member_id);
Self::deposit_event(Event::MemberAddedToBlacklist {
member: member_id,
});
add_db_reads_writes(0, 1);
}
Self::insert_out(member_id);
add_db_reads_writes(2, 1);
});
}
}
}
SlashStrategy::Disconnect => {
for offender in offenders {
if let Some(member_id) = T::MemberIdOf::convert(offender.offender.0.clone()) {
Self::insert_out(member_id);
add_db_reads_writes(1, 1);
}
}
}
}
consumed_weight
}
}
...@@ -14,11 +14,40 @@ ...@@ -14,11 +14,40 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Authority Members Pallet
//!
//! In a permissioned network, defining the set of authorities and selecting validators for the next session is crucial.
//! The authority members pallet is responsible for this. Specifically, it:
//!
//! - Manages a `Members` set with custom rules.
//! - Implements the `SessionManager` trait from the FRAME session pallet.
//!
//! ## Entering the Set of Authorities
//!
//! To become part of Duniter authorities, one must follow these steps:
//!
//! 1. Become a member of the main web of trust.
//! 2. Request membership to the smith sub web of trust.
//! 3. Obtain enough certifications to gain smith membership.
//! 4. Claim membership to the set of authorities.
//!
//! After becoming an authority, one can "go online" and "go offline" to enter or leave two sessions later.
//!
//! ## Some Vocabulary
//!
//! *Smiths* are individuals allowed to forge blocks. Specifically, this entails:
//!
//! - **Smith** status is required to become an authority.
//! - **Authority** status is required to become a validator.
//! - **Validator** status is required to add blocks.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
pub mod impls;
pub mod traits; pub mod traits;
mod types; mod types;
pub mod weights;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
...@@ -26,33 +55,33 @@ mod mock; ...@@ -26,33 +55,33 @@ mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/*#[cfg(feature = "runtime-benchmarks")] mod benchmarking;
mod benchmarking;*/
pub use pallet::*; pub use pallet::*;
pub use sp_staking::SessionIndex;
pub use traits::*;
pub use types::*; pub use types::*;
pub use weights::WeightInfo;
use self::traits::*; use codec::alloc::borrow::ToOwned;
use frame_support::traits::Get; use frame_support::traits::Get;
use sp_runtime::traits::Convert; use scale_info::prelude::{collections::BTreeMap, vec, vec::Vec};
use sp_staking::SessionIndex; use sp_runtime::traits::{Convert, IsMember};
use sp_std::prelude::*;
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::pallet_prelude::*; use frame_support::{
use frame_support::traits::ValidatorRegistration; pallet_prelude::*,
use frame_support::traits::{StorageVersion, UnfilteredDispatchable}; traits::{StorageVersion, UnfilteredDispatchable, ValidatorRegistration},
};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use sp_runtime::traits::{Convert, IsMember};
use sp_std::collections::btree_map::BTreeMap;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)] #[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info] #[pallet::without_storage_info]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
...@@ -63,21 +92,36 @@ pub mod pallet { ...@@ -63,21 +92,36 @@ pub mod pallet {
pub trait Config: pub trait Config:
frame_system::Config + pallet_session::Config + pallet_session::historical::Config frame_system::Config + pallet_session::Config + pallet_session::historical::Config
{ {
type KeysWrapper: Parameter + Into<Self::Keys>; /// Specifies the type that determines membership status.
type IsMember: IsMember<Self::MemberId>; type IsMember: IsMember<Self::MemberId>;
/// Handler for when a new session is initiated.
type OnNewSession: OnNewSession; type OnNewSession: OnNewSession;
type OnRemovedMember: OnRemovedMember<Self::MemberId>;
/// Max number of authorities allowed /// Handler for when a member is removed from authorities.
type OnOutgoingMember: OnOutgoingMember<Self::MemberId>;
/// Handler for when a new member is added to authorities.
type OnIncomingMember: OnIncomingMember<Self::MemberId>;
/// Maximum number of authorities allowed.
#[pallet::constant] #[pallet::constant]
type MaxAuthorities: Get<u32>; type MaxAuthorities: Get<u32>;
#[pallet::constant]
type MaxKeysLife: Get<SessionIndex>; /// Type representing the identifier of a member.
#[pallet::constant]
type MaxOfflineSessions: Get<SessionIndex>;
type MemberId: Copy + Ord + MaybeSerializeDeserialize + Parameter; type MemberId: Copy + Ord + MaybeSerializeDeserialize + Parameter;
/// Converts an `AccountId` to an optional `MemberId`.
type MemberIdOf: Convert<Self::AccountId, Option<Self::MemberId>>; type MemberIdOf: Convert<Self::AccountId, Option<Self::MemberId>>;
/// Specifies the origin type required to remove a member.
type RemoveMemberOrigin: EnsureOrigin<Self::RuntimeOrigin>; type RemoveMemberOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
} }
// GENESIS STUFF // // GENESIS STUFF //
...@@ -87,7 +131,6 @@ pub mod pallet { ...@@ -87,7 +131,6 @@ pub mod pallet {
pub initial_authorities: BTreeMap<T::MemberId, (T::AccountId, bool)>, pub initial_authorities: BTreeMap<T::MemberId, (T::AccountId, bool)>,
} }
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> { impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
...@@ -97,13 +140,10 @@ pub mod pallet { ...@@ -97,13 +140,10 @@ pub mod pallet {
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
for (member_id, (account_id, _is_online)) in &self.initial_authorities { for (member_id, (account_id, _is_online)) in &self.initial_authorities {
Members::<T>::insert( Members::<T>::insert(member_id, MemberData::new_genesis(account_id.to_owned()));
member_id,
MemberData::new_genesis(T::MaxKeysLife::get(), account_id.to_owned()),
);
} }
let mut members_ids = self let mut members_ids = self
.initial_authorities .initial_authorities
...@@ -120,49 +160,37 @@ pub mod pallet { ...@@ -120,49 +160,37 @@ pub mod pallet {
.collect::<Vec<T::MemberId>>(); .collect::<Vec<T::MemberId>>();
members_ids.sort(); members_ids.sort();
AuthoritiesCounter::<T>::put(members_ids.len() as u32);
OnlineAuthorities::<T>::put(members_ids.clone()); OnlineAuthorities::<T>::put(members_ids.clone());
MustRotateKeysBefore::<T>::insert(T::MaxKeysLife::get(), members_ids);
} }
} }
// STORAGE // // STORAGE //
#[pallet::storage] /// The incoming authorities.
#[pallet::getter(fn account_id_of)]
pub type AccountIdOf<T: Config> =
StorageMap<_, Twox64Concat, T::MemberId, T::AccountId, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn authorities_counter)]
pub type AuthoritiesCounter<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn incoming)] #[pallet::getter(fn incoming)]
pub type IncomingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>; pub type IncomingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
/// The online authorities.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn online)] #[pallet::getter(fn online)]
pub type OnlineAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>; pub type OnlineAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
/// The outgoing authorities.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn outgoing)] #[pallet::getter(fn outgoing)]
pub type OutgoingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>; pub type OutgoingAuthorities<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
/// The member data.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn member)] #[pallet::getter(fn member)]
pub type Members<T: Config> = pub type Members<T: Config> =
StorageMap<_, Twox64Concat, T::MemberId, MemberData<T::AccountId>, OptionQuery>; StorageMap<_, Twox64Concat, T::MemberId, MemberData<T::AccountId>, OptionQuery>;
/// The blacklisted authorities.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn members_expire_on)] #[pallet::getter(fn blacklist)]
pub type MembersExpireOn<T: Config> = pub type Blacklist<T: Config> = StorageValue<_, Vec<T::MemberId>, ValueQuery>;
StorageMap<_, Twox64Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn must_rotate_keys_before)]
pub type MustRotateKeysBefore<T: Config> =
StorageMap<_, Twox64Concat, SessionIndex, Vec<T::MemberId>, ValueQuery>;
// HOOKS // // HOOKS //
...@@ -171,47 +199,47 @@ pub mod pallet { ...@@ -171,47 +199,47 @@ pub mod pallet {
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> { pub enum Event<T: Config> {
/// List of members who will enter the set of authorities at the next session. /// List of members scheduled to join the set of authorities in the next session.
/// [Vec<member_id>] IncomingAuthorities { members: Vec<T::MemberId> },
IncomingAuthorities(Vec<T::MemberId>), /// List of members leaving the set of authorities in the next session.
/// List of members who will leave the set of authorities at the next session. OutgoingAuthorities { members: Vec<T::MemberId> },
/// [Vec<member_id>]
OutgoingAuthorities(Vec<T::MemberId>),
/// A member will leave the set of authorities in 2 sessions. /// A member will leave the set of authorities in 2 sessions.
/// [member_id] MemberGoOffline { member: T::MemberId },
MemberGoOffline(T::MemberId), /// A member will join the set of authorities in 2 sessions.
/// A member will enter the set of authorities in 2 sessions. MemberGoOnline { member: T::MemberId },
/// [member_id] /// A member, who no longer has authority rights, will be removed from the authority set in 2 sessions.
MemberGoOnline(T::MemberId), MemberRemoved { member: T::MemberId },
/// A member has lost the right to be part of the authorities, /// A member has been removed from the blacklist.
/// this member will be removed from the authority set in 2 sessions. MemberRemovedFromBlacklist { member: T::MemberId },
/// [member_id] /// A member has been blacklisted.
MemberRemoved(T::MemberId), MemberAddedToBlacklist { member: T::MemberId },
} }
// ERRORS // // ERRORS //
#[pallet::error] #[pallet::error]
pub enum Error<T> { pub enum Error<T> {
/// Already incoming /// Member already incoming.
AlreadyIncoming, AlreadyIncoming,
/// Already online /// Member already online.
AlreadyOnline, AlreadyOnline,
/// Already outgoing /// Member already outgoing.
AlreadyOutgoing, AlreadyOutgoing,
/// Not found owner key /// Owner key is invalid as a member.
MemberIdNotFound, MemberIdNotFound,
/// Member not found /// Member is blacklisted.
MemberBlacklisted,
/// Member is not blacklisted.
MemberNotBlacklisted,
/// Member not found.
MemberNotFound, MemberNotFound,
/// Neither online nor scheduled /// Neither online nor scheduled.
NotOnlineNorIncoming, NotOnlineNorIncoming,
/// Not owner /// Not member.
NotOwner,
/// Not member
NotMember, NotMember,
/// Session keys not provided /// Session keys not provided.
SessionKeysNotProvided, SessionKeysNotProvided,
/// Too man aAuthorities /// Too many authorities.
TooManyAuthorities, TooManyAuthorities,
} }
...@@ -219,7 +247,9 @@ pub mod pallet { ...@@ -219,7 +247,9 @@ pub mod pallet {
#[pallet::call] #[pallet::call]
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
#[pallet::weight(1_000_000_000)] /// Request to leave the set of validators two sessions later.
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::go_offline())]
pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo { pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// Verification phase // // Verification phase //
let who = ensure_signed(origin)?; let who = ensure_signed(origin)?;
...@@ -228,29 +258,35 @@ pub mod pallet { ...@@ -228,29 +258,35 @@ pub mod pallet {
if !Members::<T>::contains_key(member_id) { if !Members::<T>::contains_key(member_id) {
return Err(Error::<T>::MemberNotFound.into()); return Err(Error::<T>::MemberNotFound.into());
} }
if Self::is_outgoing(member_id) { if Self::is_outgoing(&member_id) {
return Err(Error::<T>::AlreadyOutgoing.into()); return Err(Error::<T>::AlreadyOutgoing.into());
} }
let is_incoming = Self::is_incoming(member_id); let is_incoming = Self::is_incoming(&member_id);
if !is_incoming && !Self::is_online(member_id) { if !is_incoming && !Self::is_online(&member_id) {
return Err(Error::<T>::NotOnlineNorIncoming.into()); return Err(Error::<T>::NotOnlineNorIncoming.into());
} }
// Apply phase // // Apply phase //
if is_incoming { if is_incoming {
Self::remove_in(member_id); Self::remove_in(&member_id);
} else { } else {
Self::insert_out(member_id); Self::insert_out(member_id);
} }
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)]
/// Request to join the set of validators two sessions later.
#[pallet::call_index(1)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::go_online())]
pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo { pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
// Verification phase // // Verification phase //
let who = ensure_signed(origin)?; let who = ensure_signed(origin)?;
let member_id = Self::verify_ownership_and_membership(&who)?; let member_id = Self::verify_ownership_and_membership(&who)?;
if Self::is_blacklisted(&member_id) {
return Err(Error::<T>::MemberBlacklisted.into());
}
if !Members::<T>::contains_key(member_id) { if !Members::<T>::contains_key(member_id) {
return Err(Error::<T>::MemberNotFound.into()); return Err(Error::<T>::MemberNotFound.into());
} }
...@@ -260,20 +296,20 @@ pub mod pallet { ...@@ -260,20 +296,20 @@ pub mod pallet {
return Err(Error::<T>::SessionKeysNotProvided.into()); return Err(Error::<T>::SessionKeysNotProvided.into());
} }
if Self::is_incoming(member_id) { if Self::is_incoming(&member_id) {
return Err(Error::<T>::AlreadyIncoming.into()); return Err(Error::<T>::AlreadyIncoming.into());
} }
let is_outgoing = Self::is_outgoing(member_id); let is_outgoing = Self::is_outgoing(&member_id);
if Self::is_online(member_id) && !is_outgoing { if Self::is_online(&member_id) && !is_outgoing {
return Err(Error::<T>::AlreadyOnline.into()); return Err(Error::<T>::AlreadyOnline.into());
} }
if AuthoritiesCounter::<T>::get() >= T::MaxAuthorities::get() { if Self::authorities_counter() >= T::MaxAuthorities::get() {
return Err(Error::<T>::TooManyAuthorities.into()); return Err(Error::<T>::TooManyAuthorities.into());
} }
// Apply phase // // Apply phase //
if is_outgoing { if is_outgoing {
Self::remove_out(member_id); Self::remove_out(&member_id);
} else { } else {
Self::insert_in(member_id); Self::insert_in(member_id);
} }
...@@ -281,74 +317,86 @@ pub mod pallet { ...@@ -281,74 +317,86 @@ pub mod pallet {
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)] /// Declare new session keys to replace current ones.
pub fn set_session_keys( #[pallet::call_index(2)]
origin: OriginFor<T>, #[pallet::weight(<T as pallet::Config>::WeightInfo::set_session_keys())]
keys: T::KeysWrapper, pub fn set_session_keys(origin: OriginFor<T>, keys: T::Keys) -> DispatchResultWithPostInfo {
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin.clone())?; let who = ensure_signed(origin.clone())?;
let member_id = Self::verify_ownership_and_membership(&who)?; let member_id = Self::verify_ownership_and_membership(&who)?;
let _post_info = pallet_session::Call::<T>::set_keys { let _post_info = pallet_session::Call::<T>::set_keys {
keys: keys.into(), keys,
proof: vec![], proof: vec![],
} }
.dispatch_bypass_filter(origin)?; .dispatch_bypass_filter(origin)?;
let current_session_index = pallet_session::Pallet::<T>::current_index(); Members::<T>::insert(member_id, MemberData { owner_key: who });
let expire_on_session =
current_session_index.saturating_add(T::MaxOfflineSessions::get());
let must_rotate_keys_before =
current_session_index.saturating_add(T::MaxKeysLife::get());
Members::<T>::mutate_exists(member_id, |member_data_opt| {
let mut member_data = member_data_opt.get_or_insert(MemberData {
expire_on_session,
must_rotate_keys_before,
owner_key: who,
});
member_data.must_rotate_keys_before = must_rotate_keys_before;
});
MustRotateKeysBefore::<T>::append(must_rotate_keys_before, member_id);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)]
/// Remove a member from the set of validators.
#[pallet::call_index(3)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::remove_member())]
pub fn remove_member( pub fn remove_member(
origin: OriginFor<T>, origin: OriginFor<T>,
member_id: T::MemberId, member_id: T::MemberId,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
T::RemoveMemberOrigin::ensure_origin(origin)?; T::RemoveMemberOrigin::ensure_origin(origin)?;
let member_data = Members::<T>::get(member_id).ok_or(Error::<T>::NotMember)?; let member_data = Members::<T>::get(member_id).ok_or(Error::<T>::MemberNotFound)?;
Self::do_remove_member(member_id, member_data.owner_key); Self::do_remove_member(member_id, member_data.owner_key);
Ok(().into()) Ok(().into())
} }
/// Remove a member from the blacklist.
#[pallet::call_index(4)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::remove_member_from_blacklist())]
/// remove an identity from the blacklist
pub fn remove_member_from_blacklist(
origin: OriginFor<T>,
member_id: T::MemberId,
) -> DispatchResultWithPostInfo {
T::RemoveMemberOrigin::ensure_origin(origin)?;
Blacklist::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) {
members_ids.remove(index);
Self::deposit_event(Event::MemberRemovedFromBlacklist { member: member_id });
Ok(().into())
} else {
Err(Error::<T>::MemberNotBlacklisted.into())
}
})
}
} }
// PUBLIC FUNCTIONS // // PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Should be transactional // Should be transactional (if an error occurs, storage should not be modified at all)
// Execute the annotated function in a new storage transaction.
// The return type of the annotated function must be `Result`. All changes to storage performed
// by the annotated function are discarded if it returns `Err`, or committed if `Ok`.
/// Change the owner key of an authority member.
#[frame_support::transactional] #[frame_support::transactional]
pub fn change_owner_key( pub fn change_owner_key(
member_id: T::MemberId, member_id: T::MemberId,
new_owner_key: T::AccountId, new_owner_key: T::AccountId,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let old_owner_key = Members::<T>::mutate_exists(member_id, |maybe_member_data| { let old_owner_key = Members::<T>::try_mutate_exists(member_id, |maybe_member_data| {
if let Some(ref mut member_data) = maybe_member_data { let member_data = maybe_member_data
Ok(core::mem::replace( .as_mut()
.ok_or(Error::<T>::MemberNotFound)?;
Ok::<T::AccountId, Error<T>>(core::mem::replace(
&mut member_data.owner_key, &mut member_data.owner_key,
new_owner_key.clone(), new_owner_key.clone(),
)) ))
} else {
Err(Error::<T>::MemberNotFound)
}
})?; })?;
let validator_id = T::ValidatorIdOf::convert(old_owner_key.clone()) let validator_id = T::ValidatorIdOf::convert(old_owner_key.clone())
.ok_or(Error::<T>::MemberNotFound)?; .ok_or(pallet_session::Error::<T>::NoAssociatedValidatorId)?;
let session_keys = pallet_session::NextKeys::<T>::get(validator_id) let session_keys = pallet_session::NextKeys::<T>::get(validator_id)
.ok_or(Error::<T>::SessionKeysNotProvided)?; .ok_or(Error::<T>::SessionKeysNotProvided)?;
...@@ -357,7 +405,7 @@ pub mod pallet { ...@@ -357,7 +405,7 @@ pub mod pallet {
.dispatch_bypass_filter(frame_system::RawOrigin::Signed(old_owner_key).into())?; .dispatch_bypass_filter(frame_system::RawOrigin::Signed(old_owner_key).into())?;
// Set session keys // Set session keys
let _post_info = pallet_session::Call::<T>::set_keys { pallet_session::Call::<T>::set_keys {
keys: session_keys, keys: session_keys,
proof: vec![], proof: vec![],
} }
...@@ -365,20 +413,28 @@ pub mod pallet { ...@@ -365,20 +413,28 @@ pub mod pallet {
Ok(().into()) Ok(().into())
} }
/// Get the number of authorities.
pub fn authorities_counter() -> u32 {
let count = OnlineAuthorities::<T>::get().len() + IncomingAuthorities::<T>::get().len()
- OutgoingAuthorities::<T>::get().len();
count as u32
}
} }
// INTERNAL FUNCTIONS // // INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
fn do_remove_member(member_id: T::MemberId, owner_key: T::AccountId) -> Weight { /// Perform authority member removal.
if Self::is_online(member_id) { fn do_remove_member(member_id: T::MemberId, owner_key: T::AccountId) {
if Self::is_online(&member_id) {
// Trigger the member deletion for next session // Trigger the member deletion for next session
Self::insert_out(member_id); Self::insert_out(member_id);
} }
// remove all member data // remove all member data
Self::remove_in(member_id); Self::remove_in(&member_id);
Self::remove_online(member_id); Self::remove_online(&member_id);
Members::<T>::remove(member_id); Members::<T>::remove(member_id);
// Purge session keys // Purge session keys
...@@ -393,27 +449,10 @@ pub mod pallet { ...@@ -393,27 +449,10 @@ pub mod pallet {
} }
// Emit event // Emit event
Self::deposit_event(Event::MemberRemoved(member_id)); Self::deposit_event(Event::MemberRemoved { member: member_id });
let _ = T::OnRemovedMember::on_removed_member(member_id);
Weight::zero()
}
pub(super) fn expire_memberships(current_session_index: SessionIndex) {
for member_id in MembersExpireOn::<T>::take(current_session_index) {
if let Some(member_data) = Members::<T>::get(member_id) {
if member_data.expire_on_session == current_session_index {
Self::do_remove_member(member_id, member_data.owner_key);
}
}
}
for member_id in MustRotateKeysBefore::<T>::take(current_session_index) {
if let Some(member_data) = Members::<T>::get(member_id) {
if member_data.must_rotate_keys_before == current_session_index {
Self::do_remove_member(member_id, member_data.owner_key);
}
}
}
} }
/// Perform incoming authorities insertion.
fn insert_in(member_id: T::MemberId) -> bool { fn insert_in(member_id: T::MemberId) -> bool {
let not_already_inserted = IncomingAuthorities::<T>::mutate(|members_ids| { let not_already_inserted = IncomingAuthorities::<T>::mutate(|members_ids| {
if let Err(index) = members_ids.binary_search(&member_id) { if let Err(index) = members_ids.binary_search(&member_id) {
...@@ -424,12 +463,13 @@ pub mod pallet { ...@@ -424,12 +463,13 @@ pub mod pallet {
} }
}); });
if not_already_inserted { if not_already_inserted {
AuthoritiesCounter::<T>::mutate(|counter| *counter += 1); Self::deposit_event(Event::MemberGoOnline { member: member_id });
Self::deposit_event(Event::MemberGoOnline(member_id));
} }
not_already_inserted not_already_inserted
} }
fn insert_out(member_id: T::MemberId) -> bool {
/// Perform outgoing authority insertion.
pub fn insert_out(member_id: T::MemberId) -> bool {
let not_already_inserted = OutgoingAuthorities::<T>::mutate(|members_ids| { let not_already_inserted = OutgoingAuthorities::<T>::mutate(|members_ids| {
if let Err(index) = members_ids.binary_search(&member_id) { if let Err(index) = members_ids.binary_search(&member_id) {
members_ids.insert(index, member_id); members_ids.insert(index, member_id);
...@@ -439,53 +479,65 @@ pub mod pallet { ...@@ -439,53 +479,65 @@ pub mod pallet {
} }
}); });
if not_already_inserted { if not_already_inserted {
AuthoritiesCounter::<T>::mutate(|counter| { Self::deposit_event(Event::MemberGoOffline { member: member_id });
if *counter > 0 {
*counter -= 1
}
});
Self::deposit_event(Event::MemberGoOffline(member_id));
} }
not_already_inserted not_already_inserted
} }
fn is_incoming(member_id: T::MemberId) -> bool {
/// Check if member is incoming.
fn is_incoming(member_id: &T::MemberId) -> bool {
IncomingAuthorities::<T>::get() IncomingAuthorities::<T>::get()
.binary_search(&member_id) .binary_search(member_id)
.is_ok() .is_ok()
} }
fn is_online(member_id: T::MemberId) -> bool {
/// Check if member is online.
fn is_online(member_id: &T::MemberId) -> bool {
OnlineAuthorities::<T>::get() OnlineAuthorities::<T>::get()
.binary_search(&member_id) .binary_search(member_id)
.is_ok() .is_ok()
} }
fn is_outgoing(member_id: T::MemberId) -> bool {
/// Check if member is outgoing.
fn is_outgoing(member_id: &T::MemberId) -> bool {
OutgoingAuthorities::<T>::get() OutgoingAuthorities::<T>::get()
.binary_search(&member_id) .binary_search(member_id)
.is_ok() .is_ok()
} }
fn remove_in(member_id: T::MemberId) {
AuthoritiesCounter::<T>::mutate(|counter| counter.saturating_sub(1)); /// Check if member is blacklisted.
fn is_blacklisted(member_id: &T::MemberId) -> bool {
Blacklist::<T>::get().contains(member_id)
}
/// Perform removal from incoming authorities.
fn remove_in(member_id: &T::MemberId) {
IncomingAuthorities::<T>::mutate(|members_ids| { IncomingAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) { if let Ok(index) = members_ids.binary_search(member_id) {
members_ids.remove(index); members_ids.remove(index);
} }
}) })
} }
fn remove_online(member_id: T::MemberId) {
AuthoritiesCounter::<T>::mutate(|counter| counter.saturating_add(1)); /// Perform removal from online authorities.
fn remove_online(member_id: &T::MemberId) {
OnlineAuthorities::<T>::mutate(|members_ids| { OnlineAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) { if let Ok(index) = members_ids.binary_search(member_id) {
members_ids.remove(index); members_ids.remove(index);
} }
}); });
} }
fn remove_out(member_id: T::MemberId) {
/// Perform removal from outgoing authorities.
fn remove_out(member_id: &T::MemberId) {
OutgoingAuthorities::<T>::mutate(|members_ids| { OutgoingAuthorities::<T>::mutate(|members_ids| {
if let Ok(index) = members_ids.binary_search(&member_id) { if let Ok(index) = members_ids.binary_search(member_id) {
members_ids.remove(index); members_ids.remove(index);
} }
}); });
} }
/// Check that account is member.
fn verify_ownership_and_membership( fn verify_ownership_and_membership(
who: &T::AccountId, who: &T::AccountId,
) -> Result<T::MemberId, DispatchError> { ) -> Result<T::MemberId, DispatchError> {
...@@ -515,30 +567,37 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> { ...@@ -515,30 +567,37 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
/// ///
/// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other /// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other
/// words, a new session must always be planned before an ongoing one can be finished. /// words, a new session must always be planned before an ongoing one can be finished.
fn new_session(session_index: SessionIndex) -> Option<Vec<T::ValidatorId>> { fn new_session(_session_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
let members_ids_to_add = IncomingAuthorities::<T>::take(); let members_ids_to_add = IncomingAuthorities::<T>::take();
let members_ids_to_del = OutgoingAuthorities::<T>::take(); let members_ids_to_del = OutgoingAuthorities::<T>::take();
if members_ids_to_add.is_empty() { if members_ids_to_add.is_empty() && members_ids_to_del.is_empty() {
if members_ids_to_del.is_empty() {
// when no change to the set of autorities, return None // when no change to the set of autorities, return None
return None; return None;
} else {
for member_id in &members_ids_to_del {
// Apply MaxOfflineSessions rule
let expire_on_session =
session_index.saturating_add(T::MaxOfflineSessions::get());
Members::<T>::mutate_exists(member_id, |member_data_opt| {
if let Some(ref mut member_data) = member_data_opt {
member_data.expire_on_session = expire_on_session;
} }
// -- handle incoming members
// callback when smith is incoming
for member_id in members_ids_to_add.iter() {
T::OnIncomingMember::on_incoming_member(*member_id);
}
// a single event with all authorities if some
if !members_ids_to_add.is_empty() {
Self::deposit_event(Event::IncomingAuthorities {
members: members_ids_to_add.clone(),
}); });
MembersExpireOn::<T>::append(expire_on_session, member_id);
} }
Self::deposit_event(Event::OutgoingAuthorities(members_ids_to_del.clone()));
// -- handle outgoing members
// callback when smith is outgoing
for member_id in members_ids_to_del.iter() {
T::OnOutgoingMember::on_outgoing_member(*member_id);
} }
} else { // a single event with all authorities if some
Self::deposit_event(Event::IncomingAuthorities(members_ids_to_add.clone())); if !members_ids_to_del.is_empty() {
Self::deposit_event(Event::OutgoingAuthorities {
members: members_ids_to_del.clone(),
});
} }
// updates the list of OnlineAuthorities and returns the list of their key // updates the list of OnlineAuthorities and returns the list of their key
...@@ -567,6 +626,7 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> { ...@@ -567,6 +626,7 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
.collect(), .collect(),
) )
} }
/// Same as `new_session`, but it this should only be called at genesis. /// Same as `new_session`, but it this should only be called at genesis.
fn new_session_genesis(_new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> { fn new_session_genesis(_new_index: SessionIndex) -> Option<Vec<T::ValidatorId>> {
Some( Some(
...@@ -582,20 +642,22 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> { ...@@ -582,20 +642,22 @@ impl<T: Config> pallet_session::SessionManager<T::ValidatorId> for Pallet<T> {
.collect(), .collect(),
) )
} }
/// End the session. /// End the session.
/// ///
/// Because the session pallet can queue validator set the ending session can be lower than the /// Because the session pallet can queue validator set the ending session can be lower than the
/// last new session index. /// last new session index.
fn end_session(_end_index: SessionIndex) {} fn end_session(_end_index: SessionIndex) {}
/// Start an already planned session. /// Start an already planned session.
/// ///
/// The session start to be used for validation. /// The session start to be used for validation.
fn start_session(start_index: SessionIndex) { fn start_session(start_index: SessionIndex) {
Self::expire_memberships(start_index);
T::OnNewSession::on_new_session(start_index); T::OnNewSession::on_new_session(start_index);
} }
} }
// see substrate FullIdentification
fn add_full_identification<T: Config>( fn add_full_identification<T: Config>(
validator_id: T::ValidatorId, validator_id: T::ValidatorId,
) -> Option<(T::ValidatorId, T::FullIdentification)> { ) -> Option<(T::ValidatorId, T::FullIdentification)> {
...@@ -604,12 +666,13 @@ fn add_full_identification<T: Config>( ...@@ -604,12 +666,13 @@ fn add_full_identification<T: Config>(
.map(|full_ident| (validator_id, full_ident)) .map(|full_ident| (validator_id, full_ident))
} }
// implement SessionManager with FullIdentification
impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::FullIdentification> impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::FullIdentification>
for Pallet<T> for Pallet<T>
{ {
fn new_session( fn new_session(
new_index: SessionIndex, new_index: SessionIndex,
) -> Option<sp_std::vec::Vec<(T::ValidatorId, T::FullIdentification)>> { ) -> Option<Vec<(T::ValidatorId, T::FullIdentification)>> {
<Self as pallet_session::SessionManager<_>>::new_session(new_index).map(|validators_ids| { <Self as pallet_session::SessionManager<_>>::new_session(new_index).map(|validators_ids| {
validators_ids validators_ids
.into_iter() .into_iter()
...@@ -617,9 +680,10 @@ impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::Fu ...@@ -617,9 +680,10 @@ impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::Fu
.collect() .collect()
}) })
} }
fn new_session_genesis( fn new_session_genesis(
new_index: SessionIndex, new_index: SessionIndex,
) -> Option<sp_std::vec::Vec<(T::ValidatorId, T::FullIdentification)>> { ) -> Option<Vec<(T::ValidatorId, T::FullIdentification)>> {
<Self as pallet_session::SessionManager<_>>::new_session_genesis(new_index).map( <Self as pallet_session::SessionManager<_>>::new_session_genesis(new_index).map(
|validators_ids| { |validators_ids| {
validators_ids validators_ids
...@@ -629,9 +693,11 @@ impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::Fu ...@@ -629,9 +693,11 @@ impl<T: Config> pallet_session::historical::SessionManager<T::ValidatorId, T::Fu
}, },
) )
} }
fn start_session(start_index: SessionIndex) { fn start_session(start_index: SessionIndex) {
<Self as pallet_session::SessionManager<_>>::start_session(start_index) <Self as pallet_session::SessionManager<_>>::start_session(start_index)
} }
fn end_session(end_index: SessionIndex) { fn end_session(end_index: SessionIndex) {
<Self as pallet_session::SessionManager<_>>::end_session(end_index) <Self as pallet_session::SessionManager<_>>::end_session(end_index)
} }
......
...@@ -16,25 +16,22 @@ ...@@ -16,25 +16,22 @@
use super::*; use super::*;
use crate::{self as pallet_authority_members}; use crate::{self as pallet_authority_members};
use frame_support::{ use frame_support::{derive_impl, pallet_prelude::*, parameter_types, traits::Everything};
pallet_prelude::*,
parameter_types,
traits::{Everything, GenesisBuild},
BasicExternalities,
};
use frame_system as system; use frame_system as system;
use pallet_offences::{traits::OnOffenceHandler, SlashStrategy};
use pallet_session::ShouldEndSession; use pallet_session::ShouldEndSession;
use sp_core::{crypto::key_types::DUMMY, H256}; use sp_core::{crypto::key_types::DUMMY, H256};
use sp_runtime::{ use sp_runtime::{
impl_opaque_keys, impl_opaque_keys,
testing::{Header, UintAuthorityId}, testing::UintAuthorityId,
traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys}, traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys},
KeyTypeId, BuildStorage, KeyTypeId,
}; };
use sp_staking::offence::OffenceDetails;
use sp_state_machine::BasicExternalities;
type AccountId = u64; type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
impl_opaque_keys! { impl_opaque_keys! {
pub struct MockSessionKeys { pub struct MockSessionKeys {
...@@ -50,14 +47,11 @@ impl From<UintAuthorityId> for MockSessionKeys { ...@@ -50,14 +47,11 @@ impl From<UintAuthorityId> for MockSessionKeys {
// Configure a mock runtime to test the pallet. // Configure a mock runtime to test the pallet.
frame_support::construct_runtime!( frame_support::construct_runtime!(
pub enum Test where pub enum Test
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{ {
System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, System: frame_system,
Session: pallet_session::{Pallet, Call, Storage, Config<T>, Event}, Session: pallet_session,
AuthorityMembers: pallet_authority_members::{Pallet, Call, Storage, Config<T>, Event<T>}, AuthorityMembers: pallet_authority_members,
} }
); );
...@@ -66,31 +60,22 @@ parameter_types! { ...@@ -66,31 +60,22 @@ parameter_types! {
pub const SS58Prefix: u8 = 42; pub const SS58Prefix: u8 = 42;
} }
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test { impl system::Config for Test {
type AccountId = AccountId;
type BaseCallFilter = Everything; type BaseCallFilter = Everything;
type BlockWeights = (); type Block = Block;
type BlockLength = (); type BlockHashCount = BlockHashCount;
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = u64;
type Hash = H256; type Hash = H256;
type Hashing = BlakeTwo256; type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>; type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header; type MaxConsumers = frame_support::traits::ConstU32<16>;
type RuntimeEvent = RuntimeEvent; type Nonce = u64;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo; type PalletInfo = PalletInfo;
type AccountData = (); type RuntimeCall = RuntimeCall;
type OnNewAccount = (); type RuntimeEvent = RuntimeEvent;
type OnKilledAccount = (); type RuntimeOrigin = RuntimeOrigin;
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix; type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
} }
pub struct TestSessionHandler; pub struct TestSessionHandler;
...@@ -118,14 +103,15 @@ impl ShouldEndSession<u64> for TestShouldEndSession { ...@@ -118,14 +103,15 @@ impl ShouldEndSession<u64> for TestShouldEndSession {
} }
impl pallet_session::Config for Test { impl pallet_session::Config for Test {
type DisablingStrategy = ();
type Keys = MockSessionKeys;
type NextSessionRotation = ();
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type SessionHandler = TestSessionHandler;
type SessionManager = AuthorityMembers;
type ShouldEndSession = TestShouldEndSession;
type ValidatorId = u64; type ValidatorId = u64;
type ValidatorIdOf = ConvertInto; type ValidatorIdOf = ConvertInto;
type ShouldEndSession = TestShouldEndSession;
type NextSessionRotation = ();
type SessionManager = AuthorityMembers;
type SessionHandler = TestSessionHandler;
type Keys = MockSessionKeys;
type WeightInfo = (); type WeightInfo = ();
} }
...@@ -148,17 +134,16 @@ impl IsMember<u64> for TestIsSmithMember { ...@@ -148,17 +134,16 @@ impl IsMember<u64> for TestIsSmithMember {
} }
impl pallet_authority_members::Config for Test { impl pallet_authority_members::Config for Test {
type KeysWrapper = MockSessionKeys;
type IsMember = TestIsSmithMember; type IsMember = TestIsSmithMember;
type MaxAuthorities = ConstU32<4>; type MaxAuthorities = ConstU32<4>;
type MaxKeysLife = ConstU32<5>;
type MaxOfflineSessions = ConstU32<2>;
type MemberId = u64; type MemberId = u64;
type MemberIdOf = ConvertInto; type MemberIdOf = ConvertInto;
type OnIncomingMember = ();
type OnNewSession = (); type OnNewSession = ();
type OnRemovedMember = (); type OnOutgoingMember = ();
type RemoveMemberOrigin = system::EnsureRoot<u64>; type RemoveMemberOrigin = system::EnsureRoot<u64>;
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
} }
// Build genesis storage according to the mock runtime. // Build genesis storage according to the mock runtime.
...@@ -170,8 +155,10 @@ pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities { ...@@ -170,8 +155,10 @@ pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities {
.map(|i| (i * 3, i * 3, UintAuthorityId(i * 3).into())) .map(|i| (i * 3, i * 3, UintAuthorityId(i * 3).into()))
.collect(); .collect();
let mut t = frame_system::GenesisConfig::default() let non_authority_keys = Vec::default();
.build_storage::<Test>()
let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap(); .unwrap();
BasicExternalities::execute_with_storage(&mut t, || { BasicExternalities::execute_with_storage(&mut t, || {
for (ref k, ..) in &keys { for (ref k, ..) in &keys {
...@@ -186,7 +173,10 @@ pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities { ...@@ -186,7 +173,10 @@ pub fn new_test_ext(initial_authorities_len: u64) -> sp_io::TestExternalities {
} }
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
pallet_session::GenesisConfig::<Test> { keys } pallet_session::GenesisConfig::<Test> {
keys,
non_authority_keys,
}
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
sp_io::TestExternalities::new(t) sp_io::TestExternalities::new(t)
...@@ -204,3 +194,13 @@ pub fn run_to_block(n: u64) { ...@@ -204,3 +194,13 @@ pub fn run_to_block(n: u64) {
Session::on_initialize(System::block_number()); Session::on_initialize(System::block_number());
} }
} }
pub(crate) fn on_offence(
offenders: &[OffenceDetails<
AccountId,
pallet_session::historical::IdentificationTuple<Test>,
>],
slash_strategy: SlashStrategy,
) {
AuthorityMembers::on_offence(offenders, slash_strategy, 0);
}
...@@ -16,9 +16,10 @@ ...@@ -16,9 +16,10 @@
use super::*; use super::*;
use crate::mock::*; use crate::mock::*;
use crate::MemberData; use frame_support::{assert_err, assert_noop, assert_ok};
use frame_support::{assert_noop, assert_ok}; use frame_system::RawOrigin;
use sp_runtime::testing::UintAuthorityId; use sp_runtime::{testing::UintAuthorityId, traits::BadOrigin};
use sp_staking::offence::OffenceDetails;
const EMPTY: Vec<u64> = Vec::new(); const EMPTY: Vec<u64> = Vec::new();
...@@ -32,27 +33,15 @@ fn test_genesis_build() { ...@@ -32,27 +33,15 @@ fn test_genesis_build() {
assert_eq!(AuthorityMembers::outgoing(), EMPTY); assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!( assert_eq!(
AuthorityMembers::member(3), AuthorityMembers::member(3),
Some(MemberData { Some(MemberData { owner_key: 3 })
expire_on_session: 0,
must_rotate_keys_before: 5,
owner_key: 3,
})
); );
assert_eq!( assert_eq!(
AuthorityMembers::member(6), AuthorityMembers::member(6),
Some(MemberData { Some(MemberData { owner_key: 6 })
expire_on_session: 0,
must_rotate_keys_before: 5,
owner_key: 6,
})
); );
assert_eq!( assert_eq!(
AuthorityMembers::member(9), AuthorityMembers::member(9),
Some(MemberData { Some(MemberData { owner_key: 9 })
expire_on_session: 0,
must_rotate_keys_before: 5,
owner_key: 9,
})
); );
// Verify Session state // Verify Session state
...@@ -71,61 +60,7 @@ fn test_new_session_shoud_not_change_authorities_set() { ...@@ -71,61 +60,7 @@ fn test_new_session_shoud_not_change_authorities_set() {
}); });
} }
#[test] /// tests consequences of go_offline call
fn test_max_keys_life_rule() {
new_test_ext(3).execute_with(|| {
run_to_block(11);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![3, 6, 9]);
// Member 3 and 6 rotate their sessions keys
assert_ok!(AuthorityMembers::set_session_keys(
RuntimeOrigin::signed(3),
UintAuthorityId(3).into()
),);
assert_ok!(AuthorityMembers::set_session_keys(
RuntimeOrigin::signed(6),
UintAuthorityId(6).into()
),);
// Verify state
assert_eq!(
AuthorityMembers::member(3),
Some(MemberData {
expire_on_session: 0,
must_rotate_keys_before: 7,
owner_key: 3,
})
);
assert_eq!(
AuthorityMembers::member(6),
Some(MemberData {
expire_on_session: 0,
must_rotate_keys_before: 7,
owner_key: 6,
})
);
// Member 9 should expire at session 5
run_to_block(26);
assert_eq!(Session::current_index(), 5);
assert_eq!(AuthorityMembers::online(), vec![3, 6]);
assert_eq!(AuthorityMembers::member(9), None);
// Member 9 should be "deprogrammed" but still in the authorities set for 1 session
assert_eq!(Session::queued_keys().len(), 2);
assert_eq!(Session::queued_keys()[0].0, 3);
assert_eq!(Session::queued_keys()[1].0, 6);
assert_eq!(Session::validators(), vec![3, 6, 9]);
// Member 9 should be **effectively** out at session 6
run_to_block(31);
assert_eq!(Session::current_index(), 6);
assert_eq!(Session::validators(), vec![3, 6]);
});
}
#[test] #[test]
fn test_go_offline() { fn test_go_offline() {
new_test_ext(3).execute_with(|| { new_test_ext(3).execute_with(|| {
...@@ -135,49 +70,36 @@ fn test_go_offline() { ...@@ -135,49 +70,36 @@ fn test_go_offline() {
assert_ok!(AuthorityMembers::go_offline(RuntimeOrigin::signed(9)),); assert_ok!(AuthorityMembers::go_offline(RuntimeOrigin::signed(9)),);
// Verify state // Verify state
assert_eq!(Session::current_index(), 0); // we are currently at session 0
assert_eq!(AuthorityMembers::incoming(), EMPTY); assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]); assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![9]); assert_eq!(AuthorityMembers::outgoing(), vec![9]);
assert_eq!(AuthorityMembers::blacklist(), EMPTY);
assert_eq!( assert_eq!(
AuthorityMembers::member(9), AuthorityMembers::member(9),
Some(MemberData { Some(MemberData { owner_key: 9 })
expire_on_session: 0,
must_rotate_keys_before: 5,
owner_key: 9,
})
); );
// Member 9 should be "deprogrammed" at the next session // Member 9 should be outgoing at the next session (session 1).
// They should be out at session 2.
run_to_block(5); run_to_block(5);
assert_eq!( assert_eq!(
AuthorityMembers::member(9), AuthorityMembers::member(9),
Some(MemberData { Some(MemberData { owner_key: 9 })
expire_on_session: 4,
must_rotate_keys_before: 5,
owner_key: 9,
})
); );
assert_eq!(AuthorityMembers::members_expire_on(4), vec![9],);
assert_eq!(Session::current_index(), 1); assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]); assert_eq!(Session::validators(), vec![3, 6, 9]);
assert_eq!(Session::queued_keys().len(), 2); assert_eq!(Session::queued_keys().len(), 2);
assert_eq!(Session::queued_keys()[0].0, 3); assert_eq!(Session::queued_keys()[0].0, 3);
assert_eq!(Session::queued_keys()[1].0, 6); assert_eq!(Session::queued_keys()[1].0, 6);
// Member 9 should be **effectively** out at session 2
run_to_block(10); run_to_block(10);
assert_eq!(Session::current_index(), 2); assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![3, 6]); assert_eq!(Session::validators(), vec![3, 6]);
// Member 9 should be removed at session 4
run_to_block(20);
assert_eq!(Session::current_index(), 4);
assert_eq!(Session::validators(), vec![3, 6]);
assert_eq!(AuthorityMembers::members_expire_on(4), EMPTY);
assert_eq!(AuthorityMembers::member(9), None);
}); });
} }
/// tests consequences of go_online call
#[test] #[test]
fn test_go_online() { fn test_go_online() {
new_test_ext(3).execute_with(|| { new_test_ext(3).execute_with(|| {
...@@ -190,11 +112,7 @@ fn test_go_online() { ...@@ -190,11 +112,7 @@ fn test_go_online() {
)); ));
assert_eq!( assert_eq!(
AuthorityMembers::member(12), AuthorityMembers::member(12),
Some(MemberData { Some(MemberData { owner_key: 12 })
expire_on_session: 2,
must_rotate_keys_before: 5,
owner_key: 12,
})
); );
// Member 12 should be able to go online // Member 12 should be able to go online
...@@ -206,11 +124,7 @@ fn test_go_online() { ...@@ -206,11 +124,7 @@ fn test_go_online() {
assert_eq!(AuthorityMembers::outgoing(), EMPTY); assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!( assert_eq!(
AuthorityMembers::member(12), AuthorityMembers::member(12),
Some(MemberData { Some(MemberData { owner_key: 12 })
expire_on_session: 2,
must_rotate_keys_before: 5,
owner_key: 12,
})
); );
// Member 12 should be "programmed" at the next session // Member 12 should be "programmed" at the next session
...@@ -259,6 +173,8 @@ fn test_too_many_authorities() { ...@@ -259,6 +173,8 @@ fn test_too_many_authorities() {
assert_eq!(AuthorityMembers::authorities_counter(), 3); assert_eq!(AuthorityMembers::authorities_counter(), 3);
assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(15)),); assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(15)),);
assert_eq!(AuthorityMembers::authorities_counter(), 4); assert_eq!(AuthorityMembers::authorities_counter(), 4);
assert_ok!(AuthorityMembers::remove_member(RawOrigin::Root.into(), 15));
assert_eq!(AuthorityMembers::authorities_counter(), 3);
}); });
} }
...@@ -286,11 +202,7 @@ fn test_go_online_then_go_offline_in_same_session() { ...@@ -286,11 +202,7 @@ fn test_go_online_then_go_offline_in_same_session() {
assert_eq!(AuthorityMembers::outgoing(), EMPTY); assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!( assert_eq!(
AuthorityMembers::member(12), AuthorityMembers::member(12),
Some(MemberData { Some(MemberData { owner_key: 12 })
expire_on_session: 2,
must_rotate_keys_before: 5,
owner_key: 12,
})
); );
}); });
} }
...@@ -314,11 +226,187 @@ fn test_go_offline_then_go_online_in_same_session() { ...@@ -314,11 +226,187 @@ fn test_go_offline_then_go_online_in_same_session() {
assert_eq!(AuthorityMembers::outgoing(), EMPTY); assert_eq!(AuthorityMembers::outgoing(), EMPTY);
assert_eq!( assert_eq!(
AuthorityMembers::member(9), AuthorityMembers::member(9),
Some(MemberData { Some(MemberData { owner_key: 9 })
expire_on_session: 0, );
must_rotate_keys_before: 5, });
owner_key: 9, }
})
// === offence handling tests below ===
/// test offence handling with disconnect strategy
// the offenders should be disconnected (same as go_offline)
// they should be able to go_online after
#[test]
fn test_offence_disconnect() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
on_offence(
&[OffenceDetails {
offender: (9, ()),
reporters: vec![],
}],
pallet_offences::SlashStrategy::Disconnect,
);
on_offence(
&[OffenceDetails {
offender: (3, ()),
reporters: vec![],
}],
pallet_offences::SlashStrategy::Disconnect,
);
// Verify state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![3, 9]);
assert_eq!(AuthorityMembers::blacklist(), EMPTY);
// Member 9 and 3 should be outgoing at the next session
run_to_block(5);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData { owner_key: 9 })
);
assert_eq!(
AuthorityMembers::member(3),
Some(MemberData { owner_key: 3 })
);
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]);
assert_eq!(Session::queued_keys().len(), 1);
assert_eq!(Session::queued_keys()[0].0, 6);
// Member 9 and 3 should be out at session 2
run_to_block(10);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![6]);
// Member 9 and 3 should be allowed to set session keys and go online
run_to_block(25);
assert_ok!(AuthorityMembers::set_session_keys(
RuntimeOrigin::signed(9),
UintAuthorityId(9).into(),
));
assert_ok!(AuthorityMembers::set_session_keys(
RuntimeOrigin::signed(3),
UintAuthorityId(3).into(),
));
assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(9)),);
assert_ok!(AuthorityMembers::go_online(RuntimeOrigin::signed(3)),);
// Report an offence again
run_to_block(35);
on_offence(
&[OffenceDetails {
offender: (3, ()),
reporters: vec![],
}],
pallet_offences::SlashStrategy::Disconnect,
);
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![3]);
assert_eq!(AuthorityMembers::blacklist(), EMPTY);
});
}
/// test offence handling with blacklist strategy
// member 9 is offender, should be blacklisted
#[test]
fn test_offence_black_list() {
new_test_ext(3).execute_with(|| {
// at block 0 begins session 0
run_to_block(1);
on_offence(
&[OffenceDetails {
offender: (9, ()),
reporters: vec![],
}],
pallet_offences::SlashStrategy::Blacklist,
);
// Verify state
// same as `test_go_offline`
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![9]);
assert_eq!(AuthorityMembers::blacklist(), vec![9]);
// Member 9 should be outgoing at the next session
run_to_block(5);
assert_eq!(Session::current_index(), 1);
assert_eq!(Session::validators(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::blacklist(), vec![9]); // still in blacklist
// Member 9 should be out at session 2
run_to_block(10);
assert_eq!(Session::current_index(), 2);
assert_eq!(Session::validators(), vec![3, 6]);
assert_eq!(AuthorityMembers::blacklist(), vec![9]); // still in blacklist
});
}
/// tests that blacklisting prevents 9 from going online
#[test]
fn test_offence_black_list_prevent_from_going_online() {
new_test_ext(3).execute_with(|| {
run_to_block(1);
on_offence(
&[OffenceDetails {
offender: (9, ()),
reporters: vec![],
}],
pallet_offences::SlashStrategy::Blacklist,
);
// Verify state
assert_eq!(AuthorityMembers::incoming(), EMPTY);
assert_eq!(AuthorityMembers::online(), vec![3, 6, 9]);
assert_eq!(AuthorityMembers::outgoing(), vec![9]);
assert_eq!(AuthorityMembers::blacklist(), vec![9]);
assert_eq!(
AuthorityMembers::member(9),
Some(MemberData { owner_key: 9 })
);
// for detail, see `test_go_offline`
// Member 9 is "deprogrammed" at the next session
// Member 9 is **effectively** out at session 2
// Member 9 is removed at session 4
// Member 9 should not be allowed to go online
run_to_block(25);
assert_ok!(AuthorityMembers::set_session_keys(
RuntimeOrigin::signed(9),
UintAuthorityId(9).into(),
));
assert_err!(
AuthorityMembers::go_online(RuntimeOrigin::signed(9)),
Error::<Test>::MemberBlacklisted
);
// Should not be able to auto remove from blacklist
assert_err!(
AuthorityMembers::remove_member_from_blacklist(RuntimeOrigin::signed(9), 9),
BadOrigin
);
assert_eq!(AuthorityMembers::blacklist(), vec![9]);
// Authorized should be able to remove from blacklist
assert_ok!(AuthorityMembers::remove_member_from_blacklist(
RawOrigin::Root.into(),
9
));
assert_eq!(AuthorityMembers::blacklist(), EMPTY);
System::assert_last_event(Event::MemberRemovedFromBlacklist { member: 9 }.into());
// Authorized should not be able to remove a non-existing member from blacklist
assert_err!(
AuthorityMembers::remove_member_from_blacklist(RawOrigin::Root.into(), 9),
Error::<Test>::MemberNotBlacklisted
); );
}); });
} }
...@@ -15,24 +15,31 @@ ...@@ -15,24 +15,31 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::SessionIndex; use super::SessionIndex;
use frame_support::pallet_prelude::Weight;
pub trait OnNewSession { pub trait OnNewSession {
fn on_new_session(index: SessionIndex) -> Weight; fn on_new_session(index: SessionIndex);
} }
impl OnNewSession for () { impl OnNewSession for () {
fn on_new_session(_: SessionIndex) -> Weight { fn on_new_session(_: SessionIndex) {}
Weight::zero()
}
} }
pub trait OnRemovedMember<MemberId> { /// Handle the consequences of going in the authority set for other pallets.
fn on_removed_member(member_id: MemberId) -> Weight; /// Typically, a smith won't expire as long as he is in the authority set.
pub trait OnIncomingMember<MemberId> {
fn on_incoming_member(member_id: MemberId);
}
/// By default: no consequences
impl<MemberId> OnIncomingMember<MemberId> for () {
fn on_incoming_member(_: MemberId) {}
} }
impl<MemberId> OnRemovedMember<MemberId> for () { /// Handle the consequences of going out of authority set for other pallets.
fn on_removed_member(_: MemberId) -> Weight { /// Typically, the smiths are not allowed to stay offline for a too long time.
Weight::zero() pub trait OnOutgoingMember<MemberId> {
fn on_outgoing_member(member_id: MemberId);
} }
/// By default: no consequences
impl<MemberId> OnOutgoingMember<MemberId> for () {
fn on_outgoing_member(_: MemberId) {}
} }
...@@ -20,22 +20,17 @@ use codec::{Decode, Encode}; ...@@ -20,22 +20,17 @@ use codec::{Decode, Encode};
use scale_info::TypeInfo; use scale_info::TypeInfo;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sp_staking::SessionIndex;
/// Represents data associated with a member.
#[cfg_attr(feature = "std", derive(Debug, Deserialize, Serialize))] #[cfg_attr(feature = "std", derive(Debug, Deserialize, Serialize))]
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] #[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct MemberData<AccountId> { pub struct MemberData<AccountId> {
pub expire_on_session: SessionIndex, /// Public key of the member.
pub must_rotate_keys_before: SessionIndex,
pub owner_key: AccountId, pub owner_key: AccountId,
} }
impl<AccountId> MemberData<AccountId> { impl<AccountId> MemberData<AccountId> {
pub fn new_genesis(must_rotate_keys_before: SessionIndex, owner_key: AccountId) -> Self { pub fn new_genesis(owner_key: AccountId) -> Self {
MemberData { MemberData { owner_key }
expire_on_session: 0,
must_rotate_keys_before,
owner_key,
}
} }
} }
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::unnecessary_cast)]
use frame_support::weights::{constants::RocksDbWeight, Weight};
/// Weight functions needed for pallet_universal_dividend.
pub trait WeightInfo {
fn go_offline() -> Weight;
fn go_online() -> Weight;
fn set_session_keys() -> Weight;
fn remove_member() -> Weight;
fn remove_member_from_blacklist() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
// Storage: Identity IdentityIndexOf (r:1 w:0)
// Storage: SmithMembership Membership (r:1 w:0)
// Storage: AuthorityMembers Members (r:1 w:0)
// Storage: AuthorityMembers OutgoingAuthorities (r:1 w:1)
// Storage: AuthorityMembers IncomingAuthorities (r:1 w:0)
// Storage: AuthorityMembers OnlineAuthorities (r:1 w:0)
// Storage: AuthorityMembers AuthoritiesCounter (r:1 w:1)
fn go_offline() -> Weight {
// Minimum execution time: 120_876 nanoseconds.
Weight::from_parts(122_190_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Identity IdentityIndexOf (r:1 w:0)
// Storage: SmithMembership Membership (r:1 w:0)
// Storage: AuthorityMembers Members (r:1 w:0)
// Storage: Session NextKeys (r:1 w:0)
// Storage: AuthorityMembers IncomingAuthorities (r:1 w:1)
// Storage: AuthorityMembers OutgoingAuthorities (r:1 w:0)
// Storage: AuthorityMembers OnlineAuthorities (r:1 w:0)
// Storage: AuthorityMembers AuthoritiesCounter (r:1 w:1)
fn go_online() -> Weight {
// Minimum execution time: 145_521 nanoseconds.
Weight::from_parts(157_428_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(8 as u64))
.saturating_add(RocksDbWeight::get().writes(2 as u64))
}
// Storage: Identity IdentityIndexOf (r:1 w:0)
// Storage: SmithMembership Membership (r:1 w:0)
// Storage: System Account (r:1 w:0)
// Storage: Session NextKeys (r:1 w:1)
// Storage: Session KeyOwner (r:4 w:0)
// Storage: Session CurrentIndex (r:1 w:0)
// Storage: AuthorityMembers Members (r:1 w:1)
// Storage: AuthorityMembers MustRotateKeysBefore (r:1 w:1)
fn set_session_keys() -> Weight {
// Minimum execution time: 181_682 nanoseconds.
Weight::from_parts(192_995_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(11 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
// Storage: AuthorityMembers Members (r:1 w:1)
// Storage: AuthorityMembers OnlineAuthorities (r:1 w:1)
// Storage: AuthorityMembers OutgoingAuthorities (r:1 w:1)
// Storage: AuthorityMembers AuthoritiesCounter (r:1 w:1)
// Storage: AuthorityMembers IncomingAuthorities (r:1 w:1)
// Storage: Session NextKeys (r:1 w:1)
// Storage: System Account (r:1 w:1)
// Storage: SmithMembership Membership (r:1 w:1)
// Storage: SmithMembership CounterForMembership (r:1 w:1)
// Storage: Session KeyOwner (r:0 w:4)
fn remove_member() -> Weight {
// Minimum execution time: 246_592 nanoseconds.
Weight::from_parts(256_761_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(9 as u64))
.saturating_add(RocksDbWeight::get().writes(13 as u64))
}
// Storage: AuthorityMembers BlackList (r:1 w:1)
fn remove_member_from_blacklist() -> Weight {
// Minimum execution time: 60_023 nanoseconds.
Weight::from_parts(60_615_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(1 as u64))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
}
}