Skip to content

Commit c92a82e

Browse files
authored
Gj/fix thea rotation (#977)
## Describe your changes Forces thea to use outgoing set to sign the Thea rotation messages to smoothly handover the session change
2 parents 071b1f9 + 4011469 commit c92a82e

File tree

8 files changed

+170
-128
lines changed

8 files changed

+170
-128
lines changed

pallets/thea-message-handler/src/lib.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ pub mod pallet {
205205
let current_set_id = <ValidatorSetId<T>>::get();
206206

207207
match payload.message.payload_type {
208-
PayloadType::ScheduledRotateValidators => {
208+
PayloadType::ScheduledRotateValidators => {}, // Deprecated
209+
PayloadType::ValidatorsRotated => {
209210
// Thea message related to key change
210211
match ValidatorSet::decode(&mut payload.message.data.as_ref()) {
211212
Err(_err) => return Err(Error::<T>::ErrorDecodingValidatorSet.into()),
@@ -218,13 +219,11 @@ pub mod pallet {
218219
validator_set.set_id,
219220
BoundedVec::truncate_from(validator_set.validators),
220221
);
222+
// We are checking if the validator set is changed, then we update it here too
223+
<ValidatorSetId<T>>::put(current_set_id.saturating_add(1));
221224
},
222225
}
223226
},
224-
PayloadType::ValidatorsRotated => {
225-
// We are checking if the validator set is changed, then we update it here too
226-
<ValidatorSetId<T>>::put(current_set_id.saturating_add(1));
227-
},
228227
PayloadType::L1Deposit => {
229228
// Normal Thea message
230229
T::Executor::execute_deposits(

pallets/thea-message-handler/src/test.rs

Lines changed: 6 additions & 15 deletions
Large diffs are not rendered by default.

pallets/thea/src/lib.rs

Lines changed: 95 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use sp_runtime::{
4040
use sp_std::collections::btree_set::BTreeSet;
4141
use sp_std::prelude::*;
4242
use thea_primitives::{
43-
types::{Message, NetworkType, PayloadType},
43+
types::{Message, PayloadType},
4444
Network, ValidatorSet, GENESIS_AUTHORITY_SET_ID,
4545
};
4646

@@ -758,103 +758,112 @@ impl<T: Config> Pallet<T> {
758758

759759
fn change_authorities(
760760
incoming: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+1th set
761-
queued: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+ 2th set
761+
_queued: BoundedVec<T::TheaId, T::MaxAuthorities>, // n+ 2th set
762762
) {
763763
// ( outgoing) -> (validators/incoming) -> (queued)
764764
// nth epoch -> n+1th epoch -> n+2nd epoch
765765
let id = Self::validator_set_id();
766766
let outgoing = <Authorities<T>>::get(id); // nth set ( active ,current )
767767
let new_id = id + 1u64;
768768
let active_networks = <ActiveNetworks<T>>::get();
769-
// We need to issue a new message if the validator set is changing,
770-
// that is, the incoming set is has different session keys from outgoing set.
771-
// This last message should be signed by the outgoing set
772-
// Similar to how Grandpa's session change works.
769+
// // We need to issue a new message if the validator set is changing,
770+
// // that is, the incoming set is has different session keys from outgoing set.
771+
// // This last message should be signed by the outgoing set
772+
// // Similar to how Grandpa's session change works.
773773
let incoming_set = BTreeSet::from_iter(incoming.to_vec());
774-
if incoming_set != BTreeSet::from_iter(queued.to_vec()) {
775-
let uncompressed_keys: Vec<[u8; 20]> = vec![];
776-
// TODO: Uncomment the following when parsing is fixed for ethereum keys.
777-
// for public_key in queued.clone().into_iter() {
778-
// let public_key: sp_core::ecdsa::Public = public_key.into();
779-
// if public_key.0 == [0u8; 33] {
780-
// uncompressed_keys.push([0u8; 20]);
781-
// continue;
782-
// }
783-
// if let Ok(compressed_key) = libsecp256k1::PublicKey::parse_compressed(&public_key.0)
784-
// {
785-
// let uncompressed_key = compressed_key.serialize();
786-
// let uncompressed_key: [u8; 64] =
787-
// if let Ok(uncompressed_key) = uncompressed_key[1..65].try_into() {
788-
// uncompressed_key
789-
// } else {
790-
// log::error!(target: "thea", "Unable to slice last 64 bytes of uncompressed_key for Evm");
791-
// Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
792-
// public_key.into(),
793-
// ));
794-
// return;
795-
// };
796-
// let hash: [u8; 32] = sp_io::hashing::keccak_256(&uncompressed_key);
797-
// if let Ok(address) = hash[12..32].try_into() {
798-
// uncompressed_keys.push(address);
799-
// } else {
800-
// log::error!(target: "thea", "Unable to slice last 20 bytes of hash for Evm");
801-
// Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
802-
// public_key.into(),
803-
// ));
804-
// return;
805-
// }
806-
// } else {
807-
// log::error!(target: "thea", "Unable to parse compressed key");
808-
// Self::deposit_event(Event::<T>::UnableToParsePublicKey(public_key.into()));
809-
// return;
810-
// }
811-
// }
812-
for network in &active_networks {
813-
let network_config = <NetworkConfig<T>>::get(*network);
814-
let message = match network_config.network_type {
815-
NetworkType::Evm => {
816-
if let Some(payload) = ValidatorSet::new(uncompressed_keys.clone(), new_id)
817-
{
818-
Self::generate_payload(
819-
PayloadType::ScheduledRotateValidators,
820-
*network,
821-
payload.encode(),
822-
)
823-
} else {
824-
log::error!(target: "thea", "Unable to generate rotate validators payload");
825-
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
826-
continue;
827-
}
828-
},
829-
NetworkType::Parachain => {
830-
if let Some(payload) = ValidatorSet::new(queued.clone(), new_id) {
831-
Self::generate_payload(
832-
PayloadType::ScheduledRotateValidators,
833-
*network,
834-
payload.encode(),
835-
)
836-
} else {
837-
log::error!(target: "thea", "Unable to generate rotate validators payload");
838-
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
839-
continue;
840-
}
841-
},
842-
};
843-
<OutgoingNonce<T>>::insert(message.network, message.nonce);
844-
<OutgoingMessages<T>>::insert(message.network, message.nonce, message);
845-
}
846-
<NextAuthorities<T>>::put(queued);
847-
}
774+
// if incoming_set != BTreeSet::from_iter(queued.to_vec()) {
775+
// let uncompressed_keys: Vec<[u8; 20]> = vec![];
776+
// // TODO: Uncomment the following when parsing is fixed for ethereum keys.
777+
// // for public_key in queued.clone().into_iter() {
778+
// // let public_key: sp_core::ecdsa::Public = public_key.into();
779+
// // if public_key.0 == [0u8; 33] {
780+
// // uncompressed_keys.push([0u8; 20]);
781+
// // continue;
782+
// // }
783+
// // if let Ok(compressed_key) = libsecp256k1::PublicKey::parse_compressed(&public_key.0)
784+
// // {
785+
// // let uncompressed_key = compressed_key.serialize();
786+
// // let uncompressed_key: [u8; 64] =
787+
// // if let Ok(uncompressed_key) = uncompressed_key[1..65].try_into() {
788+
// // uncompressed_key
789+
// // } else {
790+
// // log::error!(target: "thea", "Unable to slice last 64 bytes of uncompressed_key for Evm");
791+
// // Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
792+
// // public_key.into(),
793+
// // ));
794+
// // return;
795+
// // };
796+
// // let hash: [u8; 32] = sp_io::hashing::keccak_256(&uncompressed_key);
797+
// // if let Ok(address) = hash[12..32].try_into() {
798+
// // uncompressed_keys.push(address);
799+
// // } else {
800+
// // log::error!(target: "thea", "Unable to slice last 20 bytes of hash for Evm");
801+
// // Self::deposit_event(Event::<T>::UnableToSlicePublicKeyHash(
802+
// // public_key.into(),
803+
// // ));
804+
// // return;
805+
// // }
806+
// // } else {
807+
// // log::error!(target: "thea", "Unable to parse compressed key");
808+
// // Self::deposit_event(Event::<T>::UnableToParsePublicKey(public_key.into()));
809+
// // return;
810+
// // }
811+
// // }
812+
// for network in &active_networks {
813+
// let network_config = <NetworkConfig<T>>::get(*network);
814+
// let message = match network_config.network_type {
815+
// NetworkType::Evm => {
816+
// if let Some(payload) = ValidatorSet::new(uncompressed_keys.clone(), new_id)
817+
// {
818+
// Self::generate_payload(
819+
// PayloadType::ScheduledRotateValidators,
820+
// *network,
821+
// payload.encode(),
822+
// )
823+
// } else {
824+
// log::error!(target: "thea", "Unable to generate rotate validators payload");
825+
// Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
826+
// continue;
827+
// }
828+
// },
829+
// NetworkType::Parachain => {
830+
// if let Some(payload) = ValidatorSet::new(queued.clone(), new_id) {
831+
// Self::generate_payload(
832+
// PayloadType::ScheduledRotateValidators,
833+
// *network,
834+
// payload.encode(),
835+
// )
836+
// } else {
837+
// log::error!(target: "thea", "Unable to generate rotate validators payload");
838+
// Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(*network));
839+
// continue;
840+
// }
841+
// },
842+
// };
843+
// <OutgoingNonce<T>>::insert(message.network, message.nonce);
844+
// <OutgoingMessages<T>>::insert(message.network, message.nonce, message);
845+
// }
846+
// <NextAuthorities<T>>::put(queued);
847+
// }
848848
if incoming_set != BTreeSet::from_iter(outgoing.to_vec()) {
849849
// This will happen when new era starts, or end of the last epoch
850-
<Authorities<T>>::insert(new_id, incoming);
851-
<ValidatorSetId<T>>::put(new_id);
852850
for network in active_networks {
853-
let message =
854-
Self::generate_payload(PayloadType::ValidatorsRotated, network, Vec::new()); //Empty data means activate the next set_id
855-
<OutgoingNonce<T>>::insert(network, message.nonce);
856-
<OutgoingMessages<T>>::insert(network, message.nonce, message);
851+
if let Some(payload) = ValidatorSet::new(incoming.clone(), new_id) {
852+
let message = Self::generate_payload(
853+
PayloadType::ValidatorsRotated,
854+
network,
855+
payload.encode(),
856+
);
857+
<OutgoingNonce<T>>::insert(network, message.nonce);
858+
<OutgoingMessages<T>>::insert(network, message.nonce, message);
859+
} else {
860+
log::error!(target: "thea", "Unable to generate rotate validators payload");
861+
Self::deposit_event(Event::<T>::UnableToGenerateValidatorSet(network));
862+
continue;
863+
}
857864
}
865+
<Authorities<T>>::insert(new_id, incoming);
866+
<ValidatorSetId<T>>::put(new_id);
858867
}
859868
}
860869

pallets/thea/src/tests.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,12 @@ fn test_session_change() {
8383
// Simulating the on_new_session to last epoch of an era.
8484
Thea::on_new_session(false, authorities.into_iter(), queued.clone().into_iter());
8585
assert!(Thea::validator_set_id() == 0);
86-
assert!(Thea::outgoing_nonce(1) == 1); // Thea validator session change message is generated here
86+
assert!(Thea::outgoing_nonce(1) == 0); // Thea validator session change message is not generated here on new change only when session actually changes
8787

88+
// Simulating the on_new_session to the first epoch of the next era.
89+
Thea::on_new_session(false, queued.clone().into_iter(), queued.clone().into_iter());
90+
assert!(Thea::validator_set_id() == 1);
91+
assert!(Thea::outgoing_nonce(1) == 1);
8892
let message = Thea::get_outgoing_messages(1, 1).unwrap();
8993
assert_eq!(message.nonce, 1);
9094
let validator_set: ValidatorSet<<Test as Config>::TheaId> =
@@ -93,14 +97,6 @@ fn test_session_change() {
9397
queued.iter().map(|(_, public)| public.clone()).collect();
9498
assert_eq!(validator_set.set_id, 1);
9599
assert_eq!(validator_set.validators, queued_validators);
96-
97-
// Simulating the on_new_session to the first epoch of the next era.
98-
Thea::on_new_session(false, queued.clone().into_iter(), queued.clone().into_iter());
99-
assert!(Thea::validator_set_id() == 1);
100-
assert!(Thea::outgoing_nonce(1) == 2);
101-
let message = Thea::get_outgoing_messages(1, 2).unwrap();
102-
assert_eq!(message.nonce, 2);
103-
assert!(message.data.is_empty());
104100
})
105101
}
106102

@@ -320,11 +316,11 @@ fn test_report_misbehaviour_happy_path() {
320316
assert_ok!(Thea::report_misbehaviour(RuntimeOrigin::signed(fisherman), network, 1));
321317
})
322318
}
323-
324319
use frame_support::{
325320
assert_noop,
326321
traits::{fungible::MutateHold, tokens::Precision},
327322
};
323+
use thea_primitives::types::NetworkType;
328324
use thea_primitives::types::{AssetMetadata, IncomingMessage, SignedMessage, THEA_HOLD_REASON};
329325

330326
#[test]

pallets/thea/src/validation.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use frame_system::{offchain::SubmitTransaction, pallet_prelude::BlockNumberFor};
2727
use parity_scale_codec::Encode;
2828
use sp_application_crypto::RuntimeAppPublic;
2929
use sp_std::vec::Vec;
30+
use thea_primitives::types::PayloadType;
3031
use thea_primitives::Network;
3132

3233
impl<T: Config> Pallet<T> {
@@ -37,8 +38,11 @@ impl<T: Config> Pallet<T> {
3738
return Ok(());
3839
}
3940

40-
let id = <ValidatorSetId<T>>::get();
41+
let mut id = <ValidatorSetId<T>>::get();
42+
let id_prev = id.saturating_sub(1);
43+
4144
let authorities = <Authorities<T>>::get(id).to_vec();
45+
let prev_authorities = <Authorities<T>>::get(id_prev).to_vec();
4246

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

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

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

64+
let local_keys = T::TheaId::all();
65+
// Fetching the available keys from previous set
66+
let mut prev_available_keys = prev_authorities
67+
.iter()
68+
.enumerate()
69+
.filter_map(move |(auth_index, authority)| {
70+
local_keys
71+
.binary_search(authority)
72+
.ok()
73+
.map(|location| (auth_index, local_keys[location].clone()))
74+
})
75+
.collect::<Vec<(usize, T::TheaId)>>();
76+
prev_available_keys.sort();
77+
6078
let active_networks = <ActiveNetworks<T>>::get();
6179
log::info!(target:"thea","List of active networks: {:?}",active_networks);
6280

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

85-
let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
86-
// Note: this is a double hash signing
87-
let signature =
88-
sp_io::crypto::ecdsa_sign_prehashed(THEA, &signer.clone().into(), &msg_hash)
103+
match message.payload_type {
104+
PayloadType::ScheduledRotateValidators => {
105+
log::warn!(target: "thea", "Ignoring ScheduledRotateValidators message for thea");
106+
},
107+
PayloadType::ValidatorsRotated => {
108+
// if its validator rotated, then only the previous set should sign it.
109+
let (prev_auth_index, prev_signer) = prev_available_keys.first().ok_or(
110+
"No active keys available from previous set to sign rotation message",
111+
)?;
112+
log::info!(target: "thea", "Previous Auth Index {:?} previous signer {:?}", prev_auth_index, prev_signer.clone());
113+
114+
let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
115+
// Note: this is a double hash signing
116+
let signature = sp_io::crypto::ecdsa_sign_prehashed(
117+
THEA,
118+
&prev_signer.clone().into(),
119+
&msg_hash,
120+
)
89121
.ok_or("Expected signature to be returned")?;
90-
signed_messages.push((network, next_outgoing_nonce, signature.into()));
122+
signed_messages.push((network, next_outgoing_nonce, signature.into()));
123+
id = id_prev; // We need to set the id to prev for unsigned validation to pass
124+
auth_index = *prev_auth_index; // We need to set the id to prev for unsigned validation to pass
125+
},
126+
PayloadType::L1Deposit => {
127+
let msg_hash = sp_io::hashing::sha2_256(message.encode().as_slice());
128+
// Note: this is a double hash signing
129+
let signature = sp_io::crypto::ecdsa_sign_prehashed(
130+
THEA,
131+
&signer.clone().into(),
132+
&msg_hash,
133+
)
134+
.ok_or("Expected signature to be returned")?;
135+
signed_messages.push((network, next_outgoing_nonce, signature.into()));
136+
},
137+
}
91138
}
92139

93140
if !signed_messages.is_empty() {
94141
// we batch these signatures into a single extrinsic and submit on-chain
95142
if let Err(()) = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(
96143
Call::<T>::submit_signed_outgoing_messages {
97-
auth_index: *auth_index as u32,
144+
auth_index: auth_index as u32,
98145
id,
99146
signatures: signed_messages,
100147
}

primitives/thea/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ pub struct IncomingMessage<AccountId, Balance> {
181181
Clone, Encode, Decode, TypeInfo, Debug, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize,
182182
)]
183183
pub enum PayloadType {
184-
ScheduledRotateValidators,
184+
ScheduledRotateValidators, // Deprecated
185185
ValidatorsRotated,
186186
L1Deposit,
187187
}

0 commit comments

Comments
 (0)