Initial validator sets pallet (#187)

* Initial work on a Validator Sets pallet

* Update Validator Set docs per current discussions

* Update validator-sets primitives and storage handling

* Add validator set pallets to deny.toml

* Remove Curve from primitives

Since we aren't reusing keys across coins, there's no reason for it to be
on-chain (as previously planned).

* Update documentation on Validator Sets

* Use Twox64Concat instead of Identity

Ensures an even distribution of keys. While xxhash is breakable, these keys
aren't manipulatable by users.

* Add math ops on Amount and define a coin as 1e8

* Add validator-sets to the runtime and remove contracts

Also removes the randomness pallet which was only required by the contracts
runtime.

Does not remove the contracts folder yet so they can still be referred to while
validator-sets is under development. Does remove them from Cargo.toml.

* Add vote function to validator-sets

* Remove contracts folder

* Create an event for the Validator Sets pallet

* Remove old contracts crates from deny.toml

* Remove line from staking branch

* Remove staking from runtime

* Correct VS Config in runtime

* cargo update

* Resolve a few PR comments on terminology

* Create a serai-primitives crate

Move types such as Amount/Coin out of validator-sets. Will be expanded in the
future.

* Fixes for last commit

* Don't reserve set 0

* Further fixes

* Add files meant for last commit

* Remove Staking transfer
This commit is contained in:
Luke Parker
2023-01-04 22:52:41 -05:00
committed by GitHub
parent 52913a6e8d
commit e979883f2d
26 changed files with 572 additions and 1151 deletions

View File

@@ -0,0 +1,41 @@
[package]
name = "validator-sets-pallet"
version = "0.1.0"
description = "Validator sets pallet"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/validator-sets/pallet"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
serai-primitives = { path = "../../serai/primitives", default-features = false }
validator-sets-primitives = { path = "../primitives", default-features = false }
[features]
std = [
"scale/std",
"scale-info/std",
"frame-system/std",
"frame-support/std",
"serai-primitives/std",
"validator-sets-primitives/std",
]
runtime-benchmarks = [
"frame-system/runtime-benchmarks",
"frame-support/runtime-benchmarks",
]
default = ["std"]

View File

@@ -0,0 +1,15 @@
AGPL-3.0-only license
Copyright (c) 2022 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,207 @@
#![cfg_attr(not(feature = "std"), no_std)]
#[frame_support::pallet]
pub mod pallet {
use scale::{Encode, Decode};
use scale_info::TypeInfo;
use frame_system::pallet_prelude::*;
use frame_support::pallet_prelude::*;
use serai_primitives::*;
use validator_sets_primitives::*;
#[pallet::config]
pub trait Config: frame_system::Config + TypeInfo {
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
}
#[pallet::genesis_config]
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen)]
pub struct GenesisConfig<T: Config> {
/// Bond requirement to join the initial validator set.
/// Every participant at genesis will automatically be assumed to have this much bond.
/// This bond cannot be withdrawn however as there's no stake behind it.
pub bond: Amount,
/// Amount of coins to spawn the network with in the initial validator set.
pub coins: Coin,
/// List of participants to place in the genesis set.
pub participants: Vec<T::AccountId>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
GenesisConfig { bond: Amount(1), coins: Coin(0), participants: vec![] }
}
}
// Max of 16 coins per validator set
// At launch, we'll have BTC, ETH, DAI, and XMR
// In the future, these will be split into separate validator sets, so we're already not
// planning expansion beyond just a few coins per validator set
// The only case which really makes sense for multiple coins in a validator set is:
// 1) The coins are small, easy to run, and make no sense to be in their own set
// In this case, it's still hard to ask validators to run 16 different nodes
// 2) The coins are all on the same network yet there's no DEX on-chain
// In these cases, it'd be hard to find and justify 16 different coins from that single chain
// This could probably be just 8, yet 16 is a hedge for the unforseen
// If necessary, this can be increased with a fork
type MaxCoinsPerSet = ConstU32<16>;
// Support keys up to 96 bytes (BLS12-381 G2)
const MAX_KEY_LEN: u32 = 96;
type MaxKeyLen = ConstU32<MAX_KEY_LEN>;
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct ValidatorSet<T: Config> {
bond: Amount,
coins: BoundedVec<Coin, MaxCoinsPerSet>,
// Participant and their amount bonded to this set
// Limit each set to 100 participants for now
participants: BoundedVec<(T::AccountId, Amount), ConstU32<100>>,
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(PhantomData<T>);
/// The details of a validator set instance.
#[pallet::storage]
#[pallet::getter(fn validator_set)]
pub type ValidatorSets<T: Config> =
StorageMap<_, Twox64Concat, ValidatorSetInstance, ValidatorSet<T>, OptionQuery>;
type Key = BoundedVec<u8, MaxKeyLen>;
/// The key for a given validator set instance coin.
#[pallet::storage]
#[pallet::getter(fn key)]
pub type Keys<T: Config> =
StorageMap<_, Twox64Concat, (ValidatorSetInstance, Coin), Key, OptionQuery>;
/// If an account has voted for a specific key or not. Prevents them from voting multiple times.
#[pallet::storage]
#[pallet::getter(fn voted)]
pub type Voted<T: Config> = StorageMap<_, Blake2_128Concat, (T::AccountId, Key), (), OptionQuery>;
/// How many times a key has been voted for. Once consensus is reached, the keys will be adopted.
#[pallet::storage]
#[pallet::getter(fn vote_count)]
pub type VoteCount<T: Config> =
StorageMap<_, Blake2_128Concat, (ValidatorSetInstance, Coin, Key), u16, ValueQuery>;
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
let mut coins = Vec::new();
for coin in 0 .. self.coins.0 {
coins.push(Coin(coin));
}
let mut participants = Vec::new();
for participant in self.participants.clone() {
participants.push((participant, self.bond));
}
ValidatorSets::<T>::set(
ValidatorSetInstance(Session(0), ValidatorSetIndex(0)),
Some(ValidatorSet {
bond: self.bond,
coins: BoundedVec::try_from(coins).unwrap(),
participants: BoundedVec::try_from(participants).unwrap(),
}),
);
}
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Vote {
voter: T::AccountId,
instance: ValidatorSetInstance,
coin: Coin,
key: Key,
// Amount of votes the key now has
votes: u16,
},
KeyGen {
instance: ValidatorSetInstance,
coin: Coin,
key: Key,
},
}
#[pallet::error]
pub enum Error<T> {
/// Validator Set doesn't exist.
NonExistentValidatorSet,
/// Non-validator is voting.
NotValidator,
/// Validator Set already generated keys.
AlreadyGeneratedKeys,
/// Vvalidator has already voted for these keys.
AlreadyVoted,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(0)] // TODO
pub fn vote(
origin: OriginFor<T>,
index: ValidatorSetIndex,
coin: Coin,
key: Key,
) -> DispatchResult {
let signer = ensure_signed(origin)?;
// TODO: Do we need to check the key is within the length bounds?
// The docs suggest the BoundedVec will create/write, yet not read, which could be an issue
// if it can be passed in
// TODO: Get session
let session: Session = Session(0);
// Confirm a key hasn't been set for this set instance
let instance = ValidatorSetInstance(session, index);
if Keys::<T>::get((instance, coin)).is_some() {
Err(Error::<T>::AlreadyGeneratedKeys)?;
}
// Confirm the signer is a validator in the set
let set = ValidatorSets::<T>::get(instance).ok_or(Error::<T>::NonExistentValidatorSet)?;
if set.participants.iter().any(|participant| participant.0 == signer) {
Err(Error::<T>::NotValidator)?;
}
// Confirm this signer hasn't already voted for these keys
if Voted::<T>::get((&signer, &key)).is_some() {
Err(Error::<T>::AlreadyVoted)?;
}
Voted::<T>::set((&signer, &key), Some(()));
// Add their vote
let votes = VoteCount::<T>::mutate((instance, coin, &key), |value| {
*value += 1;
*value
});
Self::deposit_event(Event::Vote { voter: signer, instance, coin, key: key.clone(), votes });
// If we've reached consensus, set the key
if usize::try_from(votes).unwrap() == set.participants.len() {
Keys::<T>::set((instance, coin), Some(key.clone()));
Self::deposit_event(Event::KeyGen { instance, coin, key });
}
Ok(())
}
}
// TODO: Support session rotation
}
pub use pallet::*;

View File

@@ -0,0 +1,25 @@
[package]
name = "validator-sets-primitives"
version = "0.1.0"
description = "Primitives for validator sets"
license = "AGPL-3.0-only"
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/validator-sets/primitives"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2", default-features = false, features = ["derive"] }
serde = { version = "1.0", features = ["derive"], optional = true }
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
[features]
std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "sp-std/std"]
default = ["std"]

View File

@@ -0,0 +1,15 @@
AGPL-3.0-only license
Copyright (c) 2022 Luke Parker
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License Version 3 as
published by the Free Software Foundation.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -0,0 +1,21 @@
#![cfg_attr(not(feature = "std"), no_std)]
use scale::{Encode, Decode, MaxEncodedLen};
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Serialize, Deserialize};
/// The type used to identify a specific session of validators.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Session(pub u32);
/// The type used to identify a validator set.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct ValidatorSetIndex(pub u16);
/// The type used to identify a specific validator set during a specific session.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct ValidatorSetInstance(pub Session, pub ValidatorSetIndex);