diff --git a/bridges/bin/node/node/src/chain_spec.rs b/bridges/bin/node/node/src/chain_spec.rs
index 0e81f7f167..13bb3f167d 100644
--- a/bridges/bin/node/node/src/chain_spec.rs
+++ b/bridges/bin/node/node/src/chain_spec.rs
@@ -15,8 +15,8 @@
// along with Parity Bridges Common. If not, see .
use bridge_node_runtime::{
- AccountId, AuraConfig, BalancesConfig, BridgeEthPoAConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig,
- SystemConfig, WASM_BINARY,
+ AccountId, AuraConfig, BalancesConfig, BridgeEthPoAConfig, GenesisConfig, GrandpaConfig, SessionConfig,
+ SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
};
use grandpa_primitives::AuthorityId as GrandpaId;
use sc_service;
@@ -34,6 +34,8 @@ pub type ChainSpec = sc_service::ChainSpec;
pub enum Alternative {
/// Whatever the current runtime is, with just Alice as an auth.
Development,
+ /// Whatever the current runtime is, with simple Alice/Bob/Charlie/Dave/Eve auths.
+ LocalTestnet,
}
/// Helper function to generate a crypto pair from seed
@@ -54,8 +56,12 @@ where
}
/// Helper function to generate an authority key for Aura
-pub fn get_authority_keys_from_seed(s: &str) -> (AuraId, GrandpaId) {
- (get_from_seed::(s), get_from_seed::(s))
+pub fn get_authority_keys_from_seed(s: &str) -> (AccountId, AuraId, GrandpaId) {
+ (
+ get_account_id_from_seed::(s),
+ get_from_seed::(s),
+ get_from_seed::(s),
+ )
}
impl Alternative {
@@ -84,19 +90,60 @@ impl Alternative {
None,
None,
),
+ Alternative::LocalTestnet => ChainSpec::from_genesis(
+ "Local Testnet",
+ "local_testnet",
+ || {
+ testnet_genesis(
+ vec![
+ get_authority_keys_from_seed("Alice"),
+ get_authority_keys_from_seed("Bob"),
+ get_authority_keys_from_seed("Charlie"),
+ get_authority_keys_from_seed("Dave"),
+ get_authority_keys_from_seed("Eve"),
+ ],
+ get_account_id_from_seed::("Alice"),
+ vec![
+ get_account_id_from_seed::("Alice"),
+ get_account_id_from_seed::("Bob"),
+ get_account_id_from_seed::("Charlie"),
+ get_account_id_from_seed::("Dave"),
+ get_account_id_from_seed::("Eve"),
+ get_account_id_from_seed::("Ferdie"),
+ get_account_id_from_seed::("Alice//stash"),
+ get_account_id_from_seed::("Bob//stash"),
+ get_account_id_from_seed::("Charlie//stash"),
+ get_account_id_from_seed::("Dave//stash"),
+ get_account_id_from_seed::("Eve//stash"),
+ get_account_id_from_seed::("Ferdie//stash"),
+ ],
+ true,
+ )
+ },
+ vec![],
+ None,
+ None,
+ None,
+ None,
+ ),
})
}
pub(crate) fn from(s: &str) -> Option {
match s {
"" | "dev" => Some(Alternative::Development),
+ "local" => Some(Alternative::LocalTestnet),
_ => None,
}
}
}
+fn session_keys(aura: AuraId, grandpa: GrandpaId) -> SessionKeys {
+ SessionKeys { aura, grandpa }
+}
+
fn testnet_genesis(
- initial_authorities: Vec<(AuraId, GrandpaId)>,
+ initial_authorities: Vec<(AccountId, AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec,
_enable_println: bool,
@@ -110,13 +157,19 @@ fn testnet_genesis(
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
}),
pallet_aura: Some(AuraConfig {
- authorities: initial_authorities.iter().map(|x| (x.0.clone())).collect(),
+ authorities: Vec::new(),
}),
pallet_bridge_eth_poa: load_kovan_config(),
pallet_grandpa: Some(GrandpaConfig {
- authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(),
+ authorities: Vec::new(),
}),
pallet_sudo: Some(SudoConfig { key: root_key }),
+ pallet_session: Some(SessionConfig {
+ keys: initial_authorities
+ .iter()
+ .map(|x| (x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone())))
+ .collect::>(),
+ }),
}
}
diff --git a/bridges/bin/node/runtime/Cargo.toml b/bridges/bin/node/runtime/Cargo.toml
index 06c8d93070..394db75e17 100644
--- a/bridges/bin/node/runtime/Cargo.toml
+++ b/bridges/bin/node/runtime/Cargo.toml
@@ -59,6 +59,12 @@ default-features = false
rev = "2afecf81ee19b8a6edb364b419190ea47c4a4a31"
git = "https://github.com/paritytech/substrate/"
+[dependencies.pallet-session]
+version = "2.0.0-alpha.2"
+default-features = false
+rev = "2afecf81ee19b8a6edb364b419190ea47c4a4a31"
+git = "https://github.com/paritytech/substrate/"
+
[dependencies.frame-system]
version = "2.0.0-alpha.2"
default-features = false
@@ -149,6 +155,12 @@ default-features = false
rev = "2afecf81ee19b8a6edb364b419190ea47c4a4a31"
git = "https://github.com/paritytech/substrate/"
+[dependencies.sp-staking]
+version = "2.0.0-alpha.2"
+default-features = false
+rev = "2afecf81ee19b8a6edb364b419190ea47c4a4a31"
+git = "https://github.com/paritytech/substrate/"
+
[dependencies.sp-std]
version = "2.0.0-alpha.2"
default-features = false
diff --git a/bridges/bin/node/runtime/src/lib.rs b/bridges/bin/node/runtime/src/lib.rs
index 1ec47fde80..5128ec58e9 100644
--- a/bridges/bin/node/runtime/src/lib.rs
+++ b/bridges/bin/node/runtime/src/lib.rs
@@ -24,12 +24,15 @@
#[cfg(feature = "std")]
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
+use codec::{Decode, Encode};
use pallet_grandpa::fg_primitives;
use pallet_grandpa::AuthorityList as GrandpaAuthorityList;
use sp_api::impl_runtime_apis;
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_core::OpaqueMetadata;
-use sp_runtime::traits::{BlakeTwo256, Block as BlockT, ConvertInto, IdentifyAccount, IdentityLookup, Verify};
+use sp_runtime::traits::{
+ BlakeTwo256, Block as BlockT, ConvertInto, IdentifyAccount, IdentityLookup, OpaqueKeys, Verify,
+};
use sp_runtime::{
create_runtime_str, generic, impl_opaque_keys, transaction_validity::TransactionValidity, ApplyExtrinsicResult,
MultiSignature,
@@ -89,12 +92,12 @@ pub mod opaque {
pub type Block = generic::Block;
/// Opaque block identifier type.
pub type BlockId = generic::BlockId;
+}
- impl_opaque_keys! {
- pub struct SessionKeys {
- pub aura: Aura,
- pub grandpa: Grandpa,
- }
+impl_opaque_keys! {
+ pub struct SessionKeys {
+ pub aura: Aura,
+ pub grandpa: Grandpa,
}
}
@@ -233,6 +236,72 @@ impl pallet_sudo::Trait for Runtime {
type Call = Call;
}
+parameter_types! {
+ pub const Period: BlockNumber = 4;
+ pub const Offset: BlockNumber = 0;
+}
+
+impl pallet_session::Trait for Runtime {
+ type Event = Event;
+ type ValidatorId = ::AccountId;
+ type ValidatorIdOf = ();
+ type ShouldEndSession = pallet_session::PeriodicSessions;
+ type SessionManager = ShiftSessionManager;
+ type SessionHandler = ::KeyTypeIdProviders;
+ type Keys = SessionKeys;
+ type DisabledValidatorsThreshold = ();
+}
+
+pub struct ShiftSessionManager;
+
+impl ShiftSessionManager {
+ /// Select validators for session.
+ fn select_validators(
+ session_index: sp_staking::SessionIndex,
+ available_validators: &[AccountId],
+ ) -> Vec {
+ let available_validators_count = available_validators.len();
+ let count = sp_std::cmp::max(1, 2 * available_validators_count / 3);
+ let offset = session_index as usize % available_validators_count;
+ let end = offset + count;
+ let session_validators = match end.overflowing_sub(available_validators_count) {
+ (wrapped_end, false) if wrapped_end != 0 => available_validators[offset..]
+ .iter()
+ .chain(available_validators[..wrapped_end].iter())
+ .cloned()
+ .collect(),
+ _ => available_validators[offset..end].to_vec(),
+ };
+
+ session_validators
+ }
+}
+
+impl pallet_session::SessionManager for ShiftSessionManager {
+ fn end_session(_: sp_staking::SessionIndex) {}
+ fn new_session(session_index: sp_staking::SessionIndex) -> Option> {
+ // can't access genesis config here :/
+ if session_index == 0 || session_index == 1 {
+ return None;
+ }
+
+ // the idea that on first call (i.e. when session 1 ends) we're reading current
+ // set of validators from session module (they are initial validators) and save
+ // in our 'local storage'.
+ // then for every session we select (deterministically) 2/3 of these initial
+ // validators to serve validators of new session
+ let available_validators = sp_io::storage::get(b":available_validators")
+ .and_then(|validators| Decode::decode(&mut &validators[..]).ok())
+ .unwrap_or_else(|| {
+ let validators = >::validators();
+ sp_io::storage::set(b":available_validators", &validators.encode());
+ validators
+ });
+
+ Some(Self::select_validators(session_index, &available_validators))
+ }
+}
+
construct_runtime!(
pub enum Runtime where
Block = Block,
@@ -247,6 +316,7 @@ construct_runtime!(
Balances: pallet_balances::{Module, Call, Storage, Config, Event},
TransactionPayment: pallet_transaction_payment::{Module, Storage},
Sudo: pallet_sudo::{Module, Call, Config, Storage, Event},
+ Session: pallet_session::{Module, Call, Storage, Event, Config},
BridgeEthPoA: pallet_bridge_eth_poa::{Module, Call, Config, Storage, ValidateUnsigned},
}
);
@@ -374,13 +444,13 @@ impl_runtime_apis! {
impl sp_session::SessionKeys for Runtime {
fn generate_session_keys(seed: Option>) -> Vec {
- opaque::SessionKeys::generate(seed)
+ SessionKeys::generate(seed)
}
fn decode_session_keys(
encoded: Vec,
) -> Option, sp_core::crypto::KeyTypeId)>> {
- opaque::SessionKeys::decode_into_raw_public_keys(&encoded)
+ SessionKeys::decode_into_raw_public_keys(&encoded)
}
}
@@ -390,3 +460,48 @@ impl_runtime_apis! {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn shift_session_manager_works() {
+ let acc1 = AccountId::from([1u8; 32]);
+ let acc2 = AccountId::from([2u8; 32]);
+ let acc3 = AccountId::from([3u8; 32]);
+ let acc4 = AccountId::from([4u8; 32]);
+ let acc5 = AccountId::from([5u8; 32]);
+ let all_accs = vec![acc1.clone(), acc2.clone(), acc3.clone(), acc4.clone(), acc5.clone()];
+
+ // at least 1 validator is selected
+ assert_eq!(
+ ShiftSessionManager::select_validators(0, &[acc1.clone()]),
+ vec![acc1.clone()],
+ );
+
+ // at session#0, shift is also 0
+ assert_eq!(
+ ShiftSessionManager::select_validators(0, &all_accs),
+ vec![acc1.clone(), acc2.clone(), acc3.clone()],
+ );
+
+ // at session#1, shift is also 1
+ assert_eq!(
+ ShiftSessionManager::select_validators(1, &all_accs),
+ vec![acc2.clone(), acc3.clone(), acc4.clone()],
+ );
+
+ // at session#3, we're wrapping
+ assert_eq!(
+ ShiftSessionManager::select_validators(3, &all_accs),
+ vec![acc4, acc5, acc1.clone()],
+ );
+
+ // at session#5, we're starting from the beginning again
+ assert_eq!(
+ ShiftSessionManager::select_validators(5, &all_accs),
+ vec![acc1, acc2, acc3],
+ );
+ }
+}