From 6a981dae6e5257608e573d338dfab328bf29f7bf Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 25 Mar 2023 01:30:53 -0400 Subject: [PATCH] Make Validator Set Network a first-class property There already should only be one validator set operating per network. This formalizes that. Then, validator sets used to be able to operate over multiple networks. That is no longer possible. This formalization increases validator set flexibility while also allowing the ability to formalize the definiton of tokens (which is necessary to define a gas asset). --- Cargo.lock | 1 + docs/protocol/Constants.md | 35 ++++---- docs/protocol/Validator Sets.md | 30 ++----- substrate/node/Cargo.toml | 2 +- substrate/node/src/chain_spec.rs | 6 +- substrate/serai/primitives/Cargo.toml | 4 +- substrate/serai/primitives/src/coins.rs | 62 ++++++++++++++ substrate/validator-sets/pallet/src/lib.rs | 83 +++++++------------ .../validator-sets/primitives/Cargo.toml | 4 +- .../validator-sets/primitives/src/lib.rs | 13 ++- 10 files changed, 136 insertions(+), 104 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4c80790..45876ba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8514,6 +8514,7 @@ dependencies = [ name = "serai-primitives" version = "0.1.0" dependencies = [ + "lazy_static", "parity-scale-codec", "scale-info", "serde", diff --git a/docs/protocol/Constants.md b/docs/protocol/Constants.md index 8ed0524f..f92011fd 100644 --- a/docs/protocol/Constants.md +++ b/docs/protocol/Constants.md @@ -5,17 +5,18 @@ These are the list of types used to represent various properties within the protocol. -| Alias | Type | -|------------------------|----------------------------------------------| -| SeraiAddress | sr25519::Public (unchecked [u8; 32] wrapper) | -| Amount | u64 | -| Coin | u32 | -| Session | u32 | -| Validator Set Index | u16 | -| Validator Set Instance | (Session, Validator Set Index) | -| Key | BoundedVec\ | -| ExternalAddress | BoundedVec\ | -| Data | BoundedVec\ | +| Alias | Type | +|-----------------|----------------------------------------------| +| SeraiAddress | sr25519::Public (unchecked [u8; 32] wrapper) | +| Amount | u64 | +| NetworkId | u16 | +| Coin | u32 | +| Network | Vec | +| Session | u32 | +| Validator Set | (Session, NetworkId) | +| Key | BoundedVec\ | +| ExternalAddress | BoundedVec\ | +| Data | BoundedVec\ | ### Networks @@ -25,13 +26,11 @@ being isolated, the generated keys are further bound to their respective networks via an additive offset created by hashing the network's name (among other properties). The network's key is used for all coins on that network. -Networks are not acknowledged by the Serai network, solely by the processor. - -| Network | Curve | -|----------|-----------| -| Bitcoin | Secp256k1 | -| Ethereum | Secp256k1 | -| Monero | Ed25519 | +| Network | Curve | ID | +|----------|-----------|----| +| Bitcoin | Secp256k1 | 0 | +| Ethereum | Secp256k1 | 1 | +| Monero | Ed25519 | 2 | ### Coins diff --git a/docs/protocol/Validator Sets.md b/docs/protocol/Validator Sets.md index eb6abd7c..1cb5b460 100644 --- a/docs/protocol/Validator Sets.md +++ b/docs/protocol/Validator Sets.md @@ -2,30 +2,18 @@ Validator Sets are defined at the protocol level, with the following parameters: - - `bond` (Amount): Amount of bond per key-share. - - `coins` (Vec\): List of coins within this set. - - `participants` (Vec\): List of participants within this set. + - `bond` (Amount): Amount of bond per key-share. + - `network` (Network): The network this validator set operates + over. + - `participants` (Vec\): List of participants within this set. -Validator Sets are referred to by `ValidatorSetIndex` yet have their data -accessible via `ValidatorSetInstance`. +Validator Sets are referred to by `NetworkId` yet have their data accessible via +`ValidatorSetInstance`. -At launch, there will solely be Validator Set 0, managing Bitcoin, Ether, DAI, -and Monero. +### Participation in consensus -### Participation in the BFT process - -All Validator Sets participate in the BFT process described under -[Consensus](./Consensus.md). Specifically, a block containing In Instructions -for a coin must be approved by the BFT majority of the Validator Set responsible -for it, along with the BFT majority of the network by bond. - -At this time, In Instructions for a coin are only expected to be included when a -validator from the Validator Set managing the coin is the producer of the block -in question. - -Since there is currently only one Validator Set, the aforementioned BFT -conditions collapse to simply the BFT majority by bond. Ensuring BFT majority -per responsible Validator Set is accordingly unimplemented for now. +All Validator Sets participate in consensus. In the future, a dedicated group +to order Serai is planned. ### Multisig diff --git a/substrate/node/Cargo.toml b/substrate/node/Cargo.toml index dd51b15c..59930784 100644 --- a/substrate/node/Cargo.toml +++ b/substrate/node/Cargo.toml @@ -30,7 +30,7 @@ sp-consensus = { git = "https://github.com/serai-dex/substrate" } frame-benchmarking = { git = "https://github.com/serai-dex/substrate" } frame-benchmarking-cli = { git = "https://github.com/serai-dex/substrate" } -serai-runtime = { path = "../runtime" } +serai-runtime = { path = "../runtime", features = ["std"] } sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" } sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" } diff --git a/substrate/node/src/chain_spec.rs b/substrate/node/src/chain_spec.rs index b440b49e..9f71f721 100644 --- a/substrate/node/src/chain_spec.rs +++ b/substrate/node/src/chain_spec.rs @@ -50,7 +50,11 @@ fn testnet_genesis( session: SessionConfig { keys: validators.iter().map(|name| session_key(*name)).collect() }, validator_sets: ValidatorSetsConfig { bond: Amount(1_000_000 * 10_u64.pow(8)), - coins: vec![BITCOIN, ETHER, DAI, MONERO], + networks: vec![ + (BITCOIN_NET_ID, BITCOIN_NET.clone()), + (ETHEREUM_NET_ID, ETHEREUM_NET.clone()), + (MONERO_NET_ID, MONERO_NET.clone()), + ], participants: validators.iter().map(|name| account_from_name(name)).collect(), }, } diff --git a/substrate/serai/primitives/Cargo.toml b/substrate/serai/primitives/Cargo.toml index 226727c0..f7f6131e 100644 --- a/substrate/serai/primitives/Cargo.toml +++ b/substrate/serai/primitives/Cargo.toml @@ -12,6 +12,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +lazy_static = { version = "1", optional = true } + zeroize = { version = "^1.5", features = ["derive"], optional = true } scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } @@ -23,5 +25,5 @@ sp-core = { git = "https://github.com/serai-dex/substrate", default-features = f sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } [features] -std = ["zeroize", "scale/std", "scale-info/std", "serde", "sp-core/std", "sp-runtime/std"] +std = ["lazy_static", "zeroize", "scale/std", "scale-info/std", "serde", "sp-core/std", "sp-runtime/std"] default = ["std"] diff --git a/substrate/serai/primitives/src/coins.rs b/substrate/serai/primitives/src/coins.rs index 4cb4c6b3..af1f1123 100644 --- a/substrate/serai/primitives/src/coins.rs +++ b/substrate/serai/primitives/src/coins.rs @@ -4,9 +4,25 @@ use zeroize::Zeroize; use scale::{Encode, Decode, MaxEncodedLen}; use scale_info::TypeInfo; +use sp_core::{ConstU32, bounded::BoundedVec}; + #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; +/// The type used to identify networks. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] +pub struct NetworkId(pub u16); +impl From for NetworkId { + fn from(network: u16) -> NetworkId { + NetworkId(network) + } +} + +pub const BITCOIN_NET_ID: NetworkId = NetworkId(0); +pub const ETHEREUM_NET_ID: NetworkId = NetworkId(1); +pub const MONERO_NET_ID: NetworkId = NetworkId(2); + /// The type used to identify coins. #[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] @@ -22,3 +38,49 @@ pub const BITCOIN: Coin = Coin(1); pub const ETHER: Coin = Coin(2); pub const DAI: Coin = Coin(3); pub const MONERO: Coin = Coin(4); + +// Max of 8 coins per network +// Since Serai isn't interested in listing tokens, as on-chain DEXs will almost certainly have +// more liquidity, the only reason we'd have so many coins from a network is if there's no DEX +// on-chain +// There's probably no chain with so many *worthwhile* coins and no on-chain DEX +// This could probably be just 4, yet 8 is a hedge for the unforseen +// If necessary, this can be increased with a fork +pub const MAX_COINS_PER_NETWORK: u32 = 8; + +/// Network definition. +#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Network { + coins: BoundedVec>, +} + +#[cfg(feature = "std")] +impl Zeroize for Network { + fn zeroize(&mut self) { + for coin in self.coins.as_mut() { + coin.zeroize(); + } + self.coins.truncate(0); + } +} + +impl Network { + #[cfg(feature = "std")] + pub fn new(coins: Vec) -> Result { + Ok(Network { + coins: coins.try_into().map_err(|_| "coins length exceeds {MAX_COINS_PER_NETWORK}")?, + }) + } + + pub fn coins(&self) -> &[Coin] { + &self.coins + } +} + +#[cfg(feature = "std")] +lazy_static::lazy_static! { + pub static ref BITCOIN_NET: Network = Network::new(vec![BITCOIN]).unwrap(); + pub static ref ETHEREUM_NET: Network = Network::new(vec![ETHER, DAI]).unwrap(); + pub static ref MONERO_NET: Network = Network::new(vec![MONERO]).unwrap(); +} diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs index db5a6b06..9c6b0282 100644 --- a/substrate/validator-sets/pallet/src/lib.rs +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -20,44 +20,31 @@ pub mod pallet { #[pallet::genesis_config] #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)] pub struct GenesisConfig { - /// Bond requirement to join the initial validator set. + /// Bond requirement to join the initial validator sets. /// 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, - /// Coins to spawn the network with in the initial validator set. - pub coins: Vec, - /// List of participants to place in the genesis set. + /// Networks to spawn Serai with. + pub networks: Vec<(NetworkId, Network)>, + /// List of participants to place in the initial validator sets. pub participants: Vec, } #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - GenesisConfig { bond: Amount(1), coins: vec![], participants: vec![] } + GenesisConfig { bond: Amount(1), networks: vec![], 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; #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] - pub struct ValidatorSet { + pub struct ValidatorSetData { bond: Amount, - coins: BoundedVec, + network: Network, // Participant and their amount bonded to this set // Limit each set to 100 participants for now @@ -72,15 +59,14 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn validator_set)] pub type ValidatorSets = - StorageMap<_, Twox64Concat, ValidatorSetInstance, ValidatorSet, OptionQuery>; + StorageMap<_, Twox64Concat, ValidatorSet, ValidatorSetData, OptionQuery>; type Key = BoundedVec; - /// The key for a given validator set instance coin. + /// The key for a given validator set instance. #[pallet::storage] #[pallet::getter(fn key)] - pub type Keys = - StorageMap<_, Twox64Concat, (ValidatorSetInstance, Coin), Key, OptionQuery>; + pub type Keys = StorageMap<_, Twox64Concat, ValidatorSet, Key, OptionQuery>; /// If an account has voted for a specific key or not. Prevents them from voting multiple times. #[pallet::storage] @@ -91,7 +77,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn vote_count)] pub type VoteCount = - StorageMap<_, Blake2_128Concat, (ValidatorSetInstance, Coin, Key), u16, ValueQuery>; + StorageMap<_, Blake2_128Concat, (ValidatorSet, Key), u16, ValueQuery>; #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { @@ -100,15 +86,14 @@ pub mod pallet { for participant in self.participants.clone() { participants.push((participant, self.bond)); } + let participants = BoundedVec::try_from(participants).unwrap(); - ValidatorSets::::set( - ValidatorSetInstance { session: Session(0), index: ValidatorSetIndex(0) }, - Some(ValidatorSet { - bond: self.bond, - coins: BoundedVec::try_from(self.coins.clone()).unwrap(), - participants: BoundedVec::try_from(participants).unwrap(), - }), - ); + for (id, network) in self.networks.clone() { + ValidatorSets::::set( + ValidatorSet { session: Session(0), network: id }, + Some(ValidatorSetData { bond: self.bond, network, participants: participants.clone() }), + ); + } } } @@ -117,15 +102,13 @@ pub mod pallet { pub enum Event { Vote { voter: T::AccountId, - instance: ValidatorSetInstance, - coin: Coin, + set: ValidatorSet, key: Key, // Amount of votes the key now has votes: u16, }, KeyGen { - instance: ValidatorSetInstance, - coin: Coin, + set: ValidatorSet, key: Key, }, } @@ -146,12 +129,7 @@ pub mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(0)] // TODO - pub fn vote( - origin: OriginFor, - index: ValidatorSetIndex, - coin: Coin, - key: Key, - ) -> DispatchResult { + pub fn vote(origin: OriginFor, network: NetworkId, 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 @@ -161,15 +139,14 @@ pub mod pallet { let session: Session = Session(0); // Confirm a key hasn't been set for this set instance - let instance = ValidatorSetInstance { session, index }; - if Keys::::get((instance, coin)).is_some() { + let set = ValidatorSet { session, network }; + if Keys::::get(set).is_some() { Err(Error::::AlreadyGeneratedKeys)?; } // Confirm the signer is a validator in the set - let set = ValidatorSets::::get(instance).ok_or(Error::::NonExistentValidatorSet)?; - - if set.participants.iter().any(|participant| participant.0 == signer) { + let data = ValidatorSets::::get(set).ok_or(Error::::NonExistentValidatorSet)?; + if data.participants.iter().any(|participant| participant.0 == signer) { Err(Error::::NotValidator)?; } @@ -180,17 +157,17 @@ pub mod pallet { Voted::::set((&signer, &key), Some(())); // Add their vote - let votes = VoteCount::::mutate((instance, coin, &key), |value| { + let votes = VoteCount::::mutate((set, &key), |value| { *value += 1; *value }); - Self::deposit_event(Event::Vote { voter: signer, instance, coin, key: key.clone(), votes }); + Self::deposit_event(Event::Vote { voter: signer, set, key: key.clone(), votes }); // If we've reached consensus, set the key - if usize::try_from(votes).unwrap() == set.participants.len() { - Keys::::set((instance, coin), Some(key.clone())); - Self::deposit_event(Event::KeyGen { instance, coin, key }); + if usize::try_from(votes).unwrap() == data.participants.len() { + Keys::::set(set, Some(key.clone())); + Self::deposit_event(Event::KeyGen { set, key }); } Ok(()) diff --git a/substrate/validator-sets/primitives/Cargo.toml b/substrate/validator-sets/primitives/Cargo.toml index 4b2aca04..c09ce14a 100644 --- a/substrate/validator-sets/primitives/Cargo.toml +++ b/substrate/validator-sets/primitives/Cargo.toml @@ -19,6 +19,8 @@ scale-info = { version = "2", default-features = false, features = ["derive"] } serde = { version = "1", features = ["derive"], optional = true } +serai-primitives = { path = "../../serai/primitives", default-features = false } + [features] -std = ["zeroize", "scale/std", "scale-info/std", "serde"] +std = ["zeroize", "scale/std", "scale-info/std", "serde", "serai-primitives/std"] default = ["std"] diff --git a/substrate/validator-sets/primitives/src/lib.rs b/substrate/validator-sets/primitives/src/lib.rs index 2604c039..a6660d03 100644 --- a/substrate/validator-sets/primitives/src/lib.rs +++ b/substrate/validator-sets/primitives/src/lib.rs @@ -9,20 +9,17 @@ use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Serialize, Deserialize}; +use serai_primitives::NetworkId; + /// The type used to identify a specific session of validators. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] pub struct Session(pub u32); -/// The type used to identify a validator set. -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Zeroize, 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, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Zeroize, Serialize, Deserialize))] -pub struct ValidatorSetInstance { +pub struct ValidatorSet { pub session: Session, - pub index: ValidatorSetIndex, + pub network: NetworkId, }