mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
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:
41
substrate/validator-sets/pallet/Cargo.toml
Normal file
41
substrate/validator-sets/pallet/Cargo.toml
Normal 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"]
|
||||
15
substrate/validator-sets/pallet/LICENSE
Normal file
15
substrate/validator-sets/pallet/LICENSE
Normal 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/>.
|
||||
207
substrate/validator-sets/pallet/src/lib.rs
Normal file
207
substrate/validator-sets/pallet/src/lib.rs
Normal 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::*;
|
||||
25
substrate/validator-sets/primitives/Cargo.toml
Normal file
25
substrate/validator-sets/primitives/Cargo.toml
Normal 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"]
|
||||
15
substrate/validator-sets/primitives/LICENSE
Normal file
15
substrate/validator-sets/primitives/LICENSE
Normal 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/>.
|
||||
21
substrate/validator-sets/primitives/src/lib.rs
Normal file
21
substrate/validator-sets/primitives/src/lib.rs
Normal 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);
|
||||
Reference in New Issue
Block a user