mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Slash reports (#523)
* report_slashes plumbing in Substrate Notably delays the SetRetired event until it provides a slash report or the set after it becomes the set to report its slashes. * Add dedicated AcceptedHandover event * Add SlashReport TX to Tributary * Create SlashReport TXs * Handle SlashReport TXs * Add logic to generate a SlashReport to the coordinator * Route SlashReportSigner into the processor * Finish routing the SlashReport signing/TX publication * Add serai feature to processor's serai-client
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
use sp_core::{ConstU32, bounded_vec::BoundedVec};
|
||||
|
||||
pub use serai_validator_sets_primitives as primitives;
|
||||
|
||||
use serai_primitives::*;
|
||||
@@ -12,6 +14,11 @@ pub enum Call {
|
||||
key_pair: KeyPair,
|
||||
signature: Signature,
|
||||
},
|
||||
report_slashes {
|
||||
network: NetworkId,
|
||||
slashes: BoundedVec<(SeraiAddress, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
|
||||
signature: Signature,
|
||||
},
|
||||
allocate {
|
||||
network: NetworkId,
|
||||
amount: Amount,
|
||||
@@ -41,6 +48,12 @@ pub enum Event {
|
||||
set: ValidatorSet,
|
||||
key_pair: KeyPair,
|
||||
},
|
||||
AcceptedHandover {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
SetRetired {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
AllocationIncreased {
|
||||
validator: SeraiAddress,
|
||||
network: NetworkId,
|
||||
@@ -57,7 +70,4 @@ pub enum Event {
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
},
|
||||
SetRetired {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -69,6 +69,23 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn accepted_handover_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
.events(|event| {
|
||||
if let serai_abi::Event::ValidatorSets(event) = event {
|
||||
if matches!(event, ValidatorSetsEvent::AcceptedHandover { .. }) {
|
||||
Some(event.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_retired_events(&self) -> Result<Vec<ValidatorSetsEvent>, SeraiError> {
|
||||
self
|
||||
.0
|
||||
@@ -131,6 +148,13 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await
|
||||
}
|
||||
|
||||
pub async fn key_pending_slash_report(
|
||||
&self,
|
||||
network: NetworkId,
|
||||
) -> Result<Option<Public>, SeraiError> {
|
||||
self.0.storage(PALLET, "PendingSlashReport", network).await
|
||||
}
|
||||
|
||||
pub fn set_keys(
|
||||
network: NetworkId,
|
||||
removed_participants: Vec<SeraiAddress>,
|
||||
@@ -144,4 +168,17 @@ impl<'a> SeraiValidatorSets<'a> {
|
||||
signature,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn report_slashes(
|
||||
network: NetworkId,
|
||||
slashes: sp_runtime::BoundedVec<
|
||||
(SeraiAddress, u32),
|
||||
sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>,
|
||||
>,
|
||||
signature: Signature,
|
||||
) -> Transaction {
|
||||
Serai::unsigned(serai_abi::Call::ValidatorSets(
|
||||
serai_abi::validator_sets::Call::report_slashes { network, slashes, signature },
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +307,10 @@ pub mod pallet {
|
||||
#[pallet::getter(fn keys)]
|
||||
pub type Keys<T: Config> = StorageMap<_, Twox64Concat, ValidatorSet, KeyPair, OptionQuery>;
|
||||
|
||||
/// The key for validator sets which can (and still need to) publish their slash reports.
|
||||
#[pallet::storage]
|
||||
pub type PendingSlashReport<T: Config> = StorageMap<_, Identity, NetworkId, Public, OptionQuery>;
|
||||
|
||||
/// Disabled validators.
|
||||
#[pallet::storage]
|
||||
pub type SeraiDisabledIndices<T: Config> = StorageMap<_, Identity, u32, Public, OptionQuery>;
|
||||
@@ -325,6 +329,12 @@ pub mod pallet {
|
||||
set: ValidatorSet,
|
||||
key_pair: KeyPair,
|
||||
},
|
||||
AcceptedHandover {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
SetRetired {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
AllocationIncreased {
|
||||
validator: T::AccountId,
|
||||
network: NetworkId,
|
||||
@@ -341,9 +351,6 @@ pub mod pallet {
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
},
|
||||
SetRetired {
|
||||
set: ValidatorSet,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
@@ -681,8 +688,21 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
pub fn retire_set(set: ValidatorSet) {
|
||||
Keys::<T>::remove(set);
|
||||
Pallet::<T>::deposit_event(Event::SetRetired { set });
|
||||
let keys = Keys::<T>::take(set).unwrap();
|
||||
// If the prior prior set didn't report, emit they're retired now
|
||||
if PendingSlashReport::<T>::get(set.network).is_some() {
|
||||
Self::deposit_event(Event::SetRetired {
|
||||
set: ValidatorSet { network: set.network, session: Session(set.session.0 - 1) },
|
||||
});
|
||||
}
|
||||
// This overwrites the prior value as the prior to-report set's stake presumably just
|
||||
// unlocked, making their report unenforceable
|
||||
PendingSlashReport::<T>::set(set.network, Some(keys.0));
|
||||
|
||||
// We're retiring this set because the set after it accepted the handover
|
||||
Self::deposit_event(Event::AcceptedHandover {
|
||||
set: ValidatorSet { network: set.network, session: Session(set.session.0 + 1) },
|
||||
});
|
||||
}
|
||||
|
||||
/// Take the amount deallocatable.
|
||||
@@ -883,6 +903,31 @@ pub mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn report_slashes(
|
||||
origin: OriginFor<T>,
|
||||
network: NetworkId,
|
||||
slashes: BoundedVec<(Public, u32), ConstU32<{ MAX_KEY_SHARES_PER_SET / 3 }>>,
|
||||
signature: Signature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
// signature isn't checked as this is an unsigned transaction, and validate_unsigned
|
||||
// (called by pre_dispatch) checks it
|
||||
let _ = signature;
|
||||
|
||||
// TODO: Handle slashes
|
||||
let _ = slashes;
|
||||
|
||||
// Emit set retireed
|
||||
Pallet::<T>::deposit_event(Event::SetRetired {
|
||||
set: ValidatorSet { network, session: Session(Self::session(network).unwrap().0 - 1) },
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(0)] // TODO
|
||||
pub fn allocate(origin: OriginFor<T>, network: NetworkId, amount: Amount) -> DispatchResult {
|
||||
@@ -1012,11 +1057,34 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("ValidatorSets")
|
||||
.and_provides(set)
|
||||
.and_provides((0, set))
|
||||
.longevity(u64::MAX)
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
Call::report_slashes { network, ref slashes, ref signature } => {
|
||||
let network = *network;
|
||||
// Don't allow Serai to publish a slash report as BABE/GRANDPA handles slashes directly
|
||||
if network == NetworkId::Serai {
|
||||
Err(InvalidTransaction::Custom(0))?;
|
||||
}
|
||||
let Some(key) = PendingSlashReport::<T>::take(network) else {
|
||||
// Assumed already published
|
||||
Err(InvalidTransaction::Stale)?
|
||||
};
|
||||
// There must have been a previous session is PendingSlashReport is populated
|
||||
let set =
|
||||
ValidatorSet { network, session: Session(Self::session(network).unwrap().0 - 1) };
|
||||
if !key.verify(&report_slashes_message(&set, slashes), signature) {
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("ValidatorSets")
|
||||
.and_provides((1, set))
|
||||
.longevity(MAX_KEY_SHARES_PER_SET.into())
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
Call::allocate { .. } | Call::deallocate { .. } | Call::claim_deallocation { .. } => {
|
||||
Err(InvalidTransaction::Call)?
|
||||
}
|
||||
|
||||
@@ -107,6 +107,10 @@ pub fn set_keys_message(
|
||||
(b"ValidatorSets-set_keys", set, removed_participants, key_pair).encode()
|
||||
}
|
||||
|
||||
pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) -> 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.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user