Skip to content

Commit

Permalink
Redesign Slash/SlashReport types with a function to calculate the pen…
Browse files Browse the repository at this point in the history
…alty
  • Loading branch information
kayabaNerve committed Jan 14, 2025
1 parent 291ebf5 commit a7fef2b
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 52 deletions.
3 changes: 2 additions & 1 deletion coordinator/substrate/src/ephemeral.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,9 @@ impl<D: Db> ContinuallyRan for EphemeralEventStream<D> {
Err("validator's weight exceeded u16::MAX".to_string())?
};

// Do the summation in u32 so we don't risk a u16 overflow
let total_weight = validators.iter().map(|(_, weight)| u32::from(*weight)).sum::<u32>();
if total_weight > MAX_KEY_SHARES_PER_SET {
if total_weight > u32::from(MAX_KEY_SHARES_PER_SET) {
Err(format!(
"{set:?} has {total_weight} key shares when the max is {MAX_KEY_SHARES_PER_SET}"
))?;
Expand Down
7 changes: 5 additions & 2 deletions coordinator/tributary/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,11 @@ impl<'a, TD: Db, TDT: DbTxn, P: P2p> ScanBlock<'a, TD, TDT, P> {
// Create the resulting slash report
let mut slash_report = vec![];
for (validator, points) in self.validators.iter().copied().zip(amortized_slash_report) {
if points != 0 {
slash_report.push(Slash { key: validator.into(), points });
// TODO: Natively store this as a `Slash`
if points == u32::MAX {
slash_report.push(Slash::Fatal);
} else {
slash_report.push(Slash::Points(points));
}
}
assert!(slash_report.len() <= f);
Expand Down
4 changes: 2 additions & 2 deletions coordinator/tributary/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,14 +301,14 @@ impl TransactionTrait for Transaction {
Transaction::Batch { .. } => {}

Transaction::Sign { data, .. } => {
if data.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() {
if data.len() > usize::from(MAX_KEY_SHARES_PER_SET) {
Err(TransactionError::InvalidContent)?
}
// TODO: MAX_SIGN_LEN
}

Transaction::SlashReport { slash_points, .. } => {
if slash_points.len() > usize::try_from(MAX_KEY_SHARES_PER_SET).unwrap() {
if slash_points.len() > usize::from(MAX_KEY_SHARES_PER_SET) {
Err(TransactionError::InvalidContent)?
}
}
Expand Down
2 changes: 1 addition & 1 deletion substrate/abi/src/validator_sets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub enum Call {
},
report_slashes {
network: NetworkId,
slashes: BoundedVec<(SeraiAddress, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
slashes: BoundedVec<(SeraiAddress, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 / 3 }>>,
signature: Signature,
},
allocate {
Expand Down
2 changes: 1 addition & 1 deletion substrate/client/src/serai/validator_sets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ impl<'a> SeraiValidatorSets<'a> {
// (50 * (32 + 4)) > (150 * 4)
slashes: sp_runtime::BoundedVec<
(SeraiAddress, u32),
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET_U32 / 3 }>,
>,
signature: Signature,
) -> Transaction {
Expand Down
4 changes: 2 additions & 2 deletions substrate/emissions/pallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub mod pallet {
use economic_security_pallet::{Config as EconomicSecurityConfig, Pallet as EconomicSecurity};

use serai_primitives::*;
use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET, Session};
use validator_sets_primitives::{MAX_KEY_SHARES_PER_SET_U32, Session};
pub use emissions_primitives as primitives;
use primitives::*;

Expand Down Expand Up @@ -74,7 +74,7 @@ pub mod pallet {
_,
Identity,
NetworkId,
BoundedVec<(PublicKey, u64), ConstU32<{ MAX_KEY_SHARES_PER_SET }>>,
BoundedVec<(PublicKey, u64), ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>,
OptionQuery,
>;

Expand Down
1 change: 1 addition & 0 deletions substrate/primitives/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::BlockNumber;
// 1 MB
pub const BLOCK_SIZE: u32 = 1024 * 1024;
// 6 seconds
// TODO: Use Duration
pub const TARGET_BLOCK_TIME: u64 = 6;

/// Measured in blocks.
Expand Down
2 changes: 1 addition & 1 deletion substrate/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ impl pallet_authorship::Config for Runtime {
}

// Maximum number of authorities per session.
pub type MaxAuthorities = ConstU32<{ validator_sets::primitives::MAX_KEY_SHARES_PER_SET }>;
pub type MaxAuthorities = ConstU32<{ validator_sets::primitives::MAX_KEY_SHARES_PER_SET_U32 }>;

/// Longevity of an offence report.
pub type ReportLongevity = <Runtime as pallet_babe::Config>::EpochDuration;
Expand Down
18 changes: 9 additions & 9 deletions substrate/validator-sets/pallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ pub mod pallet {
_,
Identity,
NetworkId,
BoundedVec<(Public, u64), ConstU32<{ MAX_KEY_SHARES_PER_SET }>>,
BoundedVec<(Public, u64), ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 }>>,
OptionQuery,
>;
/// The validators selected to be in-set, regardless of if removed.
Expand Down Expand Up @@ -402,7 +402,7 @@ pub mod pallet {

// Clear the current InSet
assert_eq!(
InSet::<T>::clear_prefix(network, MAX_KEY_SHARES_PER_SET, None).maybe_cursor,
InSet::<T>::clear_prefix(network, MAX_KEY_SHARES_PER_SET_U32, None).maybe_cursor,
None
);

Expand All @@ -412,11 +412,11 @@ pub mod pallet {
{
let mut iter = SortedAllocationsIter::<T>::new(network);
let mut key_shares = 0;
while key_shares < u64::from(MAX_KEY_SHARES_PER_SET) {
while key_shares < u64::from(MAX_KEY_SHARES_PER_SET_U32) {
let Some((key, amount)) = iter.next() else { break };

let these_key_shares =
(amount.0 / allocation_per_key_share).min(u64::from(MAX_KEY_SHARES_PER_SET));
(amount.0 / allocation_per_key_share).min(u64::from(MAX_KEY_SHARES_PER_SET_U32));
participants.push((key, these_key_shares));

key_shares += these_key_shares;
Expand Down Expand Up @@ -535,7 +535,7 @@ pub mod pallet {
top = Some(key_shares);
}

if key_shares > u64::from(MAX_KEY_SHARES_PER_SET) {
if key_shares > u64::from(MAX_KEY_SHARES_PER_SET_U32) {
break;
}
}
Expand All @@ -547,7 +547,7 @@ pub mod pallet {
// post_amortization_key_shares_for_top_validator yields what the top validator's key shares
// would be after such a reduction, letting us evaluate this correctly
let top = post_amortization_key_shares_for_top_validator(validators_len, top, key_shares);
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET.into())
(top * 3) < key_shares.min(MAX_KEY_SHARES_PER_SET_U32.into())
}

fn increase_allocation(
Expand Down Expand Up @@ -586,7 +586,7 @@ pub mod pallet {

// The above is_bft calls are only used to check a BFT net doesn't become non-BFT
// Check here if this call would prevent a non-BFT net from *ever* becoming BFT
if (new_allocation / allocation_per_key_share) >= (MAX_KEY_SHARES_PER_SET / 3).into() {
if (new_allocation / allocation_per_key_share) >= (MAX_KEY_SHARES_PER_SET_U32 / 3).into() {
Err(Error::<T>::AllocationWouldPreventFaultTolerance)?;
}

Expand Down Expand Up @@ -1010,7 +1010,7 @@ pub mod pallet {
pub fn report_slashes(
origin: OriginFor<T>,
network: NetworkId,
slashes: BoundedVec<(Public, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
slashes: BoundedVec<(Public, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET_U32 / 3 }>>,
signature: Signature,
) -> DispatchResult {
ensure_none(origin)?;
Expand Down Expand Up @@ -1209,7 +1209,7 @@ pub mod pallet {

ValidTransaction::with_tag_prefix("ValidatorSets")
.and_provides((1, set))
.longevity(MAX_KEY_SHARES_PER_SET.into())
.longevity(MAX_KEY_SHARES_PER_SET_U32.into())
.propagate(true)
.build()
}
Expand Down
58 changes: 25 additions & 33 deletions substrate/validator-sets/primitives/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

use core::time::Duration;

#[cfg(feature = "std")]
use zeroize::Zeroize;

Expand All @@ -13,28 +15,40 @@ use borsh::{BorshSerialize, BorshDeserialize};
#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

use sp_core::{ConstU32, sr25519::Public, bounded::BoundedVec};
use sp_core::{ConstU32, bounded::BoundedVec, sr25519::Public};
#[cfg(not(feature = "std"))]
use sp_std::vec::Vec;

use serai_primitives::NetworkId;

/// The maximum amount of key shares per set.
pub const MAX_KEY_SHARES_PER_SET: u32 = 150;
mod slash_points;
pub use slash_points::*;

/// The expected duration for a session.
// 1 week
pub const SESSION_LENGTH: Duration = Duration::from_secs(7 * 24 * 60 * 60);

/// The maximum length for a key.
// Support keys up to 96 bytes (BLS12-381 G2).
pub const MAX_KEY_LEN: u32 = 96;

/// The maximum amount of key shares per set.
pub const MAX_KEY_SHARES_PER_SET: u16 = 150;
pub const MAX_KEY_SHARES_PER_SET_U32: u32 = MAX_KEY_SHARES_PER_SET as u32;

/// The type used to identify a specific session of validators.
#[derive(
Clone, Copy, PartialEq, Eq, Hash, Default, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Zeroize))]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Session(pub u32);

/// 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, PartialOrd, Ord, Hash, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
)]
#[cfg_attr(feature = "std", derive(Zeroize))]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand All @@ -43,13 +57,13 @@ pub struct ValidatorSet {
pub network: NetworkId,
}

type MaxKeyLen = ConstU32<MAX_KEY_LEN>;
/// The type representing a Key from an external network.
pub type ExternalKey = BoundedVec<u8, MaxKeyLen>;
pub type ExternalKey = BoundedVec<u8, ConstU32<MAX_KEY_LEN>>;

/// The key pair for a validator set.
///
/// This is their Ristretto key, used for signing Batches, and their key on the external network.
/// This is their Ristretto key, used for publishing data onto Serai, and their key on the external
/// network.
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
Expand Down Expand Up @@ -81,12 +95,12 @@ impl Zeroize for KeyPair {

/// The MuSig context for a validator set.
pub fn musig_context(set: ValidatorSet) -> Vec<u8> {
[b"ValidatorSets-musig_key".as_ref(), &set.encode()].concat()
(b"ValidatorSets-musig_key".as_ref(), set).encode()
}

/// The MuSig public key for a validator set.
///
/// This function panics on invalid input.
/// This function panics on invalid input, per the definition of `dkg::musig::musig_key`.
pub fn musig_key(set: ValidatorSet, set_keys: &[Public]) -> Public {
let mut keys = Vec::new();
for key in set_keys {
Expand All @@ -98,33 +112,11 @@ pub fn musig_key(set: ValidatorSet, set_keys: &[Public]) -> Public {
Public(dkg::musig::musig_key::<Ristretto>(&musig_context(set), &keys).unwrap().to_bytes())
}

/// The message for the set_keys signature.
/// The message for the `set_keys` signature.
pub fn set_keys_message(set: &ValidatorSet, key_pair: &KeyPair) -> Vec<u8> {
(b"ValidatorSets-set_keys", set, key_pair).encode()
}

#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Slash {
#[cfg_attr(
feature = "borsh",
borsh(
serialize_with = "serai_primitives::borsh_serialize_public",
deserialize_with = "serai_primitives::borsh_deserialize_public"
)
)]
pub key: Public,
pub points: u32,
}
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SlashReport(pub BoundedVec<Slash, ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>);

pub fn report_slashes_message(set: &ValidatorSet, slashes: &SlashReport) -> Vec<u8> {
(b"ValidatorSets-report_slashes", set, slashes).encode()
}

/// For a set of validators whose key shares may exceed the maximum, reduce until they equal the
/// maximum.
///
Expand Down
Loading

0 comments on commit a7fef2b

Please sign in to comment.