Skip to content

Commit

Permalink
Gj/fix thea rotation (#977)
Browse files Browse the repository at this point in the history
## Describe your changes

Forces thea to use outgoing set to sign the Thea rotation messages to
smoothly handover the session change
  • Loading branch information
Gauthamastro authored Aug 12, 2024
2 parents 071b1f9 + 4011469 commit c92a82e
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 128 deletions.
9 changes: 4 additions & 5 deletions pallets/thea-message-handler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ pub mod pallet {
let current_set_id = <ValidatorSetId<T>>::get();

match payload.message.payload_type {
PayloadType::ScheduledRotateValidators => {
PayloadType::ScheduledRotateValidators => {}, // Deprecated
PayloadType::ValidatorsRotated => {
// Thea message related to key change
match ValidatorSet::decode(&mut payload.message.data.as_ref()) {
Err(_err) => return Err(Error::<T>::ErrorDecodingValidatorSet.into()),
Expand All @@ -218,13 +219,11 @@ pub mod pallet {
validator_set.set_id,
BoundedVec::truncate_from(validator_set.validators),
);
// We are checking if the validator set is changed, then we update it here too
<ValidatorSetId<T>>::put(current_set_id.saturating_add(1));
},
}
},
PayloadType::ValidatorsRotated => {
// We are checking if the validator set is changed, then we update it here too
<ValidatorSetId<T>>::put(current_set_id.saturating_add(1));
},
PayloadType::L1Deposit => {
// Normal Thea message
T::Executor::execute_deposits(
Expand Down
21 changes: 6 additions & 15 deletions pallets/thea-message-handler/src/test.rs

Large diffs are not rendered by default.

181 changes: 95 additions & 86 deletions pallets/thea/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use sp_runtime::{
use sp_std::collections::btree_set::BTreeSet;
use sp_std::prelude::*;
use thea_primitives::{
types::{Message, NetworkType, PayloadType},
types::{Message, PayloadType},
Network, ValidatorSet, GENESIS_AUTHORITY_SET_ID,
};

Expand Down Expand Up @@ -758,103 +758,112 @@ impl<T: Config> Pallet<T> {

fn change_authorities(
incoming: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+1th set
queued: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+ 2th set
_queued: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+ 2th set
) {
// ( outgoing) -> (validators/incoming) -> (queued)
// nth epoch -> n+1th epoch -> n+2nd epoch
let id = Self::validator_set_id();
let outgoing = <Authorities<T>>::get(id); // nth set ( active ,current )
let new_id = id + 1u64;
let active_networks = <ActiveNetworks<T>>::get();
// We need to issue a new message if the validator set is changing,
// that is, the incoming set is has different session keys from outgoing set.
// This last message should be signed by the outgoing set
// Similar to how Grandpa's session change works.
// // We need to issue a new message if the validator set is changing,
// // that is, the incoming set is has different session keys from outgoing set.
// // This last message should be signed by the outgoing set
// // Similar to how Grandpa's session change works.
let incoming_set = BTreeSet::from_iter(incoming.to_vec());
if incoming_set != BTreeSet::from_iter(queued.to_vec()) {
let uncompressed_keys: Vec<[u8; 20]> = vec![];
// TODO: Uncomment the following when parsing is fixed for ethereum keys.
// for public_key in queued.clone().into_iter() {
// let public_key: sp_core::ecdsa::Public = public_key.into();
// if public_key.0 == [0u8; 33] {
// uncompressed_keys.push([0u8; 20]);
// continue;
// }
// if let Ok(compressed_key) = libsecp256k1::PublicKey::parse_compressed(&public_key.0)
// {
// let uncompressed_key = compressed_key.serialize();
// let uncompressed_key: [u8; 64] =
// if let Ok(uncompressed_key) = uncompressed_key[1..65].try_into() {
// uncompressed_key
// } else {
// log::error!(target: "thea", "Unable to slice last 64 bytes of uncompressed_key for Evm");
// Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
// public_key.into(),
// ));
// return;
// };
// let hash: [u8; 32] = sp_io::hashing::keccak_256(&uncompressed_key);
// if let Ok(address) = hash[12..32].try_into() {
// uncompressed_keys.push(address);
// } else {
// log::error!(target: "thea", "Unable to slice last 20 bytes of hash for Evm");
// Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
// public_key.into(),
// ));
// return;
// }
// } else {
// log::error!(target: "thea", "Unable to parse compressed key");
// Self::deposit_event(Event::<T>::UnableToParsePublicKey(public_key.into()));
// return;
// }
// }
for network in &active_networks {
let network_config = <NetworkConfig<T>>::get(*network);
let message = match network_config.network_type {
NetworkType::Evm => {
if let Some(payload) = ValidatorSet::new(uncompressed_keys.clone(), new_id)
{
Self::generate_payload(
PayloadType::ScheduledRotateValidators,
*network,
payload.encode(),
)
} else {
log::error!(target: "thea", "Unable to generate rotate validators payload");
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
continue;
}
},
NetworkType::Parachain => {
if let Some(payload) = ValidatorSet::new(queued.clone(), new_id) {
Self::generate_payload(
PayloadType::ScheduledRotateValidators,
*network,
payload.encode(),
)
} else {
log::error!(target: "thea", "Unable to generate rotate validators payload");
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
continue;
}
},
};
<OutgoingNonce<T>>::insert(message.network, message.nonce);
<OutgoingMessages<T>>::insert(message.network, message.nonce, message);
}
<NextAuthorities<T>>::put(queued);
}
// if incoming_set != BTreeSet::from_iter(queued.to_vec()) {
// let uncompressed_keys: Vec<[u8; 20]> = vec![];
// // TODO: Uncomment the following when parsing is fixed for ethereum keys.
// // for public_key in queued.clone().into_iter() {
// // let public_key: sp_core::ecdsa::Public = public_key.into();
// // if public_key.0 == [0u8; 33] {
// // uncompressed_keys.push([0u8; 20]);
// // continue;
// // }
// // if let Ok(compressed_key) = libsecp256k1::PublicKey::parse_compressed(&public_key.0)
// // {
// // let uncompressed_key = compressed_key.serialize();
// // let uncompressed_key: [u8; 64] =
// // if let Ok(uncompressed_key) = uncompressed_key[1..65].try_into() {
// // uncompressed_key
// // } else {
// // log::error!(target: "thea", "Unable to slice last 64 bytes of uncompressed_key for Evm");
// // Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
// // public_key.into(),
// // ));
// // return;
// // };
// // let hash: [u8; 32] = sp_io::hashing::keccak_256(&uncompressed_key);
// // if let Ok(address) = hash[12..32].try_into() {
// // uncompressed_keys.push(address);
// // } else {
// // log::error!(target: "thea", "Unable to slice last 20 bytes of hash for Evm");
// // Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
// // public_key.into(),
// // ));
// // return;
// // }
// // } else {
// // log::error!(target: "thea", "Unable to parse compressed key");
// // Self::deposit_event(Event::<T>::UnableToParsePublicKey(public_key.into()));
// // return;
// // }
// // }
// for network in &active_networks {
// let network_config = <NetworkConfig<T>>::get(*network);
// let message = match network_config.network_type {
// NetworkType::Evm => {
// if let Some(payload) = ValidatorSet::new(uncompressed_keys.clone(), new_id)
// {
// Self::generate_payload(
// PayloadType::ScheduledRotateValidators,
// *network,
// payload.encode(),
// )
// } else {
// log::error!(target: "thea", "Unable to generate rotate validators payload");
// Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
// continue;
// }
// },
// NetworkType::Parachain => {
// if let Some(payload) = ValidatorSet::new(queued.clone(), new_id) {
// Self::generate_payload(
// PayloadType::ScheduledRotateValidators,
// *network,
// payload.encode(),
// )
// } else {
// log::error!(target: "thea", "Unable to generate rotate validators payload");
// Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
// continue;
// }
// },
// };
// <OutgoingNonce<T>>::insert(message.network, message.nonce);
// <OutgoingMessages<T>>::insert(message.network, message.nonce, message);
// }
// <NextAuthorities<T>>::put(queued);
// }
if incoming_set != BTreeSet::from_iter(outgoing.to_vec()) {
// This will happen when new era starts, or end of the last epoch
<Authorities<T>>::insert(new_id, incoming);
<ValidatorSetId<T>>::put(new_id);
for network in active_networks {
let message =
Self::generate_payload(PayloadType::ValidatorsRotated, network, Vec::new()); //Empty data means activate the next set_id
<OutgoingNonce<T>>::insert(network, message.nonce);
<OutgoingMessages<T>>::insert(network, message.nonce, message);
if let Some(payload) = ValidatorSet::new(incoming.clone(), new_id) {
let message = Self::generate_payload(
PayloadType::ValidatorsRotated,
network,
payload.encode(),
);
<OutgoingNonce<T>>::insert(network, message.nonce);
<OutgoingMessages<T>>::insert(network, message.nonce, message);
} else {
log::error!(target: "thea", "Unable to generate rotate validators payload");
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(network));
continue;
}
}
<Authorities<T>>::insert(new_id, incoming);
<ValidatorSetId<T>>::put(new_id);
}
}

Expand Down
16 changes: 6 additions & 10 deletions pallets/thea/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,12 @@ fn test_session_change() {
// Simulating the on_new_session to last epoch of an era.
Thea::on_new_session(false, authorities.into_iter(), queued.clone().into_iter());
assert!(Thea::validator_set_id() == 0);
assert!(Thea::outgoing_nonce(1) == 1); // Thea validator session change message is generated here
assert!(Thea::outgoing_nonce(1) == 0); // Thea validator session change message is not generated here on new change only when session actually changes

// Simulating the on_new_session to the first epoch of the next era.
Thea::on_new_session(false, queued.clone().into_iter(), queued.clone().into_iter());
assert!(Thea::validator_set_id() == 1);
assert!(Thea::outgoing_nonce(1) == 1);
let message = Thea::get_outgoing_messages(1, 1).unwrap();
assert_eq!(message.nonce, 1);
let validator_set: ValidatorSet<<Test as Config>::TheaId> =
Expand All @@ -93,14 +97,6 @@ fn test_session_change() {
queued.iter().map(|(_, public)| public.clone()).collect();
assert_eq!(validator_set.set_id, 1);
assert_eq!(validator_set.validators, queued_validators);

// Simulating the on_new_session to the first epoch of the next era.
Thea::on_new_session(false, queued.clone().into_iter(), queued.clone().into_iter());
assert!(Thea::validator_set_id() == 1);
assert!(Thea::outgoing_nonce(1) == 2);
let message = Thea::get_outgoing_messages(1, 2).unwrap();
assert_eq!(message.nonce, 2);
assert!(message.data.is_empty());
})
}

Expand Down Expand Up @@ -320,11 +316,11 @@ fn test_report_misbehaviour_happy_path() {
assert_ok!(Thea::report_misbehaviour(RuntimeOrigin::signed(fisherman), network, 1));
})
}

use frame_support::{
assert_noop,
traits::{fungible::MutateHold, tokens::Precision},
};
use thea_primitives::types::NetworkType;
use thea_primitives::types::{AssetMetadata, IncomingMessage, SignedMessage, THEA_HOLD_REASON};

#[test]
Expand Down
65 changes: 56 additions & 9 deletions pallets/thea/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use frame_system::{offchain::SubmitTransaction, pallet_prelude::BlockNumberFor};
use parity_scale_codec::Encode;
use sp_application_crypto::RuntimeAppPublic;
use sp_std::vec::Vec;
use thea_primitives::types::PayloadType;
use thea_primitives::Network;

impl<T: Config> Pallet<T> {
Expand All @@ -37,8 +38,11 @@ impl<T: Config> Pallet<T> {
return Ok(());
}

let id = <ValidatorSetId<T>>::get();
let mut id = <ValidatorSetId<T>>::get();
let id_prev = id.saturating_sub(1);

let authorities = <Authorities<T>>::get(id).to_vec();
let prev_authorities = <Authorities<T>>::get(id_prev).to_vec();

let local_keys = T::TheaId::all();

Expand All @@ -54,9 +58,23 @@ impl<T: Config> Pallet<T> {
.collect::<Vec<(usize, T::TheaId)>>();
available_keys.sort();

let (auth_index, signer) = available_keys.first().ok_or("No active keys available")?;
let (mut auth_index, signer) = available_keys.first().ok_or("No active keys available")?;
log::info!(target: "thea", "Auth Index {:?} signer {:?}", auth_index, signer.clone());

let local_keys = T::TheaId::all();
// Fetching the available keys from previous set
let mut prev_available_keys = prev_authorities
.iter()
.enumerate()
.filter_map(move |(auth_index, authority)| {
local_keys
.binary_search(authority)
.ok()
.map(|location| (auth_index, local_keys[location].clone()))
})
.collect::<Vec<(usize, T::TheaId)>>();
prev_available_keys.sort();

let active_networks = <ActiveNetworks<T>>::get();
log::info!(target:"thea","List of active networks: {:?}",active_networks);

Expand All @@ -71,7 +89,7 @@ impl<T: Config> Pallet<T> {
None => {},
Some(signed_msg) => {
// Don't sign again if we already signed it
if signed_msg.contains_signature(&(*auth_index as u32)) {
if signed_msg.contains_signature(&(auth_index as u32)) {
log::warn!(target:"thea","Next outgoing nonce for network {:?} is: {:?} is already signed ",network, next_outgoing_nonce);
continue;
}
Expand All @@ -82,19 +100,48 @@ impl<T: Config> Pallet<T> {
Some(msg) => msg,
};

let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
// Note: this is a double hash signing
let signature =
sp_io::crypto::ecdsa_sign_prehashed(THEA, &signer.clone().into(), &msg_hash)
match message.payload_type {
PayloadType::ScheduledRotateValidators => {
log::warn!(target: "thea", "Ignoring ScheduledRotateValidators message for thea");
},
PayloadType::ValidatorsRotated => {
// if its validator rotated, then only the previous set should sign it.
let (prev_auth_index, prev_signer) = prev_available_keys.first().ok_or(
"No active keys available from previous set to sign rotation message",
)?;
log::info!(target: "thea", "Previous Auth Index {:?} previous signer {:?}", prev_auth_index, prev_signer.clone());

let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
// Note: this is a double hash signing
let signature = sp_io::crypto::ecdsa_sign_prehashed(
THEA,
&prev_signer.clone().into(),
&msg_hash,
)
.ok_or("Expected signature to be returned")?;
signed_messages.push((network, next_outgoing_nonce, signature.into()));
signed_messages.push((network, next_outgoing_nonce, signature.into()));
id = id_prev; // We need to set the id to prev for unsigned validation to pass
auth_index = *prev_auth_index; // We need to set the id to prev for unsigned validation to pass
},
PayloadType::L1Deposit => {
let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
// Note: this is a double hash signing
let signature = sp_io::crypto::ecdsa_sign_prehashed(
THEA,
&signer.clone().into(),
&msg_hash,
)
.ok_or("Expected signature to be returned")?;
signed_messages.push((network, next_outgoing_nonce, signature.into()));
},
}
}

if !signed_messages.is_empty() {
// we batch these signatures into a single extrinsic and submit on-chain
if let Err(()) = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(
Call::<T>::submit_signed_outgoing_messages {
auth_index: *auth_index as u32,
auth_index: auth_index as u32,
id,
signatures: signed_messages,
}
Expand Down
2 changes: 1 addition & 1 deletion primitives/thea/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ pub struct IncomingMessage<AccountId, Balance> {
Clone, Encode, Decode, TypeInfo, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize,
)]
pub enum PayloadType {
ScheduledRotateValidators,
ScheduledRotateValidators, // Deprecated
ValidatorsRotated,
L1Deposit,
}
Expand Down
Loading

0 comments on commit c92a82e

Please sign in to comment.