Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gj/fix thea rotation #977

Merged
merged 5 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading