Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement validator sessions pallets #233

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- arch: aarch64
target-tupl: aarch64-unknown-linux-gnu
dependencies: gcc-aarch64-linux-gnu protobuf-compiler libclang-dev g++-aarch64-linux-gnu
bindgenExtraClangArgs: "--sysroot=/usr/aarch64-linux-gnu -mfloat-abi=hard"
bindgenExtraClangArgs: "--sysroot=/usr/aarch64-linux-gnu"

steps:
- uses: actions/checkout@v4
Expand Down
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pallet-scheduler = { version = "39.0.0", default-features = false }
pallet-balances = { version = "39.0.0", default-features = false }
pallet-babe = { version = "38.0.0", default-features = false }
pallet-grandpa = { version = "38.0.0", default-features = false }
pallet-session = { version = "38.0.0", default-features = false }
pallet-sudo = { version = "38.0.0", default-features = false }
pallet-timestamp = { version = "37.0.0", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { version = "38.0.0", default-features = false }
Expand All @@ -61,10 +62,13 @@ sp-genesis-builder = { version = "0.15.1", default-features = false }
sp-inherents = { version = "34.0.0", default-features = false }
sp-offchain = { version = "34.0.0", default-features = false }
sp-session = { version = "36.0.0", default-features = false }
sp-staking = { version = "36.0.0", default-features = false }
sp-state-machine = { version = "0.43.0", default-features = false }
sp-statement-store = { version = "18.0.0", default-features = false }
sp-storage = { version = "21.0.0", default-features = false }
sp-transaction-pool = { version = "34.0.0", default-features = false }
sp-version = { version = "37.0.0", default-features = false }
sp-weights = { version = "31.0.0", default-features = false }
frame-try-runtime = { version = "0.44.0", default-features = false }
pallet-collective = { version = "38.0.0", default-features = false }
pallet-membership = { version = "38.0.0", default-features = false }
Expand Down
25 changes: 19 additions & 6 deletions node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sp_consensus_babe::AuthorityId as BabeId;
use sp_consensus_grandpa::AuthorityId as GrandpaId;
use sp_core::{sr25519, Pair, Public};
use sp_runtime::traits::{IdentifyAccount, Verify};
use sqnc_runtime::WASM_BINARY;
use sqnc_runtime::{opaque::SessionKeys, SessionConfig, ValidatorSetConfig, WASM_BINARY};
use sqnc_runtime_types::{AccountId, RuntimeExpressionSymbol, RuntimeRestriction, Signature};

const DEFAULT_PROTOCOL_ID: &str = "sqnc";
Expand All @@ -29,8 +29,12 @@ where
}

/// Generate an authority key.
pub fn authority_keys_from_seed(s: &str) -> (BabeId, GrandpaId) {
(get_from_seed::<BabeId>(s), get_from_seed::<GrandpaId>(s))
pub fn authority_keys_from_seed(s: &str) -> (AccountId, BabeId, GrandpaId) {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<BabeId>(s),
get_from_seed::<GrandpaId>(s),
)
}

