Properly define the on-chain handover protocol

The new key publishing `Batch`s is more than sufficient.

Also uses the correct key to verify the published `Batch`s authenticity.
This commit is contained in:
Luke Parker
2023-10-10 23:55:59 -04:00
parent 1a0b4198ba
commit 13cbc99149
5 changed files with 55 additions and 36 deletions

View File

@@ -50,5 +50,6 @@ std = [
"in-instructions-primitives/std",
"tokens-pallet/std",
"validator-sets-pallet/std",
]
default = ["std"]

View File

@@ -80,29 +80,21 @@ pub mod pallet {
}
}
fn key_for_network<T: Config>(network: NetworkId) -> Result<Public, InvalidTransaction> {
// TODO: Get the latest session
let session = Session(0);
fn key_for_network<T: Config>(network: NetworkId) -> Result<(Session, Option<Public>, Option<Public>), InvalidTransaction> {
let session = ValidatorSets::<T>::session(network);
let mut set = ValidatorSet { session, network };
// TODO: If this session just set their keys, it'll invalidate any batches in the mempool
// Should there be a transitory period/future-set cut off?
if let Some(keys) = ValidatorSets::<T>::keys(set) {
Ok(keys.0)
} else {
// If this set hasn't set their keys yet, use the previous set's
if set.session.0 == 0 {
// Since there haven't been any keys set, no signature can legitimately exist
Err(InvalidTransaction::BadProof)?;
}
let latest = ValidatorSets::<T>::keys(set).map(|keys| keys.0);
let prior = if set.session.0 != 0 {
set.session.0 -= 1;
if let Some(keys) = ValidatorSets::<T>::keys(set) {
Ok(keys.0)
} else {
Err(InvalidTransaction::BadProof)?
}
ValidatorSets::<T>::keys(set).map(|keys| keys.0)
} else {
None
};
// If there's no keys set, then this must be an invalid signature
if prior.is_none() && latest.is_none() {
Err(InvalidTransaction::BadProof)?;
}
Ok((session, prior, latest))
}
#[pallet::call]
@@ -155,7 +147,7 @@ pub mod pallet {
};
let network = batch.batch.network;
let key = key_for_network::<T>(network)?;
let (current_session, prior, current) = key_for_network::<T>(network)?;
// verify the batch size
// TODO: Merge this encode with the one done by batch_message
@@ -164,10 +156,26 @@ pub mod pallet {
}
// verify the signature
if !key.verify(&batch_message(&batch.batch), &batch.signature) {
let batch_message = batch_message(&batch.batch);
// Check the prior key first since only a single `Batch` (the last one) will be when prior is
// Some yet prior wasn't the signing key
let valid_by_prior = if let Some(key) = prior {
key.verify(&batch_message, &batch.signature)
} else { false };
let valid = valid_by_prior || (if let Some(key) = current {
key.verify(&batch_message, &batch.signature)
} else { false });
if !valid {
Err(InvalidTransaction::BadProof)?;
}
// If it wasn't valid by the prior key, meaning it was valid by the current key, the current
// key is publishing `Batch`s. This should only happen once the current key has verified all
// `Batch`s published by the prior key, meaning they are accepting the hand-over.
if prior.is_some() && (!valid_by_prior) {
ValidatorSets::<T>::retire_session(network, Session(current_session.0 - 1));
}
// check that this validator set isn't publishing a batch more than once per block
let current_block = <frame_system::Pallet<T>>::block_number();
let last_block = LastBatchBlock::<T>::get(network).unwrap_or(Zero::zero());

View File

@@ -58,7 +58,7 @@ pub mod pallet {
#[pallet::storage]
pub type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, Session, OptionQuery>;
impl<T: Config> Pallet<T> {
fn session(network: NetworkId) -> Session {
pub fn session(network: NetworkId) -> Session {
if network == NetworkId::Serai {
Session(pallet_session::Pallet::<T>::current_index())
} else {
@@ -206,12 +206,6 @@ pub mod pallet {
let set = ValidatorSet { network, session };
Pallet::<T>::deposit_event(Event::NewSet { set });
if network != NetworkId::Serai {
// Remove the keys for the set prior to the one now rotating out
if session.0 >= 2 {
let prior_to_now_rotating = ValidatorSet { network, session: Session(session.0 - 2) };
MuSigKeys::<T>::remove(prior_to_now_rotating);
Keys::<T>::remove(prior_to_now_rotating);
}
MuSigKeys::<T>::set(set, Some(musig_key(set, &participants)));
}
Participants::<T>::set(network, participants.try_into().unwrap());
@@ -398,8 +392,16 @@ pub mod pallet {
// Handover is automatically complete for Serai as it doesn't have a handover protocol
// TODO: Update how handover completed is determined. It's not on set keys. It's on new
// set accepting responsibility
let handover_completed = (network == NetworkId::Serai) ||
Keys::<T>::contains_key(ValidatorSet { network, session: Self::session(network) });
let handover_completed = (network == NetworkId::Serai) || {
let current_session = Self::session(network);
// This function shouldn't be used on genesis
debug_assert!(current_session != Session(0));
// Check the prior session had its keys cleared, which happens once its retired
!Keys::<T>::contains_key(ValidatorSet {
network,
session: Session(current_session.0 - 1),
})
};
// Only spawn a NewSet if the current set was actually established with a completed
// handover protocol
if handover_completed {
@@ -411,6 +413,12 @@ pub mod pallet {
pub fn validators(network: NetworkId) -> Vec<Public> {
Self::participants(network).into()
}
pub fn retire_session(network: NetworkId, session: Session) {
let set = ValidatorSet { network, session };
MuSigKeys::<T>::remove(set);
Keys::<T>::remove(set);
}
}
}