mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Restore the set_keys call
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -9748,6 +9748,7 @@ dependencies = [
|
||||
"serai-abi",
|
||||
"serai-coins-pallet",
|
||||
"sp-api",
|
||||
"sp-application-crypto",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"zeroize",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use alloc::vec::Vec;
|
||||
use alloc::{vec, vec::Vec};
|
||||
|
||||
use zeroize::Zeroize;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
@@ -6,8 +6,10 @@ use borsh::{BorshSerialize, BorshDeserialize};
|
||||
use ciphersuite::{group::GroupEncoding, GroupIo};
|
||||
use dalek_ff_group::Ristretto;
|
||||
|
||||
use sp_core::sr25519::Public;
|
||||
|
||||
use crate::{
|
||||
crypto::{Public, KeyPair},
|
||||
crypto::KeyPair,
|
||||
network_id::{ExternalNetworkId, NetworkId},
|
||||
balance::Amount,
|
||||
};
|
||||
@@ -86,15 +88,17 @@ impl ExternalValidatorSet {
|
||||
|
||||
/// The MuSig public key for a validator set.
|
||||
///
|
||||
/// This function panics on invalid input, per the definition of `dkg::musig::musig_key`.
|
||||
pub fn musig_key(&self, set_keys: &[Public]) -> Public {
|
||||
let mut keys = Vec::new();
|
||||
for key in set_keys {
|
||||
keys.push(
|
||||
<Ristretto as GroupIo>::read_G::<&[u8]>(&mut key.0.as_ref()).expect("invalid participant"),
|
||||
/// This function panics on invalid points as keys and on invalid input, per the definition of
|
||||
/// `dkg::musig::musig_key`.
|
||||
pub fn musig_key(&self, keys: &[Public]) -> Public {
|
||||
let mut decompressed_keys = vec![];
|
||||
for key in keys {
|
||||
decompressed_keys.push(
|
||||
<Ristretto as GroupIo>::read_G::<&[u8]>(&mut key.0.as_slice())
|
||||
.expect("invalid participant"),
|
||||
);
|
||||
}
|
||||
Public(dkg::musig_key::<Ristretto>(self.musig_context(), &keys).unwrap().to_bytes())
|
||||
dkg::musig_key::<Ristretto>(self.musig_context(), &decompressed_keys).unwrap().to_bytes().into()
|
||||
}
|
||||
|
||||
/// The message for the `set_keys` signature.
|
||||
@@ -150,7 +154,7 @@ impl KeyShares {
|
||||
/// Reduction occurs by reducing each validator in a reverse round-robin. This means the
|
||||
/// validators with the least key shares are evicted first.
|
||||
#[must_use]
|
||||
pub fn amortize_excess(validators: &mut [(sp_core::sr25519::Public, KeyShares)]) -> usize {
|
||||
pub fn amortize_excess(validators: &mut [(Public, KeyShares)]) -> usize {
|
||||
let total_key_shares = validators.iter().map(|(_key, shares)| shares.0).sum::<u16>();
|
||||
let mut actual_len = validators.len();
|
||||
let mut offset = 1;
|
||||
|
||||
@@ -21,6 +21,7 @@ bitvec = { version = "1", default-features = false, features = ["alloc", "serde"
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "bit-vec"] }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "16336c737dbe833e9d138a256af99698aba637c7", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "16336c737dbe833e9d138a256af99698aba637c7", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "16336c737dbe833e9d138a256af99698aba637c7", default-features = false }
|
||||
sp-api = { git = "https://github.com/serai-dex/patch-polkadot-sdk", rev = "16336c737dbe833e9d138a256af99698aba637c7", default-features = false }
|
||||
|
||||
@@ -39,6 +40,7 @@ serai-coins-pallet = { path = "../coins", default-features = false }
|
||||
zeroize = "^1.5"
|
||||
|
||||
rand_core = "0.6"
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"] }
|
||||
|
||||
[features]
|
||||
@@ -48,6 +50,7 @@ std = [
|
||||
"scale/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-io/std",
|
||||
"sp-api/std",
|
||||
|
||||
|
||||
@@ -14,11 +14,16 @@ pub(crate) type SortedAllocationsKey = (NetworkId, [u8; 8], [u8; 16], Public);
|
||||
/// This storage is expected to be owned by the `Allocations` interface and not directly read/write
|
||||
/// to.
|
||||
pub(crate) trait AllocationsStorage {
|
||||
/// An opaque map storing allocations.
|
||||
/// An map storing allocations.
|
||||
///
|
||||
/// This is opaque and to be exclusively read/write by `Allocations`.
|
||||
type Allocations: StorageMap<AllocationsKey, Amount, Query = Option<Amount>>;
|
||||
/// An opaque map storing allocations in a sorted manner.
|
||||
|
||||
/// An map storing allocations in a sorted manner.
|
||||
///
|
||||
/// This MUST be instantiated with a map using `Identity` for its hasher.
|
||||
///
|
||||
/// This is opaque and to be exclusively read/write by `Allocations`.
|
||||
/*
|
||||
This is premised on the underlying trie iterating from keys with low-bytes to keys with
|
||||
high-bytes.
|
||||
|
||||
@@ -5,7 +5,9 @@ use serai_abi::primitives::{crypto::SignedEmbeddedEllipticCurveKeys, network_id:
|
||||
use frame_support::storage::StorageDoubleMap;
|
||||
|
||||
pub(crate) trait EmbeddedEllipticCurveKeysStorage {
|
||||
/// An opaque map storing keys on an embedded elliptic curve.
|
||||
/// An map storing keys on an embedded elliptic curve.
|
||||
///
|
||||
/// This is opaque and to be exclusively read/write by `EmbeddedEllipticCurveKeys`.
|
||||
type EmbeddedEllipticCurveKeys: StorageDoubleMap<
|
||||
ExternalNetworkId,
|
||||
Public,
|
||||
|
||||
49
substrate/validator-sets/src/keys.rs
Normal file
49
substrate/validator-sets/src/keys.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use sp_core::sr25519::Public;
|
||||
|
||||
use serai_abi::primitives::{
|
||||
crypto::{ExternalKey, KeyPair},
|
||||
validator_sets::ExternalValidatorSet,
|
||||
};
|
||||
|
||||
use frame_support::storage::StorageMap;
|
||||
|
||||
pub(crate) trait KeysStorage {
|
||||
/// An map storing keys validator sets use for oraclization.
|
||||
///
|
||||
/// This is opaque and to be exclusively read/write by `Keys`.
|
||||
type OraclizationKeys: StorageMap<ExternalValidatorSet, Public, Query = Option<Public>>;
|
||||
|
||||
/// An map storing keys validator sets use for interacting with external networks.
|
||||
///
|
||||
/// This is opaque and to be exclusively read/write by `Keys`.
|
||||
type ExternalKeys: StorageMap<ExternalValidatorSet, ExternalKey, Query = Option<ExternalKey>>;
|
||||
}
|
||||
|
||||
/// An interface for managing validators' embedded elliptic curve keys.
|
||||
pub(crate) trait Keys {
|
||||
/// If a validator set has yet to set keys.
|
||||
#[must_use]
|
||||
fn needs_to_set_keys(set: ExternalValidatorSet) -> bool;
|
||||
|
||||
/// Set the pair of keys for an external network.
|
||||
fn set_keys(set: ExternalValidatorSet, key_pair: KeyPair);
|
||||
|
||||
/// Clear a historic set of keys.
|
||||
fn clear_keys(set: ExternalValidatorSet);
|
||||
}
|
||||
|
||||
impl<S: KeysStorage> Keys for S {
|
||||
fn needs_to_set_keys(set: ExternalValidatorSet) -> bool {
|
||||
S::OraclizationKeys::contains_key(set)
|
||||
}
|
||||
|
||||
fn set_keys(set: ExternalValidatorSet, key_pair: KeyPair) {
|
||||
S::OraclizationKeys::insert(set, Public::from(key_pair.0 .0));
|
||||
S::ExternalKeys::insert(set, key_pair.1);
|
||||
}
|
||||
|
||||
fn clear_keys(set: ExternalValidatorSet) {
|
||||
S::OraclizationKeys::remove(set);
|
||||
S::ExternalKeys::remove(set);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ use allocations::*;
|
||||
mod sessions;
|
||||
use sessions::{*, GenesisValidators as GenesisValidatorsContainer};
|
||||
|
||||
mod keys;
|
||||
use keys::{KeysStorage, Keys as _};
|
||||
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet {
|
||||
@@ -28,11 +31,11 @@ mod pallet {
|
||||
|
||||
use serai_abi::{
|
||||
primitives::{
|
||||
crypto::SignedEmbeddedEllipticCurveKeys,
|
||||
crypto::{SignedEmbeddedEllipticCurveKeys, ExternalKey, KeyPair, Signature},
|
||||
network_id::*,
|
||||
coin::*,
|
||||
balance::*,
|
||||
validator_sets::{Session, ValidatorSet, KeyShares as KeySharesStruct},
|
||||
validator_sets::{Session, ExternalValidatorSet, ValidatorSet, KeyShares as KeySharesStruct},
|
||||
address::SeraiAddress,
|
||||
},
|
||||
economic_security::EconomicSecurity,
|
||||
@@ -132,13 +135,20 @@ mod pallet {
|
||||
type DelayedDeallocations = DelayedDeallocations<T>;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
/// The generated key pair for a given validator set instance.
|
||||
// Satisfy the `Keys` abstractions
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn keys)]
|
||||
pub type Keys<T: Config> =
|
||||
StorageMap<_, Twox64Concat, ExternalValidatorSet, KeyPair, OptionQuery>;
|
||||
type OraclizationKeys<T: Config> =
|
||||
StorageMap<_, Identity, ExternalValidatorSet, Public, OptionQuery>;
|
||||
#[pallet::storage]
|
||||
type ExternalKeys<T: Config> =
|
||||
StorageMap<_, Identity, ExternalValidatorSet, ExternalKey, OptionQuery>;
|
||||
|
||||
impl<T: Config> KeysStorage for Abstractions<T> {
|
||||
type OraclizationKeys = OraclizationKeys<T>;
|
||||
type ExternalKeys = ExternalKeys<T>;
|
||||
}
|
||||
|
||||
/* TODO
|
||||
/// The key for validator sets which can (and still need to) publish their slash reports.
|
||||
#[pallet::storage]
|
||||
pub type PendingSlashReport<T: Config> =
|
||||
@@ -196,6 +206,15 @@ mod pallet {
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
GenesisValidators::<T>::set(Some(
|
||||
self
|
||||
.participants
|
||||
.iter()
|
||||
.map(|(participant, _keys)| *participant)
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.expect("amount of genesis validators exceeded the maximum allowed per set"),
|
||||
));
|
||||
for (participant, keys) in &self.participants {
|
||||
for (network, keys) in ExternalNetworkId::all().zip(keys.iter().cloned()) {
|
||||
assert_eq!(network, keys.network());
|
||||
@@ -504,7 +523,6 @@ mod pallet {
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/* TODO
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn set_keys(
|
||||
@@ -516,28 +534,24 @@ mod pallet {
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
||||
// (called by pre_dispatch) checks it
|
||||
// `signature_participants`, `signature` are checked within `ValidateUnsigned`
|
||||
let _ = signature_participants;
|
||||
let _ = signature;
|
||||
|
||||
let session = Self::session(NetworkId::from(network)).unwrap();
|
||||
let session = Self::current_session(NetworkId::from(network))
|
||||
.expect("validated `set_keys` for a non-existent session");
|
||||
let set = ExternalValidatorSet { network, session };
|
||||
Abstractions::<T>::set_keys(set, key_pair);
|
||||
|
||||
Keys::<T>::set(set, Some(key_pair.clone()));
|
||||
|
||||
// If this is the first ever set for this network, set TotalAllocatedStake now
|
||||
// We generally set TotalAllocatedStake when the prior set retires, and the new set is fully
|
||||
// active and liable. Since this is the first set, there is no prior set to wait to retire
|
||||
// If this is the first session of an external network, mark them current, not solely decided
|
||||
if session == Session(0) {
|
||||
Self::set_total_allocated_stake(NetworkId::from(network));
|
||||
Abstractions::<T>::accept_handover(network.into());
|
||||
}
|
||||
|
||||
Self::deposit_event(Event::KeyGen { set, key_pair });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/* TODO
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||
pub fn report_slashes(
|
||||
@@ -632,7 +646,6 @@ mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
@@ -643,58 +656,57 @@ mod pallet {
|
||||
Call::set_keys { network, ref key_pair, ref signature_participants, ref signature } => {
|
||||
let network = *network;
|
||||
|
||||
// Confirm this set has a session
|
||||
let Some(current_session) = Self::session(NetworkId::from(network)) else {
|
||||
Err(InvalidTransaction::Custom(1))?
|
||||
// Confirm this network has a session decided
|
||||
let Some(latest_decided_session) = Self::latest_decided_session(network.into()) else {
|
||||
Err(InvalidTransaction::BadSigner)?
|
||||
};
|
||||
|
||||
let set = ExternalValidatorSet { network, session: current_session };
|
||||
let set = ExternalValidatorSet { network, session: latest_decided_session };
|
||||
|
||||
// Confirm it has yet to set keys
|
||||
if Keys::<T>::get(set).is_some() {
|
||||
// Confirm this set has yet to set keys
|
||||
if Abstractions::<T>::needs_to_set_keys(set) {
|
||||
Err(InvalidTransaction::Stale)?;
|
||||
}
|
||||
|
||||
// This is a needed precondition as this uses storage variables for the latest decided
|
||||
// session on this assumption
|
||||
assert_eq!(Pallet::<T>::latest_decided_session(network.into()), Some(current_session));
|
||||
|
||||
let participants = Participants::<T>::get(NetworkId::from(network))
|
||||
.expect("session existed without participants");
|
||||
let participants = Abstractions::<T>::selected_validators(set.into()).collect::<Vec<_>>();
|
||||
assert!(
|
||||
!participants.is_empty(),
|
||||
"set which was decided had no selected participants stored"
|
||||
);
|
||||
|
||||
// Check the bitvec is of the proper length
|
||||
if participants.len() != signature_participants.len() {
|
||||
Err(InvalidTransaction::Custom(2))?;
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
|
||||
// Find the participating, total key shares
|
||||
let mut all_key_shares = 0;
|
||||
let mut signers = vec![];
|
||||
let mut signing_key_shares = 0;
|
||||
for (participant, in_use) in participants.into_iter().zip(signature_participants) {
|
||||
let participant = participant.0;
|
||||
let shares = InSet::<T>::get(NetworkId::from(network), participant)
|
||||
.expect("participant from Participants wasn't InSet");
|
||||
all_key_shares += shares;
|
||||
for ((participant, shares), in_use) in
|
||||
participants.into_iter().zip(signature_participants)
|
||||
{
|
||||
all_key_shares += shares.0;
|
||||
|
||||
if !in_use {
|
||||
continue;
|
||||
}
|
||||
|
||||
signers.push(participant);
|
||||
signing_key_shares += shares;
|
||||
signing_key_shares += shares.0;
|
||||
}
|
||||
|
||||
// Check enough validators participated
|
||||
{
|
||||
let f = all_key_shares - signing_key_shares;
|
||||
if signing_key_shares < ((2 * f) + 1) {
|
||||
Err(InvalidTransaction::Custom(3))?;
|
||||
Err(InvalidTransaction::BadSigner)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the signature with the MuSig key of the signers
|
||||
// We theoretically don't need set_keys_message to bind to removed_participants, as the
|
||||
// key we're signing with effectively already does so, yet there's no reason not to
|
||||
if !musig_key(set.into(), &signers).verify(&set_keys_message(&set, key_pair), signature) {
|
||||
use sp_application_crypto::RuntimePublic;
|
||||
if !set.musig_key(&signers).verify(&set.set_keys_message(key_pair), &signature.0.into()) {
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
|
||||
@@ -704,6 +716,7 @@ mod pallet {
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
/* TODO
|
||||
Call::report_slashes { network, ref slashes, ref signature } => {
|
||||
let network = *network;
|
||||
let Some(key) = PendingSlashReport::<T>::take(network) else {
|
||||
@@ -726,7 +739,8 @@ mod pallet {
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
Call::set_embedded_elliptic_curve_key { .. } |
|
||||
*/
|
||||
Call::set_embedded_elliptic_curve_keys { .. } |
|
||||
Call::allocate { .. } |
|
||||
Call::deallocate { .. } |
|
||||
Call::claim_deallocation { .. } => Err(InvalidTransaction::Call)?,
|
||||
@@ -734,12 +748,13 @@ mod pallet {
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
||||
// Explicitly provide a pre-dispatch which calls `validate_unsigned`
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO
|
||||
impl<T: Config> AllowMint for Pallet<T> {
|
||||
fn is_allowed(balance: &ExternalBalance) -> bool {
|
||||
// get the required stake
|
||||
|
||||
@@ -4,12 +4,14 @@ use sp_core::{Encode, Decode, ConstU32, sr25519::Public, bounded::BoundedVec};
|
||||
use serai_abi::primitives::{
|
||||
network_id::NetworkId,
|
||||
balance::Amount,
|
||||
validator_sets::{KeyShares as KeySharesStruct, Session, ValidatorSet},
|
||||
validator_sets::{KeyShares as KeySharesStruct, Session, ExternalValidatorSet, ValidatorSet},
|
||||
};
|
||||
|
||||
use frame_support::storage::{StorageValue, StorageMap, StorageDoubleMap, StoragePrefixedMap};
|
||||
|
||||
use crate::{embedded_elliptic_curve_keys::EmbeddedEllipticCurveKeys, allocations::Allocations};
|
||||
use crate::{
|
||||
embedded_elliptic_curve_keys::EmbeddedEllipticCurveKeys, allocations::Allocations, keys::Keys,
|
||||
};
|
||||
|
||||
/// The list of genesis validators.
|
||||
pub(crate) type GenesisValidators =
|
||||
@@ -18,7 +20,7 @@ pub(crate) type GenesisValidators =
|
||||
/// The key for the SelectedValidators map.
|
||||
pub(crate) type SelectedValidatorsKey = (ValidatorSet, [u8; 16], Public);
|
||||
|
||||
pub(crate) trait SessionsStorage: EmbeddedEllipticCurveKeys + Allocations {
|
||||
pub(crate) trait SessionsStorage: EmbeddedEllipticCurveKeys + Allocations + Keys {
|
||||
/// The genesis validators
|
||||
///
|
||||
/// The usage of is shared with the rest of the pallet. `Sessions` only reads it.
|
||||
@@ -288,6 +290,11 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
||||
selected_validators.append(&mut genesis_validators);
|
||||
}
|
||||
|
||||
// If we failed to select any validators, return `false` now
|
||||
if total_key_shares == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let latest_decided_session = Storage::LatestDecidedSession::mutate(network, |session| {
|
||||
let next_session = session.map(|session| Session(session.0 + 1)).unwrap_or(Session(0));
|
||||
*session = Some(next_session);
|
||||
@@ -341,6 +348,12 @@ impl<Storage: SessionsStorage> Sessions for Storage {
|
||||
let historic_set = ValidatorSet { network, session: historic_session };
|
||||
Storage::KeyShares::remove(historic_set);
|
||||
clear_selected_validators::<Storage::SelectedValidators>(historic_set);
|
||||
match historic_set.network {
|
||||
NetworkId::Serai => {}
|
||||
NetworkId::External(network) => {
|
||||
Storage::clear_keys(ExternalValidatorSet { network, session: historic_session })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user