pub fn development_config() -> Result<ChainSpec, String> {
Expand Down Expand Up @@ -141,7 +145,7 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {

/// Configure initial storage state for FRAME modules.
fn testnet_genesis(
initial_authorities: Vec<(BabeId, GrandpaId)>,
initial_authorities: Vec<(AccountId, BabeId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
technical_committee_accounts: Vec<AccountId>,
Expand All @@ -152,11 +156,20 @@ fn testnet_genesis(
"balances": endowed_accounts.iter().cloned().map(|k| (k, 1i64 << 60)).collect::<Vec<_>>(),
},
"babe": {
"authorities": initial_authorities.iter().map(|x| (x.0.clone(), 1)).collect::<Vec<_>>(),
"authorities": Vec::<BabeId>::new(),
"epochConfig": Some(sqnc_runtime::BABE_GENESIS_EPOCH_CONFIG),
},
"grandpa": {
"authorities": initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect::<Vec<_>>(),
"authorities": Vec::<GrandpaId>::new(),
},
"validatorSet": ValidatorSetConfig {
initial_validators: initial_authorities.iter().map(|x| x.0.clone()).collect::<Vec<_>>(),
},
"session": SessionConfig {
keys: initial_authorities.iter().map(|x| {
(x.0.clone(), x.0.clone(), SessionKeys { babe: x.1.clone(), grandpa: x.2.clone() })
}).collect::<Vec<_>>(),
non_authority_keys: Vec::new()
},
"sudo": {
"key": Some(root_key),
Expand Down
44 changes: 44 additions & 0 deletions pallets/validator-set/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[package]
name = 'pallet-validator-set'
authors = ['Digital Catapult <https://www.digicatapult.org.uk>']
description = 'SessionManager implementation that allows a configured origin to manager the validators for future sessions'
edition = '2021'
license = 'Apache-2.0'
repository = 'https://github.com/digicatapult/sqnc-node/'
version = { workspace = true }

[dependencies]
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
sp-staking = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-session = { workspace = true, features = ['historical'] }
sp-weights = { workspace = true }
scale-info = { workspace = true, features = ['derive', 'serde'] }
log = { workspace = true }
parity-scale-codec = { workspace = true, features = ["derive"] }

[dev-dependencies]
sp-state-machine = { workspace = true }
serde = { workspace = true, features = ['derive'] }

[features]
default = ['std']
runtime-benchmarks = ['frame-benchmarking/runtime-benchmarks']
std = [
'parity-scale-codec/std',
'frame-benchmarking/std',
'frame-support/std',
'frame-system/std',
'scale-info/std',
'sp-core/std',
'sp-io/std',
'sp-std/std',
'sp-runtime/std',
'pallet-session/std',
]
try-runtime = ['frame-support/try-runtime']
204 changes: 204 additions & 0 deletions pallets/validator-set/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# Validator Set Pallet

A [Substrate](https://github.com/paritytech/polkadot-sdk/tree/master/substrate#substrate) pallet to add/remove authorities/validators in PoA networks.

## Attribution

`pallet-validator-set` has been adapted from commit `3bd041f43df5911b7d95c947737aa0a0e45588f8` of the
[`substrate-validator-set`](https://github.com/gautamdhameja/substrate-validator-set) pallet originally
licensed under the Apache-2.0 license. A copy of this license can
be found at [./licenses/substrate-validator-set.LICENSE](./licenses/substrate-validator-set.LICENSE).
This module is then relicensed by Digital Catapult under the same Apache-2.0
license a copy of which is [in the root of this repository](../../LICENSE)

License: Apache-2.0

## Setup with Substrate Node Template

### Dependencies - runtime/cargo.toml

- Add the module's dependency in the `Cargo.toml` of your runtime directory. Make sure to enter the correct path or git url of the pallet as per your setup.

- Make sure that you also have the Substrate [session pallet](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/session) as part of your runtime. This is because the validator-set pallet is dependent on the session pallet.

```toml
[dependencies.validator-set]
default-features = false
package = 'substrate-validator-set'
git = 'https://github.com/gautamdhameja/substrate-validator-set.git'
version = '1.1.0'

[dependencies.pallet-session]
default-features = false
git = 'https://github.com/paritytech/polkadot-sdk.git'
tag = 'polkadot-v1.13.0'
```

```toml
std = [
...
'validator-set/std',
'pallet-session/std',
]
```

### Pallet Initialization - runtime/src/lib.rs

- Import `OpaqueKeys` in your `runtime/src/lib.rs`.

```rust
use sp_runtime::traits::{
AccountIdLookup, BlakeTwo256, Block as BlockT, Verify, IdentifyAccount, NumberFor, OpaqueKeys,
};
```

- Also in `runtime/src/lib.rs` import the `EnsureRoot` trait. This would change if you want to configure a custom origin (see below).

```rust
use frame_system::EnsureRoot;
```

- Declare the pallet in your `runtime/src/lib.rs`. The pallet supports configurable origin and you can either set it to use one of the governance pallets (Collective, Democracy, etc.), or just use root as shown below. But **do not use a normal origin here** because the addition and removal of validators should be done using elevated privileges.

```rust
parameter_types! {
pub const MinAuthorities: u32 = 2;
}

impl validator_set::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type AddRemoveOrigin = EnsureRoot<AccountId>;
type MinAuthorities = MinAuthorities;
type WeightInfo = validator_set::weights::SubstrateWeight<Runtime>;
}
```

- Also, declare the session pallet in your `runtime/src/lib.rs`. Some of the type configuration of session pallet would depend on the ValidatorSet pallet as shown below.

```rust
parameter_types! {
pub const Period: u32 = 2 * MINUTES;
pub const Offset: u32 = 0;
}

impl pallet_session::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type ValidatorId = <Self as frame_system::Config>::AccountId;
type ValidatorIdOf = validator_set::ValidatorOf<Self>;
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
type SessionManager = ValidatorSet;
type SessionHandler = <opaque::SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
type Keys = opaque::SessionKeys;
type WeightInfo = ();
}
```

- Add `validator_set`, and `session` pallets in `construct_runtime` macro. **Make sure to add them before `Aura` and `Grandpa` pallets and after `Balances`. Also make sure that the `validator_set` pallet is added _before_ the `session` pallet, because it provides the initial validators at genesis, and must initialize first.**

```rust
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
...
Balances: pallet_balances,
ValidatorSet: validator_set,
Session: pallet_session,
Aura: pallet_aura,
Grandpa: pallet_grandpa,
...
...
}
);
```

### Genesis config - chain_spec.rs

- Import `opaque::SessionKeys, ValidatorSetConfig, SessionConfig` from the runtime in `node/src/chain_spec.rs`.

```rust
use node_template_runtime::{
AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig,
SudoConfig, SystemConfig, WASM_BINARY, Signature,
opaque::SessionKeys, ValidatorSetConfig, SessionConfig
};
```

- And then in `node/src/chain_spec.rs` update the key generation functions.

```rust
fn session_keys(aura: AuraId, grandpa: GrandpaId) -> SessionKeys {
SessionKeys { aura, grandpa }
}

pub fn authority_keys_from_seed(s: &str) -> (AccountId, AuraId, GrandpaId) {
(
get_account_id_from_seed::<sr25519::Public>(s),
get_from_seed::<AuraId>(s),
get_from_seed::<GrandpaId>(s)
)
}
```

- Add genesis config in the `chain_spec.rs` file for `session` and `validatorset` pallets, and update it for `Aura` and `Grandpa` pallets. Because the validators are provided by the `session` pallet, we do not initialize them explicitly for `Aura` and `Grandpa` pallets. Order is important, notice that `pallet_session` is declared after `pallet_balances` since it depends on it (session accounts should have some balance).

```rust
fn testnet_genesis(
wasm_binary: &[u8],
initial_authorities: Vec<(AccountId, AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool,
) -> GenesisConfig {
GenesisConfig {
system: SystemConfig {
// Add Wasm runtime to storage.
code: wasm_binary.to_vec(),
},
balances: BalancesConfig {
// Configure endowed accounts with initial balance of 1 << 60.
balances: endowed_accounts.iter().cloned().map(|k| (k, 1 << 60)).collect(),
},
validator_set: ValidatorSetConfig {
initial_validators: initial_authorities.iter().map(|x| x.0.clone()).collect::<Vec<_>>(),
},
session: SessionConfig {
keys: initial_authorities.iter().map(|x| {
(x.0.clone(), x.0.clone(), session_keys(x.1.clone(), x.2.clone()))
}).collect::<Vec<_>>(),
},
aura: AuraConfig {
authorities: vec![],
},
grandpa: GrandpaConfig {
authorities: vec![],
},
sudo: SudoConfig {
// Assign network admin rights.
key: Some(root_key),
},
transaction_payment: Default::default(),
}
}
```

## Run

Once you have set up the pallet in your node/node-template and everything compiles, follow the steps in [docs/local-network-setup.md](./docs/local-network-setup.md) to run a local network and add validators.

## Extensions

### Council-based Governance

Instead of using `sudo`, for a council-based governance, use the pallet with the `Collective` pallet. Follow the steps in [docs/council-integration.md](./docs/council-integration.md).

### Auto-removal Of Offline Validators

When a validator goes offline, it skips its block production slot and that causes increased block times. Sometimes, we want to remove these offline validators so that the block time can recover to normal. The `ImOnline` pallet, when added to a runtime, can report offline validators. The `ValidatorSet` pallet implements the required types to integrate with `ImOnline` pallet for automatic removal of offline validators. To use the `ValidatorSet` pallet with the `ImOnline` pallet, follow the steps in [docs/im-online-integration.md](./docs/im-online-integration.md).

## Disclaimer

This code is **not audited** for production use cases. You can expect security vulnerabilities. Do not use it without proper testing and audit in a production applications.
Loading
Loading