5511 lines
201 KiB
Rust
5511 lines
201 KiB
Rust
// Ensure we're `no_std` when compiling for Wasm.
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use frame_support::{
|
|
pallet_prelude::*,
|
|
traits::{
|
|
tokens::{Preservation, Precision, Fortitude}, fungible::{Inspect, Mutate},
|
|
DisabledValidators, ValidatorSet, ValidatorSetWithIdentification,
|
|
OneSessionHandler,
|
|
},
|
|
};
|
|
use frame_system::{
|
|
offchain::{SubmitTransaction, SendTransactionTypes},
|
|
pallet_prelude::*,
|
|
};
|
|
|
|
use sp_runtime::{
|
|
offchain::{
|
|
self as rt_offchain,
|
|
storage::StorageValueRef,
|
|
storage_lock::{StorageLock, Time},
|
|
},
|
|
traits::UniqueSaturatedInto,
|
|
Saturating, Perbill,
|
|
};
|
|
|
|
use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap};
|
|
use sp_application_crypto::RuntimeAppPublic;
|
|
use sp_runtime::traits::BlockNumberProvider;
|
|
use sp_io::hashing::blake2_256;
|
|
|
|
use ghost_helpers::{
|
|
get_byzantium_quorum_threshold, SubstrateBlake2Hasher,
|
|
lcg::PerfectSlotMapperU128,
|
|
hash_chain::cumulative_hash,
|
|
networks::{NetworkData, NetworkCurve, NetworkType},
|
|
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
|
|
};
|
|
|
|
#[cfg(test)]
|
|
use ghost_traits::exodus::IdentifierConverter;
|
|
|
|
use ghost_traits::{
|
|
bounded_bitmap::{
|
|
BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader,
|
|
BoundedBitmapIterable,
|
|
},
|
|
exodus::{
|
|
MerkleTreeBuilder, DistributedKeyGeneration, EllipticCurveDiffieHellman,
|
|
FlexibleRoundOptimizedSchnorrThresholdSignature, EvmGovernanceRegistrar,
|
|
},
|
|
networks::{
|
|
NetworkDataBasicHandler, NetworkDataInspectHandler,
|
|
NetworkDataMutateHandler,
|
|
},
|
|
};
|
|
|
|
use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
|
|
|
|
#[cfg(feature = "runtime-benchmarks")]
|
|
mod benchmarking;
|
|
|
|
#[cfg(any(test))]
|
|
mod mock;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
#[cfg_attr(feature = "runtime-benchmarks", macro_use)]
|
|
mod impls;
|
|
mod error;
|
|
mod types;
|
|
pub mod weights;
|
|
|
|
pub use types::*;
|
|
pub use error::ExodusError;
|
|
pub use weights::WeightInfo;
|
|
|
|
pub mod sr25519 {
|
|
mod app_sr25519 {
|
|
use sp_application_crypto::{app_crypto, sr25519, KeyTypeId};
|
|
const EXODUS: KeyTypeId = KeyTypeId(*b"exds");
|
|
app_crypto!(sr25519, EXODUS);
|
|
}
|
|
|
|
sp_application_crypto::with_pair! {
|
|
pub type AuthorityPair = app_sr25519::Pair;
|
|
}
|
|
|
|
pub type AuthoritySignature = app_sr25519::Signature;
|
|
pub type AuthorityId = app_sr25519::Public;
|
|
}
|
|
|
|
const MIN_LOCK_GUARD_PERIOD: u64 = 15_000;
|
|
const BLOCKS_FOR_DKG_RESET: u64 = 5;
|
|
|
|
const DB_STORAGE_PREFIX : &[u8] = b"exodus::";
|
|
const PALLET_LOG_TARGET : &str = "time:ghost-exodus";
|
|
const OFFCHAIN_INDEX_KEY : &[u8] = b"dkg-round2-encrypted";
|
|
|
|
const DKG_ROUND0_PREFIX: &[u8] = b"dkg-round0";
|
|
const DKG_ROUND1_PREFIX: &[u8] = b"dkg-round1";
|
|
const DKG_ROUND2_PREFIX: &[u8] = b"dkg-round2";
|
|
const DKG_ROUND3_PREFIX: &[u8] = b"dkg-round3";
|
|
const DKG_ROUND4_PREFIX: &[u8] = b"dkg-round4";
|
|
const DKG_SECRET_PREFIX: &[u8] = b"dkg-secret";
|
|
|
|
const EXODUS_PREFIX_NONCES: &[u8] = b"exodus-nonces";
|
|
const EXODUS_PREFIX_SHARES: &[u8] = b"exodus-shares";
|
|
const EXODUS_PREFIX_BLENDS: &[u8] = b"exodus-blends";
|
|
const EXODUS_PREFIX_EXILES: &[u8] = b"exodus-exiles";
|
|
|
|
const MAX_MESSAGE_SIZE : u32 = 544;
|
|
const HEADER_MAX_BYTES : u32 = 1 + 4;
|
|
const ELEMENT_MAX_BYTES : u32 = 33;
|
|
const SCALAR_MAX_BYTES : u32 = 32;
|
|
|
|
const ENCRYPTION_NONCE_MAX_BYTES : u32 = 12;
|
|
const AUTHENTICATION_TAG_MAX_BYTES : u32 = 16;
|
|
|
|
const ROUND_NUMBER_0: u8 = 0u8;
|
|
const ROUND_NUMBER_1: u8 = 1u8;
|
|
const ROUND_NUMBER_2: u8 = 2u8;
|
|
const ROUND_NUMBER_3: u8 = 3u8;
|
|
const ROUND_NUMBER_4: u8 = 4u8;
|
|
const ROUND_NUMBER_5: u8 = 5u8;
|
|
const ROUND_NUMBER_6: u8 = 6u8;
|
|
|
|
const fn postcard_varint_prefix_len(len: usize) -> usize {
|
|
if len <= 127 { 1 }
|
|
else if len <= 16383 { 2 }
|
|
else { 3 }
|
|
}
|
|
|
|
const fn scale_compact_prefix_len(len: usize) -> usize {
|
|
if len <= 63 { 1 }
|
|
else if len <= 16383 { 2 }
|
|
else if len <= 1073741823 { 4 }
|
|
else { 8 }
|
|
}
|
|
|
|
const fn round1_package_size(
|
|
commitments_count: usize,
|
|
element_bytes_len: usize,
|
|
signature_bytes_len: usize,
|
|
header_bytes_len: usize
|
|
) -> usize {
|
|
let commitments_bytes_len = commitments_count.saturating_mul(element_bytes_len);
|
|
let commitment_vec_prefix = postcard_varint_prefix_len(commitments_count);
|
|
|
|
postcard_varint_prefix_len(signature_bytes_len)
|
|
.saturating_add(signature_bytes_len)
|
|
.saturating_add(header_bytes_len)
|
|
.saturating_add(commitment_vec_prefix)
|
|
.saturating_add(commitments_bytes_len)
|
|
}
|
|
|
|
const fn encrypted_ciphertext_bytes_len(
|
|
scalar_bytes_len: usize,
|
|
header_bytes_len: usize,
|
|
) -> usize {
|
|
let ciphertext_len = header_bytes_len.saturating_add(scalar_bytes_len);
|
|
let ciphertext_vec_prefix = postcard_varint_prefix_len(ciphertext_len);
|
|
|
|
ciphertext_len.saturating_add(ciphertext_vec_prefix)
|
|
.saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize)
|
|
}
|
|
|
|
const fn round2_encrypted_package_size(
|
|
participants: usize,
|
|
scalar_bytes_len: usize,
|
|
header_bytes_len: usize,
|
|
) -> usize {
|
|
let encrypted_ciphertext_bytes_len =
|
|
encrypted_ciphertext_bytes_len(
|
|
scalar_bytes_len,
|
|
header_bytes_len,
|
|
);
|
|
|
|
let ciphertext_len = encrypted_ciphertext_bytes_len
|
|
.saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize);
|
|
|
|
let ciphertext_vec_prefix = scale_compact_prefix_len(ciphertext_len);
|
|
let size = ciphertext_len.saturating_add(ciphertext_vec_prefix);
|
|
|
|
participants.saturating_mul(size)
|
|
}
|
|
|
|
const fn round2_package_size(
|
|
participants: usize,
|
|
scalar_bytes_len: usize,
|
|
header_bytes_len: usize,
|
|
) -> usize {
|
|
let signing_share_len = header_bytes_len.saturating_add(scalar_bytes_len);
|
|
let signing_share_vec_prefix = postcard_varint_prefix_len(signing_share_len);
|
|
|
|
let size = signing_share_vec_prefix.saturating_add(signing_share_len);
|
|
participants.saturating_mul(size)
|
|
}
|
|
|
|
const fn nonce_commitment_package_size(
|
|
element_max_bytes: usize,
|
|
header_max_bytes: usize,
|
|
) -> usize {
|
|
header_max_bytes
|
|
.saturating_add(element_max_bytes)
|
|
.saturating_add(element_max_bytes)
|
|
}
|
|
|
|
const fn signature_share_bytes_len(
|
|
scalar_bytes_len: usize,
|
|
header_bytes_len: usize,
|
|
) -> usize {
|
|
header_bytes_len.saturating_add(scalar_bytes_len)
|
|
}
|
|
|
|
type DkgIndex = ghost_helpers::DkgIndexU32;
|
|
type AuthIndex = ghost_helpers::AuthIndexU16;
|
|
type BitmapChunk = ghost_helpers::BitmapChunkU32;
|
|
|
|
type RoastSession = u16;
|
|
type ExodusSession = u64;
|
|
|
|
type EvmAddress = sp_core::H160;
|
|
type ExodusHash = sp_core::H256;
|
|
type EvmBytes32 = sp_core::H256;
|
|
|
|
type ParticipantsBitmap<T> = BoundedBitmap<BitmapChunk, <T as Config>::MaxAuthoritiesChunks>;
|
|
type BitmapByAuthority<T> = BoundedBTreeMap<AuthIndex, ParticipantsBitmap<T>, <T as Config>::MaxAuthorities>;
|
|
|
|
pub type EvmSessionsBundle<T> = BoundedVec::<u8, MaxEvmRotationSessionsSize<T>>;
|
|
pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
|
|
pub type NetworkIdOf<T> = <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;
|
|
|
|
pub type ExodusSessionsMap<T> = BoundedBTreeMap<
|
|
NetworkIdOf<T>,
|
|
ExodusSession,
|
|
<<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks,
|
|
>;
|
|
|
|
pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
|
|
<T as frame_system::Config>::AccountId,
|
|
>>::ValidatorId;
|
|
|
|
pub type IdentificationTuple<T> = (
|
|
ValidatorId<T>,
|
|
<<T as Config>::ValidatorSet as ValidatorSetWithIdentification<
|
|
<T as frame_system::Config>::AccountId,
|
|
>>::Identification,
|
|
);
|
|
|
|
type ExodusResult<T> = Result<T, ExodusError>;
|
|
|
|
pub struct MaxEvmRotationSessionsSize<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for MaxEvmRotationSessionsSize<T> {
|
|
fn get() -> u32 {
|
|
let exodus_session_size = core::mem::size_of::<ExodusSession>() as u32;
|
|
let max_networks = <T::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks::get();
|
|
|
|
exodus_session_size.saturating_mul(max_networks)
|
|
}
|
|
}
|
|
|
|
pub struct MaxAuthoritiesBitmaskSize<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for MaxAuthoritiesBitmaskSize<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
max_authorities.div_ceil(8)
|
|
}
|
|
}
|
|
|
|
pub struct Round1MaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for Round1MaxBytes<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
let min_authorities = get_byzantium_quorum_threshold(max_authorities);
|
|
let max_signature_bytes = ELEMENT_MAX_BYTES + SCALAR_MAX_BYTES;
|
|
|
|
round1_package_size(
|
|
min_authorities as usize,
|
|
ELEMENT_MAX_BYTES as usize,
|
|
max_signature_bytes as usize,
|
|
HEADER_MAX_BYTES as usize,
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct Round2BlobMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for Round2BlobMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
|
|
round2_package_size(
|
|
max_authorities as usize,
|
|
SCALAR_MAX_BYTES as usize,
|
|
HEADER_MAX_BYTES as usize
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct EncryptedRound2BlobMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for EncryptedRound2BlobMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
|
|
round2_encrypted_package_size(
|
|
max_authorities as usize,
|
|
SCALAR_MAX_BYTES as usize,
|
|
HEADER_MAX_BYTES as usize
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct AccusedIndicesMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for AccusedIndicesMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
max_authorities.saturating_add(7).div_ceil(8)
|
|
}
|
|
}
|
|
|
|
pub struct MerkleProofMaxSize<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for MerkleProofMaxSize<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
|
|
max_authorities
|
|
.next_power_of_two()
|
|
.trailing_zeros()
|
|
.saturating_mul(ExodusHash::len_bytes() as u32)
|
|
}
|
|
}
|
|
|
|
pub struct BindingFactorsProof<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for BindingFactorsProof<T> {
|
|
fn get() -> u32 {
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
get_byzantium_quorum_threshold(max_authorities)
|
|
.next_power_of_two()
|
|
.trailing_zeros()
|
|
}
|
|
}
|
|
|
|
pub struct NonceCommitmentMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for NonceCommitmentMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
nonce_commitment_package_size(
|
|
ELEMENT_MAX_BYTES as usize,
|
|
HEADER_MAX_BYTES as usize,
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct CiphertextMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for CiphertextMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
encrypted_ciphertext_bytes_len(
|
|
SCALAR_MAX_BYTES as usize,
|
|
HEADER_MAX_BYTES as usize,
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct SignatureShareMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for SignatureShareMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
signature_share_bytes_len(
|
|
SCALAR_MAX_BYTES as usize,
|
|
HEADER_MAX_BYTES as usize,
|
|
) as u32
|
|
}
|
|
}
|
|
|
|
pub struct SignatureMaxBytes<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<u32> for SignatureMaxBytes<T> {
|
|
fn get() -> u32 {
|
|
let signature_max_bytes = ELEMENT_MAX_BYTES + SCALAR_MAX_BYTES;
|
|
postcard_varint_prefix_len(signature_max_bytes as usize) as u32
|
|
}
|
|
}
|
|
|
|
pub struct ZeroScalarDefault<T>(sp_std::marker::PhantomData<T>);
|
|
impl<T: Config> Get<BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>> for ZeroScalarDefault<T> {
|
|
fn get() -> BoundedVec<u8, sp_core::ConstU32<{ SCALAR_MAX_BYTES }>> {
|
|
let zero_vec = vec![0u8; SCALAR_MAX_BYTES as usize];
|
|
BoundedVec::try_from(zero_vec).unwrap_or_default()
|
|
}
|
|
}
|
|
|
|
pub use pallet::*;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
|
|
const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::storage_version(STORAGE_VERSION)]
|
|
#[pallet::without_storage_info]
|
|
pub struct Pallet<T>(_);
|
|
|
|
#[pallet::config]
|
|
pub trait Config: SendTransactionTypes<Call<Self>> + frame_system::Config + core::fmt::Debug {
|
|
type RuntimeEvent: From<Event<Self>>
|
|
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
|
|
|
type AuthorityId: Member
|
|
+ Parameter
|
|
+ RuntimeAppPublic
|
|
+ Ord
|
|
+ MaybeSerializeDeserialize
|
|
+ MaxEncodedLen;
|
|
|
|
type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
|
|
type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
|
|
|
|
type NetworkDataHandler: NetworkDataInspectHandler<NetworkData>
|
|
+ NetworkDataMutateHandler<NetworkData, BalanceOf<Self>>
|
|
+ NetworkDataBasicHandler<NetworkCurve = NetworkCurve, NetworkType = NetworkType>;
|
|
|
|
type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;
|
|
|
|
type DisabledValidators: DisabledValidators;
|
|
|
|
#[pallet::constant]
|
|
type UnsignedPriority: Get<TransactionPriority>;
|
|
|
|
#[pallet::constant]
|
|
type UnsignedLongevity: Get<u64>;
|
|
|
|
#[pallet::constant]
|
|
type MaxAuthorities: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type MaxAuthoritiesChunks: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type DkgRoundPeriod: Get<u64>;
|
|
|
|
#[pallet::constant]
|
|
type RemovalLimit: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type MaxCursorLen: Get<u32>;
|
|
|
|
#[pallet::constant]
|
|
type BaseEvmAmount: Get<u128>;
|
|
|
|
#[pallet::constant]
|
|
type MaxExodusRequestCount: Get<u32>;
|
|
|
|
type WeightInfo: WeightInfo;
|
|
}
|
|
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
Round0PackageRegistered {
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
hash: ExodusHash,
|
|
},
|
|
Round1PackageRegistered {
|
|
authority_index: AuthIndex,
|
|
coefficients_count: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
},
|
|
Round2PackagesRegistered {
|
|
authority_index: AuthIndex,
|
|
encrypted_count: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
},
|
|
ComplaintsRegistered {
|
|
complaints_count: AuthIndex,
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
},
|
|
Round4JustificationPackage {
|
|
justifications_count: AuthIndex,
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
},
|
|
Round5PublicPackageRegistered {
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
verifying_hash: ExodusHash,
|
|
},
|
|
Round6VerifyingShareRegistered {
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
},
|
|
NonceCommitmentRegistered {
|
|
authority_index: AuthIndex,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
network_curve: NetworkCurve,
|
|
roast_session: RoastSession,
|
|
},
|
|
GroupCommitmentRegistered {
|
|
authority_index: AuthIndex,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
network_curve: NetworkCurve,
|
|
roast_session: RoastSession,
|
|
},
|
|
PartialSignatureRegistered {
|
|
authority_index: AuthIndex,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
network_curve: NetworkCurve,
|
|
roast_session: RoastSession,
|
|
},
|
|
EvmBridgeOutRegistered {
|
|
who: T::AccountId,
|
|
network_id: NetworkIdOf<T>,
|
|
amount: BalanceOf<T>,
|
|
bounty: Perbill,
|
|
receiver: EvmAddress,
|
|
}
|
|
}
|
|
|
|
#[pallet::error]
|
|
pub enum Error<T> {
|
|
DkgWrongRound,
|
|
TooManyEntries,
|
|
BridgeOverflow,
|
|
TooManyPackages,
|
|
WrongNetworkType,
|
|
MemberNotQualified,
|
|
InvalidMerkleProof,
|
|
NetworkDoesNotExist,
|
|
VerifyingShareNotFound,
|
|
BridgeQueueMinimumNotMet,
|
|
DkgAuthoritiesInProgress,
|
|
DkgAuthoritiesNotInitialized,
|
|
DkgQualificationNotInitialized,
|
|
|
|
Round0PackageInvalidProof,
|
|
Round0PackageAlreadyRegistered,
|
|
Round1PackageBadHash,
|
|
Round1PackageInvalidProof,
|
|
Round1PackageAlreadyRegistered,
|
|
Round1PackageInvalidLength,
|
|
Round2PackagesInvalidProof,
|
|
Round2PackagesAlreadyRegistered,
|
|
Round2PackagesWrongCoefficients,
|
|
Round3PackageInvalidProof,
|
|
Round3PackagesAlreadyRegistered,
|
|
Round4PackageInvalidProof,
|
|
Round4PackagesAlreadyRegistered,
|
|
Round5PackageInvalidProof,
|
|
Round5PackagesAlreadyRegistered,
|
|
Round6PackageInvalidProof,
|
|
Round6PackagesAlreadyRegistered,
|
|
|
|
ExodusMessageTooBig,
|
|
ExodusRequestNotFound,
|
|
ExodusAccumulationFailed,
|
|
ExodusIncorrectRoastStatus,
|
|
ExodusInvalidSignatureShare,
|
|
ExodusAlreadyInRoastSession,
|
|
ExodusSignedMessageAlreadyExists,
|
|
|
|
ExodusNonceNotRegistered,
|
|
ExodusNonceAlreadyRegistered,
|
|
ExodusGroupAlreadyRegistered,
|
|
ExodusCommitmentNotRegistered,
|
|
ExodusSignedMessageAlreadyRegistered,
|
|
|
|
ExodusNoncePackageInvalidProof,
|
|
ExodusGroupPackageInvalidProof,
|
|
ExodusSharePackageInvalidProof,
|
|
}
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn current_exodus_sessions)]
|
|
pub(super) type CurrentExodusSessions<T: Config> =
|
|
StorageValue<_, ExodusSessionsMap<T>, ValueQuery>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn exodus_requests)]
|
|
pub(super) type ExodusRequests<T: Config> = CountedStorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Twox64Concat, NetworkCurve>,
|
|
NMapKey<Twox64Concat, ExodusContext<NetworkIdOf<T>>>,
|
|
),
|
|
ExodusRequest<T>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn roast_states)]
|
|
pub(super) type RoastSessionStates<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
Twox64Concat, RoastSession,
|
|
RoastSessionState<ParticipantsBitmap<T>>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn nonce_commitments)]
|
|
pub(super) type NonceCommitments<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, (ExodusContext<NetworkIdOf<T>>, RoastSession),
|
|
Twox64Concat, AuthIndex,
|
|
ExodusSeparatedNonce,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn group_committers)]
|
|
pub(super) type GroupCommitters<T: Config> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Twox64Concat, ExodusContext<NetworkIdOf<T>>>,
|
|
NMapKey<Twox64Concat, RoastSession>,
|
|
NMapKey<Identity, ExodusHash>,
|
|
NMapKey<Blake2_256, BoundedVec<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>>,
|
|
),
|
|
ParticipantsBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn group_commitments_consensus)]
|
|
pub(super) type GroupCommitmentsConsensus<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
Twox64Concat, RoastSession,
|
|
ConsensusState<ParticipantsBitmap<T>>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn signature_scalars)]
|
|
pub(super) type SignatureScalars<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
Twox64Concat, RoastSession,
|
|
BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>,
|
|
ValueQuery,
|
|
ZeroScalarDefault<T>,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn roast_sessions)]
|
|
pub(super) type RoastSessions<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
Twox64Concat, AuthIndex,
|
|
RoastSession,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn exodus_signatured_rotations)]
|
|
pub(super) type ExodusSignedRotations<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, DkgIndex,
|
|
Twox64Concat, NetworkType,
|
|
(ExodusContext<NetworkIdOf<T>>, ExodusSignedMessage<T>),
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn exodus_signed_bridges)]
|
|
pub(super) type ExodusSignedBridges<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, DkgIndex,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
ExodusSignedMessage<T>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn exodus_signed_governance)]
|
|
pub(super) type ExodusSignedGovernance<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, DkgIndex,
|
|
Twox64Concat, ExodusContext<NetworkIdOf<T>>,
|
|
ExodusSignedMessage<T>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn dkg_maybe_remove_cursor)]
|
|
pub(super) type DkgMaybeCursor<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
BoundedVec<u8, T::MaxCursorLen>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn round0_packages)]
|
|
pub(super) type Round0Packages<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
Twox64Concat, AuthIndex,
|
|
ExodusHash,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn round1_packages)]
|
|
pub(super) type Round1Packages<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
ParticipantsBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn encrypted_round2_packages)]
|
|
pub(super) type Round2Packages<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
ParticipantsBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn complaints)]
|
|
pub(super) type Complaints<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
BitmapByAuthority<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn justifications)]
|
|
pub(super) type Justifications<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
BitmapByAuthority<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn verifications)]
|
|
pub(super) type Verifications<T: Config> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Twox64Concat, NetworkCurve>,
|
|
NMapKey<Twox64Concat, DkgIndex>,
|
|
NMapKey<Twox64Concat, ExodusHash>,
|
|
),
|
|
ParticipantsBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn verifying_key_consensus)]
|
|
pub(super) type VerifyingKeyConsensus<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
Twox64Concat, DkgIndex,
|
|
QualifiedConsensusState<
|
|
ParticipantsBitmap<T>,
|
|
BoundedVec<u8, MaxAuthoritiesBitmaskSize<T>>
|
|
>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn verifying_shares)]
|
|
pub(super) type VerifyingShares<T: Config> = StorageNMap<
|
|
_,
|
|
(
|
|
NMapKey<Twox64Concat, NetworkCurve>,
|
|
NMapKey<Twox64Concat, DkgIndex>,
|
|
NMapKey<Twox64Concat, AuthIndex>,
|
|
),
|
|
BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
|
OptionQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn verifying_shares_participants)]
|
|
pub(super) type VerifyingSharesParticipants<T: Config> = StorageDoubleMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
Twox64Concat, DkgIndex,
|
|
ParticipantsBitmap<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn active_verifying_key)]
|
|
pub(super) type ActiveVerifyingKey<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn active_authorities)]
|
|
pub(super) type ActiveAuthorities<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
WeakBoundedVec<<T as Config>::AuthorityId, <T as Config>::MaxAuthorities>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn active_dkg_authority)]
|
|
pub(super) type ActiveDkgAuthorities<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
ReadyDkgAuthorities<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn qualification_authorities)]
|
|
pub(super) type QualificationAuthorities<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
WeakBoundedVec<<T as Config>::AuthorityId, <T as Config>::MaxAuthorities>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn qualification_dkg_state)]
|
|
pub(super) type QualificationDkgState<T: Config> = StorageMap<
|
|
_,
|
|
Twox64Concat, NetworkCurve,
|
|
PendingDkgAuthorities<T>,
|
|
ValueQuery,
|
|
>;
|
|
|
|
#[pallet::genesis_config]
|
|
#[derive(frame_support::DefaultNoBound)]
|
|
pub struct GenesisConfig<T: Config> {
|
|
pub authorities: Vec<T::AuthorityId>,
|
|
}
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
|
fn build(&self) {
|
|
use strum::IntoEnumIterator;
|
|
|
|
// NOTE: would be nice to use same logic during forkless
|
|
// upgrade in order to prevent any issues down the road.
|
|
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let converted_round_period: BlockNumberFor<T> =
|
|
T::DkgRoundPeriod::get().unique_saturated_into();
|
|
|
|
assert!(
|
|
block_longevity < converted_round_period,
|
|
"CRITICAL CONFIG ERROR: T::DKGRoundPeriod could not be less than expected block inclusion!",
|
|
);
|
|
|
|
let max_authorities = T::MaxAuthorities::get();
|
|
validate_bitmap_sizes::<ParticipantsBitmap<T>>(max_authorities);
|
|
|
|
for network_curve in NetworkCurve::iter() {
|
|
assert!(
|
|
network_curve.element_bytes_len() <= ELEMENT_MAX_BYTES as usize,
|
|
"Curve {:?} requires {} bytes for elements but ELEMENT_MAX_BYTES is {}.",
|
|
network_curve,
|
|
network_curve.element_bytes_len(),
|
|
ELEMENT_MAX_BYTES,
|
|
);
|
|
|
|
assert!(
|
|
network_curve.scalar_bytes_len() <= SCALAR_MAX_BYTES as usize,
|
|
"Curve {:?} requires {} bytes for scalar but SCALAR_MAX_BYTES is {}.",
|
|
network_curve,
|
|
network_curve.scalar_bytes_len(),
|
|
SCALAR_MAX_BYTES,
|
|
);
|
|
|
|
for (network_type, network_ids) in T::NetworkDataHandler::iter_types_by_curve(&network_curve) {
|
|
let rotation_message_size = match network_type {
|
|
NetworkType::Evm => {
|
|
let dummy_evm_xor_key = u128::MAX;
|
|
let dummy_evm_bitmask = u128::MIN;
|
|
let dummy_public_address = EvmBytes32::repeat_byte(69u8);
|
|
let mut dummy_packed_sessions =
|
|
EvmSessionsBundle::<T>::with_max_capacity();
|
|
|
|
let max_size = EvmSessionsBundle::<T>::bound();
|
|
dummy_packed_sessions.bounded_resize(max_size, 69u8);
|
|
|
|
let network_id = network_ids
|
|
.as_ref()
|
|
.first()
|
|
.copied()
|
|
.expect("At this point we should have at least one `NetworkId` registered; qed");
|
|
|
|
let dummy_exodus_request =
|
|
ExodusRequest::<T>::evm_rotation(
|
|
dummy_public_address,
|
|
dummy_evm_xor_key,
|
|
dummy_evm_bitmask,
|
|
dummy_packed_sessions,
|
|
);
|
|
|
|
let dummy_exodus_context =
|
|
ExodusContext::<NetworkIdOf<T>>::new(network_id, 0);
|
|
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
dummy_exodus_request
|
|
.get_message(&mut message_buffer, dummy_exodus_context)
|
|
.map(|message| message.len() as u32)
|
|
.unwrap_or(u32::MAX)
|
|
},
|
|
_ => MAX_MESSAGE_SIZE,
|
|
};
|
|
|
|
assert!(
|
|
rotation_message_size <= MAX_MESSAGE_SIZE,
|
|
"Curve {:?} requires {} bytes for rotation message but MAX_MESSAGE_SIZE is {}",
|
|
network_curve,
|
|
rotation_message_size,
|
|
MAX_MESSAGE_SIZE,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {
|
|
#[pallet::call_index(0)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round0_package(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round0_package(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round0_package_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round0PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round0(round0) = dkg_package else {
|
|
return Err(Error::<T>::Round0PackageInvalidProof.into());
|
|
};
|
|
|
|
QualificationDkgState::<T>::try_mutate(&network_curve,
|
|
|qualification_state| -> DispatchResult {
|
|
ensure!(
|
|
qualification_state.is_zero_phase(),
|
|
Error::<T>::DkgWrongRound,
|
|
);
|
|
|
|
ensure!(
|
|
!qualification_state.contains_index(authority_index),
|
|
Error::<T>::Round0PackageAlreadyRegistered,
|
|
);
|
|
|
|
qualification_state.insert_index(authority_index);
|
|
Ok(())
|
|
})?;
|
|
|
|
Round0Packages::<T>::insert(
|
|
&network_curve,
|
|
authority_index,
|
|
round0.package_hash,
|
|
);
|
|
|
|
Self::deposit_event(Event::<T>::Round0PackageRegistered {
|
|
authority_index,
|
|
network_curve,
|
|
hash: round0.package_hash,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(1)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round1_package(
|
|
dkg_package.bytes_len(),
|
|
),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round1_package(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round1_package_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round1PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round1(round1) = dkg_package else {
|
|
return Err(Error::<T>::Round1PackageInvalidProof.into());
|
|
};
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_first_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
let hash1 = Round0Packages::<T>::get(&network_curve, authority_index);
|
|
let hash2 = ExodusHash::from(blake2_256(&round1.package));
|
|
ensure!(!hash1.is_zero() && hash1 == hash2, Error::<T>::Round1PackageBadHash);
|
|
|
|
let needed_commitment_count = QualificationAuthorities::<T>::decode_len(&network_curve)
|
|
.map(|max_signers| {
|
|
get_byzantium_quorum_threshold(max_signers as AuthIndex)
|
|
})
|
|
.ok_or(Error::<T>::DkgQualificationNotInitialized)?;
|
|
|
|
let estimated_commitment_count =
|
|
network_curve.dkg_verify_proof_of_knowledge(
|
|
authority_index,
|
|
&round1.package,
|
|
).map_err(|_| Error::<T>::Round1PackageInvalidProof)?;
|
|
|
|
ensure!(
|
|
estimated_commitment_count == needed_commitment_count,
|
|
Error::<T>::Round1PackageInvalidLength,
|
|
);
|
|
|
|
Round1Packages::<T>::try_mutate(
|
|
&network_curve,
|
|
|round1_packages| -> DispatchResult {
|
|
ensure!(
|
|
!round1_packages.contains(authority_index),
|
|
Error::<T>::Round1PackageAlreadyRegistered,
|
|
);
|
|
round1_packages.insert(authority_index);
|
|
Ok(())
|
|
})?;
|
|
|
|
Self::deposit_event(Event::<T>::Round1PackageRegistered {
|
|
authority_index,
|
|
network_curve,
|
|
coefficients_count: estimated_commitment_count,
|
|
});
|
|
|
|
let index_key = Self::create_offchain_dkg_key(
|
|
network_curve,
|
|
authority_index,
|
|
ROUND_NUMBER_1,
|
|
);
|
|
|
|
sp_io::offchain_index::set(
|
|
&index_key,
|
|
&round1.package.encode(),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(2)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_encrypted_round2_packages(
|
|
dkg_package.bytes_len(),
|
|
),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_encrypted_round2_packages(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round2_package_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round2PackagesInvalidProof)?;
|
|
|
|
let DkgPackage::Round2(round2_bundle) = dkg_package else {
|
|
return Err(Error::<T>::Round2PackagesInvalidProof.into());
|
|
};
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_second_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
let encrypted_indexes = ParticipantsBitmap::<T>::from_bitmask(
|
|
&round2_bundle.bundle.bitmask,
|
|
);
|
|
|
|
let diff = state.get_indexes() ^ &encrypted_indexes;
|
|
let only_one_lost = diff.count_ones::<AuthIndex>() == 1;
|
|
let only_authority_index = diff.contains(authority_index);
|
|
|
|
ensure!(
|
|
only_one_lost && only_authority_index,
|
|
Error::<T>::Round2PackagesWrongCoefficients,
|
|
);
|
|
|
|
Round2Packages::<T>::try_mutate(&network_curve,
|
|
|round2_packages| -> DispatchResult {
|
|
ensure!(
|
|
!round2_packages.contains(authority_index),
|
|
Error::<T>::Round2PackagesAlreadyRegistered,
|
|
);
|
|
|
|
round2_packages.insert(authority_index);
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Self::deposit_event(Event::<T>::Round2PackagesRegistered {
|
|
encrypted_count: encrypted_indexes.count_ones::<AuthIndex>(),
|
|
authority_index,
|
|
network_curve,
|
|
});
|
|
|
|
let index_key = Self::create_offchain_dkg_key(
|
|
network_curve,
|
|
authority_index,
|
|
ROUND_NUMBER_2,
|
|
);
|
|
|
|
sp_io::offchain_index::set(
|
|
&index_key,
|
|
&round2_bundle.bundle.encode(),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(3)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round3_complaints(
|
|
dkg_package.bytes_len(),
|
|
),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round3_complaints(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round3_complaints_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round3PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round3(round3_complaints) = dkg_package else {
|
|
return Err(Error::<T>::Round3PackageInvalidProof.into());
|
|
};
|
|
|
|
let complaints_bitmap = ParticipantsBitmap::<T>::from_bitmask(
|
|
&round3_complaints.indices
|
|
);
|
|
|
|
let complaints_count: AuthIndex = complaints_bitmap.count_ones();
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_third_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
Complaints::<T>::try_mutate(
|
|
&network_curve,
|
|
|complaints| -> DispatchResult {
|
|
ensure!(
|
|
!complaints.contains_key(&authority_index),
|
|
Error::<T>::Round3PackagesAlreadyRegistered,
|
|
);
|
|
|
|
complaints.try_insert(authority_index, complaints_bitmap)
|
|
.map_err(|_| Error::<T>::TooManyPackages)?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Self::deposit_event(Event::<T>::ComplaintsRegistered {
|
|
authority_index,
|
|
complaints_count,
|
|
network_curve,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(4)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round4_justifications(
|
|
dkg_package.bytes_len(),
|
|
),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round4_justifications(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round4_justifications_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round4PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round4(round4_bundle) = dkg_package else {
|
|
return Err(Error::<T>::Round4PackageInvalidProof.into());
|
|
};
|
|
|
|
let justifications_bitmap = ParticipantsBitmap::<T>::from_bitmask(
|
|
&round4_bundle.bundle.bitmask
|
|
);
|
|
|
|
let justifications_count: AuthIndex = justifications_bitmap.count_ones();
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_fourth_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
Justifications::<T>::try_mutate(
|
|
&network_curve,
|
|
|justifications| -> DispatchResult {
|
|
ensure!(
|
|
!justifications.contains_key(&authority_index),
|
|
Error::<T>::Round4PackagesAlreadyRegistered,
|
|
);
|
|
|
|
justifications.try_insert(authority_index, justifications_bitmap)
|
|
.map_err(|_| Error::<T>::TooManyPackages)?;
|
|
|
|
Ok(())
|
|
})?;
|
|
|
|
Self::deposit_event(Event::<T>::Round4JustificationPackage {
|
|
justifications_count,
|
|
authority_index,
|
|
network_curve,
|
|
});
|
|
|
|
let index_key = Self::create_offchain_dkg_key(
|
|
network_curve,
|
|
authority_index,
|
|
ROUND_NUMBER_4,
|
|
);
|
|
|
|
sp_io::offchain_index::set(
|
|
&index_key,
|
|
&round4_bundle.bundle.encode(),
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(5)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round5_verifying_package(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round5_verifying_package(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature:<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round5_package_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round5PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round5(round5) = dkg_package else {
|
|
return Err(Error::<T>::Round5PackageInvalidProof.into());
|
|
};
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_fifth_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
let metadata_hashes = [
|
|
round5.merkle_root.as_ref(),
|
|
round5.verifying_key.as_ref(),
|
|
round5.key_members.as_ref(),
|
|
];
|
|
let verifying_hash = cumulative_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
|
|
|
|
let dkg_index = state.get_dkg_index();
|
|
let verification_key = (network_curve, dkg_index, verifying_hash);
|
|
|
|
let latest_participants = Verifications::<T>::try_mutate(
|
|
&verification_key,
|
|
|bitmap| -> Result<ParticipantsBitmap<T>, DispatchError> {
|
|
ensure!(
|
|
!bitmap.contains(authority_index),
|
|
Error::<T>::Round5PackagesAlreadyRegistered,
|
|
);
|
|
|
|
if bitmap.is_empty() {
|
|
*bitmap = ParticipantsBitmap::<T>::empty_from(
|
|
state.get_indexes(),
|
|
);
|
|
}
|
|
|
|
bitmap.insert(authority_index);
|
|
Ok(bitmap.clone())
|
|
})?;
|
|
|
|
VerifyingKeyConsensus::<T>::mutate(&network_curve, &dkg_index, |current| {
|
|
let state_participants_count = current.state.participants.count_ones::<AuthIndex>();
|
|
let latest_participants_count = latest_participants.count_ones::<AuthIndex>();
|
|
|
|
if state_participants_count < latest_participants_count {
|
|
*current = QualifiedConsensusState::new(
|
|
round5.verifying_key,
|
|
latest_participants,
|
|
round5.merkle_root,
|
|
round5.key_members,
|
|
);
|
|
}
|
|
});
|
|
|
|
Self::deposit_event(Event::<T>::Round5PublicPackageRegistered {
|
|
authority_index,
|
|
verifying_hash,
|
|
network_curve,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(6)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_round6_public_share_package(
|
|
dkg_package.bytes_len(),
|
|
),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_round6_public_share_package(
|
|
origin: OriginFor<T>,
|
|
dkg_package: DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature:<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
|
|
Self::validate_round6_package_size(&dkg_package, &signature)
|
|
.map_err(|_| Error::<T>::Round6PackageInvalidProof)?;
|
|
|
|
let DkgPackage::Round6(round6) = dkg_package else {
|
|
return Err(Error::<T>::Round6PackageInvalidProof.into());
|
|
};
|
|
|
|
let expected_share_len = network_curve.element_bytes_len();
|
|
ensure!(
|
|
round6.verifying_share.len() == expected_share_len,
|
|
Error::<T>::Round6PackageInvalidProof,
|
|
);
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
ensure!(state.is_sixth_phase(), Error::<T>::DkgWrongRound);
|
|
|
|
let dkg_index = state.get_dkg_index();
|
|
let qualified_consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
|
|
|
|
network_curve.verify_merkle_proof(
|
|
&round6.verifying_share,
|
|
&round6.merkle_proof,
|
|
qualified_consensus.state.merkle_root,
|
|
authority_index,
|
|
).map_err(|_| Error::<T>::InvalidMerkleProof)?;
|
|
|
|
let verifying_share_key = (network_curve, dkg_index, authority_index);
|
|
|
|
ensure!(
|
|
!VerifyingShares::<T>::contains_key(&verifying_share_key),
|
|
Error::<T>::Round6PackagesAlreadyRegistered
|
|
);
|
|
|
|
VerifyingSharesParticipants::<T>::try_mutate(
|
|
&network_curve,
|
|
&dkg_index,
|
|
|participants| -> DispatchResult {
|
|
ensure!(
|
|
!participants.contains(authority_index),
|
|
Error::<T>::Round6PackagesAlreadyRegistered
|
|
);
|
|
|
|
if participants.is_empty() {
|
|
*participants = ParticipantsBitmap::<T>::empty_from(
|
|
&state.get_indexes()
|
|
);
|
|
}
|
|
|
|
participants.insert(authority_index);
|
|
Ok(())
|
|
})?;
|
|
|
|
VerifyingShares::<T>::insert(&verifying_share_key, round6.verifying_share);
|
|
|
|
Self::deposit_event(Event::<T>::Round6VerifyingShareRegistered {
|
|
authority_index,
|
|
network_curve,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(7)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_nonce_commitment(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_nonce_commitment(
|
|
origin: OriginFor<T>,
|
|
exodus_package: ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature:<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_context = exodus_package.copy_exodus_context();
|
|
|
|
let (mut exodus_request, roast_session) =
|
|
Self::validate_exodus_nonce_commitment_size_and_get_metadata(
|
|
&exodus_package,
|
|
&signature,
|
|
).map_err(|_| Error::<T>::ExodusNoncePackageInvalidProof)?;
|
|
|
|
let ExodusPackage::NonceCommitment(package) = exodus_package else {
|
|
return Err(Error::<T>::ExodusNoncePackageInvalidProof.into());
|
|
};
|
|
|
|
|
|
let threshold = ActiveAuthorities::<T>::decode_len(&network_curve)
|
|
.map(|max_participants| get_byzantium_quorum_threshold(max_participants))
|
|
.ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;
|
|
|
|
ensure!(
|
|
!RoastSessions::<T>::contains_key(exodus_context, authority_index),
|
|
Error::<T>::ExodusAlreadyInRoastSession,
|
|
);
|
|
|
|
let mut roast_state =
|
|
RoastSessionStates::<T>::get(&exodus_context, roast_session);
|
|
|
|
ensure!(
|
|
roast_state.status == RoastStatus::NonceCommitments,
|
|
Error::<T>::ExodusIncorrectRoastStatus,
|
|
);
|
|
|
|
ensure!(
|
|
!roast_state.nonce_committers.contains(authority_index),
|
|
Error::<T>::ExodusNonceAlreadyRegistered,
|
|
);
|
|
|
|
ensure!(
|
|
!roast_state.group_committers.contains(authority_index),
|
|
Error::<T>::ExodusGroupAlreadyRegistered,
|
|
);
|
|
|
|
ensure!(
|
|
!roast_state.partial_signers.contains(authority_index),
|
|
Error::<T>::ExodusSignedMessageAlreadyRegistered,
|
|
);
|
|
|
|
roast_state.nonce_committers.insert(authority_index);
|
|
|
|
if roast_state.nonce_committers.count_ones::<usize>() == threshold {
|
|
roast_state.status = RoastStatus::GroupCommitments;
|
|
exodus_request.next_roast_session = roast_session
|
|
.saturating_add(1);
|
|
|
|
ExodusRequests::<T>::insert((network_curve, exodus_context), exodus_request);
|
|
}
|
|
|
|
RoastSessions::<T>::insert(exodus_context, &authority_index, roast_session);
|
|
RoastSessionStates::<T>::insert(exodus_context, &roast_session, roast_state);
|
|
NonceCommitments::<T>::insert(
|
|
(exodus_context, roast_session), authority_index,
|
|
ExodusSeparatedNonce::new(
|
|
package.hiding_commitment,
|
|
package.binding_commitment,
|
|
),
|
|
);
|
|
|
|
Self::deposit_event(Event::<T>::NonceCommitmentRegistered {
|
|
authority_index,
|
|
exodus_context,
|
|
network_curve,
|
|
roast_session,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(8)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_group_commitment(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_group_commitment(
|
|
origin: OriginFor<T>,
|
|
exodus_package: ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature:<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_context = exodus_package.copy_exodus_context();
|
|
|
|
let roast_session =
|
|
Self::validate_exodus_group_commitment_size_and_get_metadata(
|
|
&exodus_package,
|
|
&signature,
|
|
).map_err(|_| Error::<T>::ExodusGroupPackageInvalidProof)?;
|
|
|
|
let ExodusPackage::GroupCommitment(package) = exodus_package else {
|
|
return Err(Error::<T>::ExodusGroupPackageInvalidProof.into());
|
|
};
|
|
|
|
let max_authorities = ActiveAuthorities::<T>::decode_len(&network_curve)
|
|
.ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;
|
|
|
|
let threshold = get_byzantium_quorum_threshold(max_authorities);
|
|
|
|
ensure!(
|
|
ExodusRequests::<T>::contains_key((&network_curve, exodus_context)),
|
|
Error::<T>::ExodusRequestNotFound
|
|
);
|
|
|
|
let group_commitment_key =(
|
|
exodus_context,
|
|
roast_session,
|
|
package.binding_factors_root,
|
|
&package.group_commitment
|
|
);
|
|
|
|
let mut group_participants =
|
|
GroupCommitters::<T>::get(&group_commitment_key);
|
|
|
|
ensure!(
|
|
!group_participants.contains(authority_index),
|
|
Error::<T>::ExodusGroupAlreadyRegistered,
|
|
);
|
|
|
|
if group_participants.is_empty() {
|
|
group_participants = ParticipantsBitmap::<T>::empty(max_authorities);
|
|
}
|
|
|
|
group_participants.insert(authority_index);
|
|
let group_participants_count = group_participants.count_ones::<usize>();
|
|
let consensus_reached = group_participants_count
|
|
.ge(&threshold.div_ceil(2));
|
|
|
|
let mut roast_state = RoastSessionStates::<T>::get(exodus_context, roast_session);
|
|
|
|
ensure!(
|
|
consensus_reached || roast_state.status == RoastStatus::GroupCommitments,
|
|
Error::<T>::ExodusIncorrectRoastStatus,
|
|
);
|
|
|
|
ensure!(
|
|
roast_state.nonce_committers.contains(authority_index),
|
|
Error::<T>::ExodusNonceNotRegistered,
|
|
);
|
|
|
|
ensure!(
|
|
!roast_state.partial_signers.contains(authority_index),
|
|
Error::<T>::ExodusSignedMessageAlreadyRegistered,
|
|
);
|
|
|
|
roast_state.group_committers.insert(authority_index);
|
|
|
|
if group_participants_count >= threshold.div_ceil(2) {
|
|
roast_state.group_committers |= &roast_state.nonce_committers;
|
|
roast_state.status = RoastStatus::PartialSignatures;
|
|
}
|
|
|
|
RoastSessionStates::<T>::insert(exodus_context, &roast_session, roast_state);
|
|
GroupCommitters::<T>::insert(&group_commitment_key, group_participants.clone());
|
|
GroupCommitmentsConsensus::<T>::mutate(exodus_context, &roast_session, |consensus| {
|
|
let consensus_count = consensus.participants
|
|
.count_ones::<usize>();
|
|
|
|
if consensus_count < group_participants_count {
|
|
*consensus = ConsensusState::new(
|
|
package.group_commitment,
|
|
group_participants,
|
|
package.binding_factors_root,
|
|
);
|
|
}
|
|
});
|
|
|
|
Self::deposit_event(Event::<T>::GroupCommitmentRegistered {
|
|
network_curve,
|
|
exodus_context,
|
|
authority_index,
|
|
roast_session
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(9)]
|
|
#[pallet::weight((
|
|
T::WeightInfo::register_signature_share(),
|
|
DispatchClass::Normal,
|
|
Pays::No,
|
|
))]
|
|
pub fn register_signature_share(
|
|
origin: OriginFor<T>,
|
|
exodus_package: ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature:<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> DispatchResult {
|
|
ensure_none(origin)?;
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_context = exodus_package.copy_exodus_context();
|
|
|
|
let (roast_session, active_dkg_index) =
|
|
Self::validate_exodus_signature_share_size_and_get_metadata(
|
|
&exodus_package,
|
|
&signature,
|
|
).map_err(|_| Error::<T>::ExodusSharePackageInvalidProof)?;
|
|
|
|
let ExodusPackage::SignatureShare(package) = exodus_package else {
|
|
return Err(Error::<T>::ExodusSharePackageInvalidProof.into());
|
|
};
|
|
|
|
let threshold = ActiveAuthorities::<T>::decode_len(&network_curve)
|
|
.map(|max_participants| get_byzantium_quorum_threshold(max_participants))
|
|
.ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;
|
|
|
|
let commitment_consensus =
|
|
GroupCommitmentsConsensus::<T>::get(exodus_context, &roast_session);
|
|
|
|
let consensus_participants_count = commitment_consensus
|
|
.participants
|
|
.count_ones::<usize>();
|
|
|
|
ensure!(
|
|
consensus_participants_count >= threshold.div_ceil(2),
|
|
Error::<T>::ExodusIncorrectRoastStatus,
|
|
);
|
|
|
|
let exodus_request =
|
|
ExodusRequests::<T>::get((&network_curve, exodus_context))
|
|
.ok_or(Error::<T>::ExodusRequestNotFound)?;
|
|
|
|
ensure!(
|
|
match exodus_request.r#type {
|
|
ExodusRequestType::EvmRotation { .. } => {
|
|
!ExodusSignedRotations::<T>::contains_key(
|
|
active_dkg_index, NetworkType::Evm,
|
|
)
|
|
},
|
|
ExodusRequestType::UtxoRotation => {
|
|
!ExodusSignedRotations::<T>::contains_key(
|
|
active_dkg_index, NetworkType::Utxo,
|
|
)
|
|
},
|
|
ExodusRequestType::EvmBridgeOut { .. } | ExodusRequestType::UtxoBridgeOut { .. } => {
|
|
!ExodusSignedBridges::<T>::contains_key(
|
|
active_dkg_index, exodus_context,
|
|
)
|
|
}
|
|
ExodusRequestType::EvmGovernance { .. } => {
|
|
!ExodusSignedGovernance::<T>::contains_key(
|
|
active_dkg_index, exodus_context,
|
|
)
|
|
}
|
|
},
|
|
Error::<T>::ExodusSignedMessageAlreadyExists,
|
|
);
|
|
|
|
let mut roast_state =
|
|
RoastSessionStates::<T>::get(exodus_context, roast_session);
|
|
|
|
ensure!(
|
|
roast_state.status == RoastStatus::PartialSignatures,
|
|
Error::<T>::ExodusIncorrectRoastStatus,
|
|
);
|
|
|
|
ensure!(
|
|
roast_state.nonce_committers.contains(authority_index),
|
|
Error::<T>::ExodusNonceNotRegistered,
|
|
);
|
|
|
|
ensure!(
|
|
roast_state.group_committers.contains(authority_index),
|
|
Error::<T>::ExodusCommitmentNotRegistered,
|
|
);
|
|
|
|
ensure!(
|
|
!roast_state.partial_signers.contains(authority_index),
|
|
Error::<T>::ExodusSignedMessageAlreadyRegistered,
|
|
);
|
|
|
|
network_curve.verify_merkle_proof(
|
|
&package.self_binding_factor,
|
|
&package.binding_factors_proof,
|
|
commitment_consensus.merkle_root,
|
|
authority_index,
|
|
).map_err(|_| Error::<T>::InvalidMerkleProof)?;
|
|
|
|
let nonce_commitment =
|
|
NonceCommitments::<T>::get(
|
|
(exodus_context, roast_session),
|
|
authority_index
|
|
).ok_or(Error::<T>::ExodusNonceNotRegistered)?;
|
|
|
|
let verifying_share =
|
|
VerifyingShares::<T>::get((network_curve, active_dkg_index, authority_index))
|
|
.ok_or(Error::<T>::VerifyingShareNotFound)?;
|
|
|
|
let verifying_key = ActiveVerifyingKey::<T>::get(network_curve);
|
|
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
let message = exodus_request.get_message(&mut message_buffer, exodus_context)
|
|
.ok_or(Error::<T>::ExodusMessageTooBig)?;
|
|
|
|
let invalid_signature_share = network_curve.verify_signature_share(
|
|
authority_index,
|
|
roast_state.group_committers.iter(),
|
|
&package.signature_share,
|
|
&package.self_binding_factor,
|
|
&nonce_commitment.hiding,
|
|
&nonce_commitment.binding,
|
|
&commitment_consensus.element_bytes,
|
|
&verifying_share,
|
|
&verifying_key,
|
|
message,
|
|
).is_err();
|
|
|
|
if invalid_signature_share {
|
|
#[cfg(not(feature = "runtime-benchmarks"))]
|
|
return Err(Error::<T>::ExodusInvalidSignatureShare.into());
|
|
}
|
|
|
|
let signature_scalar =
|
|
SignatureScalars::<T>::get(exodus_context, roast_session);
|
|
|
|
let new_signature_scalar = network_curve
|
|
.accumulate_signature_scalar(
|
|
&signature_scalar[..network_curve.scalar_bytes_len()],
|
|
&package.signature_share,
|
|
)
|
|
.map_err(|_| Error::<T>::ExodusAccumulationFailed)?;
|
|
|
|
roast_state.partial_signers.insert(authority_index);
|
|
|
|
let bounded_signature_scalar =
|
|
BoundedVec::<u8, ConstU32<{ SCALAR_MAX_BYTES }>>::try_from(new_signature_scalar)
|
|
.map_err(|_| Error::<T>::TooManyEntries)?;
|
|
|
|
let exodus_signed_message = ExodusSignedMessage::new(
|
|
commitment_consensus.element_bytes.iter().copied::<u8>(),
|
|
bounded_signature_scalar.iter().copied::<u8>(),
|
|
exodus_request.r#type,
|
|
).ok_or(Error::<T>::TooManyEntries)?;
|
|
|
|
Self::deposit_event(Event::<T>::PartialSignatureRegistered {
|
|
authority_index,
|
|
exodus_context,
|
|
network_curve,
|
|
roast_session
|
|
});
|
|
|
|
RoastSessions::<T>::remove(exodus_context, authority_index);
|
|
|
|
if roast_state.partial_signers.count_ones::<usize>() < threshold {
|
|
RoastSessionStates::<T>::insert(
|
|
exodus_context, &roast_session,
|
|
roast_state
|
|
);
|
|
SignatureScalars::<T>::insert(
|
|
exodus_context, roast_session,
|
|
bounded_signature_scalar
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
RoastSessionStates::<T>::remove(exodus_context, &roast_session);
|
|
SignatureScalars::<T>::remove(exodus_context, &roast_session);
|
|
|
|
GroupCommitmentsConsensus::<T>::remove(exodus_context, &roast_session);
|
|
GroupCommitters::<T>::remove((
|
|
exodus_context, &roast_session,
|
|
&commitment_consensus.merkle_root,
|
|
&commitment_consensus.element_bytes,
|
|
));
|
|
|
|
let invalid_aggregated_signature = network_curve
|
|
.is_signature_valid(
|
|
&verifying_key,
|
|
&commitment_consensus.element_bytes,
|
|
&bounded_signature_scalar,
|
|
message,
|
|
)
|
|
.is_err();
|
|
|
|
if invalid_aggregated_signature {
|
|
#[cfg(not(feature = "runtime-benchmarks"))]
|
|
return Ok(());
|
|
}
|
|
|
|
ExodusRequests::<T>::remove((&network_curve, exodus_context));
|
|
|
|
if exodus_signed_message.is_evm_rotation() {
|
|
ExodusSignedRotations::<T>::insert(
|
|
active_dkg_index, NetworkType::Evm,
|
|
(exodus_context, exodus_signed_message),
|
|
);
|
|
} else if exodus_signed_message.is_utxo_rotation() {
|
|
ExodusSignedRotations::<T>::insert(
|
|
active_dkg_index, NetworkType::Utxo,
|
|
(exodus_context, exodus_signed_message),
|
|
);
|
|
} else if exodus_signed_message.is_bridge_out() {
|
|
ExodusSignedBridges::<T>::insert(
|
|
active_dkg_index, exodus_context,
|
|
exodus_signed_message,
|
|
);
|
|
} else if exodus_signed_message.is_evm_governance() {
|
|
ExodusSignedGovernance::<T>::insert(
|
|
active_dkg_index, exodus_context,
|
|
exodus_signed_message,
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[pallet::call_index(10)]
|
|
#[pallet::weight(T::WeightInfo::register_evm_bridge_out_exodus())]
|
|
pub fn register_evm_bridge_out_exodus(
|
|
origin: OriginFor<T>,
|
|
network_id: NetworkIdOf<T>,
|
|
amount: BalanceOf<T>,
|
|
bounty: Perbill,
|
|
receiver: EvmAddress,
|
|
) -> DispatchResult {
|
|
let who = ensure_signed(origin)?;
|
|
|
|
let base_evm_amount: BalanceOf<T> = T::BaseEvmAmount::get()
|
|
.unique_saturated_into();
|
|
|
|
let is_amount_sufficient = 1u32.checked_shl(ExodusRequests::<T>::count())
|
|
.map(|scale| {
|
|
T::Currency::minimum_balance()
|
|
.saturating_mul(scale.unique_saturated_into())
|
|
.saturating_add(base_evm_amount)
|
|
.le(&amount)
|
|
})
|
|
.unwrap_or(false);
|
|
|
|
ensure!(is_amount_sufficient, Error::<T>::BridgeQueueMinimumNotMet);
|
|
|
|
let actual_amount = T::Currency::burn_from(
|
|
&who,
|
|
amount,
|
|
Preservation::Expendable,
|
|
Precision::Exact,
|
|
Fortitude::Polite,
|
|
)?;
|
|
|
|
Self::do_register_evm_bridge_out_exodus(
|
|
network_id,
|
|
actual_amount,
|
|
bounty,
|
|
receiver,
|
|
)?;
|
|
|
|
Self::deposit_event(Event::<T>::EvmBridgeOutRegistered {
|
|
who,
|
|
network_id,
|
|
amount: actual_amount,
|
|
bounty,
|
|
receiver,
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
|
fn on_initialize(current_block: BlockNumberFor<T>) -> Weight {
|
|
let mut weight = T::DbWeight::get().reads(1);
|
|
|
|
let converted_block: usize = current_block.unique_saturated_into();
|
|
|
|
let network_curve =
|
|
match T::NetworkDataHandler::network_for_block(converted_block) {
|
|
Some((_, network)) => network.curve,
|
|
None => return weight,
|
|
};
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let mut dkg_qualification = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !dkg_qualification.is_dkg_finalized() {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let maybe_cursor = DkgMaybeCursor::<T>::get(&network_curve);
|
|
let release_block = dkg_qualification.get_release_block();
|
|
|
|
if maybe_cursor.is_none() && current_block < release_block { return weight; }
|
|
|
|
let qualification_len =
|
|
match QualificationAuthorities::<T>::decode_len(&network_curve) {
|
|
Some(total_authorities) => total_authorities as AuthIndex,
|
|
None => return weight,
|
|
};
|
|
|
|
let min_threshold = get_byzantium_quorum_threshold(qualification_len);
|
|
|
|
let removal_result = match dkg_qualification.get_phase() {
|
|
DkgPhase::Vacant => DkgRemovalResult::new(|| Self::prepare_round0_storage(network_curve, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round0 => DkgRemovalResult::new(|| Self::prepare_round1_storage(network_curve, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round1 => DkgRemovalResult::new(|| Self::prepare_round2_storage(network_curve, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round2 => DkgRemovalResult::new(|| Self::prepare_round3_storage(network_curve, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round3 => DkgRemovalResult::new(|| Self::prepare_round4_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round4 => DkgRemovalResult::new(|| Self::prepare_round5_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round5 => DkgRemovalResult::new(|| Self::prepare_round6_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round6 => DkgRemovalResult::new(|| Self::prepare_round7_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Round7 => DkgRemovalResult::new(|| Self::prepare_finalization_storage(network_curve, &mut dkg_qualification, maybe_cursor)),
|
|
DkgPhase::Finalized => return weight,
|
|
};
|
|
|
|
weight.saturating_accrue(removal_result.used_weight());
|
|
if !removal_result.fully_cleared() { return weight; }
|
|
|
|
let threshold_reached = min_threshold <= dkg_qualification.count_ones::<AuthIndex>();
|
|
let is_dkg_phase_vacant = dkg_qualification.is_dkg_vacant();
|
|
|
|
if threshold_reached || is_dkg_phase_vacant {
|
|
dkg_qualification.next_phase();
|
|
} else {
|
|
dkg_qualification.restart_dkg();
|
|
}
|
|
|
|
let blocks_delay = if threshold_reached && !is_dkg_phase_vacant {
|
|
T::DkgRoundPeriod::get().unique_saturated_into()
|
|
} else {
|
|
BLOCKS_FOR_DKG_RESET.unique_saturated_into()
|
|
};
|
|
|
|
let next_release_block = release_block.saturating_add(blocks_delay);
|
|
|
|
dkg_qualification.set_release_block(next_release_block);
|
|
QualificationDkgState::<T>::insert(&network_curve, dkg_qualification);
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1))
|
|
}
|
|
|
|
weight
|
|
}
|
|
|
|
fn offchain_worker(current_block: BlockNumberFor<T>) {
|
|
if !sp_io::offchain::is_validator() {
|
|
return;
|
|
}
|
|
|
|
log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus starting at block #{:?}", current_block);
|
|
|
|
match Self::start_exodus(current_block) {
|
|
Ok(inner_results) => {
|
|
for result in inner_results {
|
|
match result {
|
|
Ok(action) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus action '{:?}' at block #{:?} finished.", action, current_block),
|
|
Err(error) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus failed at block #{:?}: {:?}.", current_block, error),
|
|
}
|
|
}
|
|
|
|
},
|
|
Err(err) => {
|
|
log::error!(target: PALLET_LOG_TARGET, "🗺️ Exodus skipped at {:?}: {:?}", current_block, err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[pallet::validate_unsigned]
|
|
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
|
type Call = Call<T>;
|
|
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
|
match call {
|
|
Call::register_round0_package { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round0_package_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_round1_package { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round1_package_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_encrypted_round2_packages { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round2_package_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_round3_complaints { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round3_complaints_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_round4_justifications { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round4_justifications_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_round5_verifying_package { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round5_package_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_round6_public_share_package { dkg_package, signature } => {
|
|
let package_metadata = dkg_package.get_metadata();
|
|
|
|
Self::validate_round6_package_size(dkg_package, signature)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides(package_metadata)
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_nonce_commitment { exodus_package, signature } => {
|
|
let package_metadata = exodus_package.get_metadata();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
let (_, roast_session) =
|
|
Self::validate_exodus_nonce_commitment_size_and_get_metadata(
|
|
exodus_package,
|
|
signature,
|
|
)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides((package_metadata, exodus_context, roast_session))
|
|
.propagate(true)
|
|
.build()
|
|
},
|
|
Call::register_group_commitment { exodus_package, signature } => {
|
|
let package_metadata = exodus_package.get_metadata();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
let roast_session =
|
|
Self::validate_exodus_group_commitment_size_and_get_metadata(
|
|
exodus_package,
|
|
signature,
|
|
)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides((package_metadata, exodus_context, roast_session))
|
|
.propagate(true)
|
|
.build()
|
|
|
|
}
|
|
Call::register_signature_share { exodus_package, signature } => {
|
|
let package_metadata = exodus_package.get_metadata();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
let (roast_session, _) =
|
|
Self::validate_exodus_signature_share_size_and_get_metadata(
|
|
exodus_package,
|
|
signature,
|
|
)?;
|
|
|
|
ValidTransaction::with_tag_prefix("GhostExodus")
|
|
.priority(T::UnsignedPriority::get())
|
|
.longevity(T::UnsignedLongevity::get())
|
|
.and_provides((package_metadata, exodus_context, roast_session))
|
|
.propagate(true)
|
|
.build()
|
|
}
|
|
_ => InvalidTransaction::Call.into(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
fn get_and_increment_exodus_session(
|
|
network_id: NetworkIdOf<T>,
|
|
) -> Result<ExodusSession, DispatchError> {
|
|
CurrentExodusSessions::<T>::try_mutate(|all_exodus_sessions| {
|
|
let current_session = match all_exodus_sessions.get_mut(&network_id) {
|
|
Some(session_ref) => {
|
|
let current = *session_ref;
|
|
*session_ref = current.saturating_add(1);
|
|
current
|
|
},
|
|
None => {
|
|
all_exodus_sessions.try_insert(network_id, 1)
|
|
.map_err(|_| Error::<T>::TooManyEntries)?;
|
|
0
|
|
}
|
|
};
|
|
|
|
Ok(current_session)
|
|
})
|
|
}
|
|
|
|
fn do_register_evm_exodus(
|
|
network_id: NetworkIdOf<T>,
|
|
exodus_request: ExodusRequest<T>,
|
|
) -> DispatchResult {
|
|
let network = T::NetworkDataHandler::get(&network_id)
|
|
.ok_or(Error::<T>::NetworkDoesNotExist)?;
|
|
|
|
ensure!(
|
|
QualificationDkgState::<T>::get(&network.curve).is_dkg_finalized(),
|
|
Error::<T>::DkgAuthoritiesInProgress,
|
|
);
|
|
|
|
ensure!(network.r#type.is_evm(), Error::<T>::WrongNetworkType);
|
|
|
|
let exodus_session = Self::get_and_increment_exodus_session(network_id)?;
|
|
let exodus_context = ExodusContext::new(network_id, exodus_session);
|
|
|
|
ExodusRequests::<T>::insert((&network.curve, &exodus_context), exodus_request);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn do_register_evm_bridge_out_exodus(
|
|
network_id: NetworkIdOf<T>,
|
|
amount: BalanceOf<T>,
|
|
bounty: Perbill,
|
|
receiver: EvmAddress,
|
|
) -> DispatchResult {
|
|
let out_amount =
|
|
T::NetworkDataHandler::register_outgoing(&network_id, amount)
|
|
.map_err(|_| Error::<T>::BridgeOverflow)?;
|
|
|
|
let evm_bridge_request = ExodusRequest::evm_bridge_out(out_amount, bounty, receiver);
|
|
|
|
Self::do_register_evm_exodus(network_id, evm_bridge_request)
|
|
}
|
|
|
|
fn do_register_evm_governance(
|
|
network_id: NetworkIdOf<T>,
|
|
target_address: EvmAddress,
|
|
governance_action: GovernanceAction,
|
|
) -> DispatchResult {
|
|
let evm_governance_request =
|
|
ExodusRequest::evm_governance(target_address, governance_action);
|
|
|
|
Self::do_register_evm_exodus(network_id, evm_governance_request)
|
|
}
|
|
|
|
fn prepare_round_storage<ClearFn, CompleteFn>(
|
|
network_curve: NetworkCurve,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
clear_fn: ClearFn,
|
|
on_complete_fn: CompleteFn,
|
|
) -> (Weight, bool)
|
|
where
|
|
ClearFn: FnOnce(Option<&[u8]>, &mut Weight) -> Option<Vec<u8>>,
|
|
CompleteFn: FnOnce(&mut Weight),
|
|
{
|
|
let mut weight = T::DbWeight::get().reads_writes(0, 0);
|
|
|
|
let maybe_cursor_slice: Option<&[u8]> =
|
|
maybe_cursor.as_ref().map(|v| &v[..]);
|
|
|
|
let fully_cleared = match clear_fn(maybe_cursor_slice, &mut weight) {
|
|
Some(cursor) => {
|
|
match BoundedVec::<u8, T::MaxCursorLen>::try_from(cursor) {
|
|
Ok(cursor) => {
|
|
DkgMaybeCursor::<T>::insert(&network_curve, cursor)
|
|
},
|
|
Err(_) => {
|
|
// NOTE: should never happen. Because we are using
|
|
// overwrites but not insertions. Set `T::MaxCursorLen`
|
|
// based on the longest key - currently the key for
|
|
// `GroupCommitters` up to 160 bytes
|
|
//
|
|
// context = u128 + u128 + prefix = 16 + 16 + 8 = 40
|
|
// roast_session = u128 + prefix = 16 + 8 = 24
|
|
// exodus_hash = 32 unless H256 is changed
|
|
// group_key = 32 in any case because of Blake2_256
|
|
//
|
|
// Twox128("pallet") + Twox128("function") = 16 + 16
|
|
// result = 40 + 24 + 32 + 32 + 32 = 160 bytes
|
|
DkgMaybeCursor::<T>::remove(&network_curve)
|
|
}
|
|
}
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
false
|
|
},
|
|
None => {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
DkgMaybeCursor::<T>::remove(&network_curve);
|
|
|
|
on_complete_fn(&mut weight);
|
|
true
|
|
}
|
|
};
|
|
|
|
(weight, fully_cleared)
|
|
}
|
|
|
|
fn prepare_round0_storage(
|
|
network_curve: NetworkCurve,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|cursor_slice, weight| {
|
|
let result = Round0Packages::<T>::clear(
|
|
T::RemovalLimit::get(),
|
|
cursor_slice,
|
|
);
|
|
|
|
weight.saturating_accrue(
|
|
T::DbWeight::get().reads_writes(
|
|
(result.backend as u64).saturating_add(1),
|
|
result.backend as u64
|
|
)
|
|
);
|
|
|
|
result.maybe_cursor
|
|
},
|
|
|_| {
|
|
dkg_qualification.nullify_indexes();
|
|
},
|
|
)
|
|
}
|
|
|
|
fn prepare_round1_storage(
|
|
network_curve: NetworkCurve,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, weight| {
|
|
let participants_len = dkg_qualification.get_indexes().active_bits_count();
|
|
let empty_round1_packages = ParticipantsBitmap::<T>::empty(participants_len);
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
Round1Packages::<T>::insert(network_curve, empty_round1_packages);
|
|
None
|
|
},
|
|
|_| {}
|
|
)
|
|
}
|
|
|
|
fn prepare_round2_storage(
|
|
network_curve: NetworkCurve,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
let participants_len = dkg_qualification.get_indexes().active_bits_count();
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
let empty_round2_packages = ParticipantsBitmap::<T>::empty(participants_len);
|
|
Round2Packages::<T>::insert(network_curve, empty_round2_packages);
|
|
None
|
|
},
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let round1_participants = Round1Packages::<T>::get(&network_curve);
|
|
dkg_qualification.intersect_indexes(&round1_participants);
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn prepare_round3_storage(
|
|
network_curve: NetworkCurve,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
Complaints::<T>::remove(network_curve);
|
|
None
|
|
},
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let round2_packages = Round2Packages::<T>::get(&network_curve);
|
|
dkg_qualification.intersect_indexes(&round2_packages);
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn prepare_round4_storage(
|
|
network_curve: NetworkCurve,
|
|
min_threshold: AuthIndex,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
let dkg_index = dkg_qualification.get_dkg_index();
|
|
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|cursor_slice, weight| {
|
|
let prev_dkg_index = dkg_index.saturating_sub(1);
|
|
let result = Verifications::<T>::clear_prefix(
|
|
(network_curve, prev_dkg_index),
|
|
T::RemovalLimit::get(),
|
|
cursor_slice,
|
|
);
|
|
|
|
weight.saturating_accrue(
|
|
T::DbWeight::get().reads_writes(
|
|
(result.backend as u64).saturating_add(1),
|
|
result.backend as u64
|
|
)
|
|
);
|
|
|
|
let maybe_cursor = result.maybe_cursor;
|
|
|
|
if maybe_cursor.is_none() {
|
|
Justifications::<T>::remove(network_curve);
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
}
|
|
|
|
maybe_cursor
|
|
},
|
|
|weight| {
|
|
let qualification_indexes = dkg_qualification.get_indexes();
|
|
let safe_count = min_threshold.saturating_sub(1);
|
|
|
|
let mut offence_counters = BTreeMap::<AuthIndex, AuthIndex>::new();
|
|
|
|
let mut compromised_participants = ParticipantsBitmap::<T>::empty_from(
|
|
&qualification_indexes,
|
|
);
|
|
let mut active_participants = ParticipantsBitmap::<T>::empty_from(
|
|
&qualification_indexes,
|
|
);
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let complaints = Complaints::<T>::get(&network_curve);
|
|
|
|
complaints.iter().for_each(|(&auth_index, bitmap)| {
|
|
active_participants.insert(auth_index);
|
|
bitmap.iter::<AuthIndex>().for_each(|index| {
|
|
offence_counters.entry(index)
|
|
.and_modify(|count| *count = count.saturating_add(1))
|
|
.or_insert(1);
|
|
})
|
|
});
|
|
|
|
offence_counters.iter()
|
|
.filter(|(_, &count)| count >= safe_count)
|
|
.for_each(|(&auth_index, _)| {
|
|
compromised_participants.insert(auth_index);
|
|
});
|
|
|
|
let result_mask = qualification_indexes
|
|
& active_participants
|
|
& !compromised_participants;
|
|
|
|
dkg_qualification.intersect_indexes(&result_mask);
|
|
}
|
|
)
|
|
}
|
|
|
|
pub fn prepare_round5_storage(
|
|
network_curve: NetworkCurve,
|
|
min_threshold: AuthIndex,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
let dkg_index = dkg_qualification.get_dkg_index();
|
|
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, weight| {
|
|
let prev_dkg_index = dkg_index.saturating_sub(1);
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
VerifyingKeyConsensus::<T>::remove(network_curve, &prev_dkg_index);
|
|
None
|
|
},
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(2));
|
|
let justifications_bitmap = Justifications::<T>::get(network_curve);
|
|
let complaints_bitmap = Complaints::<T>::get(network_curve);
|
|
|
|
let safe_count = min_threshold.saturating_sub(1);
|
|
let mut number_of_leaked_keys = 0;
|
|
|
|
let qualified_indexes = dkg_qualification.get_indexes();
|
|
let total_authorities_len = qualified_indexes.count_ones::<usize>();
|
|
|
|
let mut failed_authorities = ParticipantsBitmap::<T>::empty_from(
|
|
&qualified_indexes,
|
|
);
|
|
|
|
let mut active_authorities = ParticipantsBitmap::<T>::empty_from(
|
|
&qualified_indexes,
|
|
);
|
|
|
|
let mut complaints_matrix = vec![
|
|
ParticipantsBitmap::<T>::empty_from(&qualified_indexes);
|
|
total_authorities_len
|
|
];
|
|
|
|
complaints_bitmap.iter().for_each(|(&accuser_index, offenders_bitmap)| {
|
|
offenders_bitmap.iter().for_each(|offended_index: AuthIndex| {
|
|
let idx = offended_index as usize;
|
|
if idx >= complaints_matrix.len() { return; }
|
|
|
|
complaints_matrix[idx].insert(accuser_index);
|
|
});
|
|
});
|
|
|
|
justifications_bitmap.iter().for_each(|(&offended_index, recipients_bitmap)| {
|
|
active_authorities.insert(offended_index);
|
|
|
|
if recipients_bitmap.count_ones::<AuthIndex>() >= safe_count {
|
|
number_of_leaked_keys.saturating_inc();
|
|
}
|
|
|
|
let idx = offended_index as usize;
|
|
if idx >= complaints_matrix.len() { return; }
|
|
|
|
let accusers_bitmap = &complaints_matrix[idx];
|
|
if accusers_bitmap.is_empty() { return; }
|
|
|
|
let successful_justifications = accusers_bitmap & recipients_bitmap;
|
|
let failed_to_justify = successful_justifications != *accusers_bitmap;
|
|
|
|
if failed_to_justify {
|
|
failed_authorities.insert(offended_index);
|
|
}
|
|
});
|
|
|
|
let result_mask = if number_of_leaked_keys >= min_threshold {
|
|
ParticipantsBitmap::<T>::empty_from(&qualified_indexes)
|
|
} else {
|
|
active_authorities & !failed_authorities
|
|
};
|
|
|
|
dkg_qualification.intersect_indexes(&result_mask);
|
|
}
|
|
)
|
|
}
|
|
|
|
fn prepare_round6_storage(
|
|
network_curve: NetworkCurve,
|
|
min_threshold: AuthIndex,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
let dkg_index = dkg_qualification.get_dkg_index();
|
|
let default_participants = ParticipantsBitmap::<T>::empty_from(
|
|
&dkg_qualification.get_indexes(),
|
|
);
|
|
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|cursor_slice, weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
let active_dkg_index = ActiveDkgAuthorities::<T>::get(&network_curve)
|
|
.get_dkg_index();
|
|
|
|
let previous_dkg_index = dkg_index.saturating_sub(1);
|
|
|
|
if previous_dkg_index == active_dkg_index {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
VerifyingSharesParticipants::<T>::insert(
|
|
&network_curve,
|
|
&previous_dkg_index,
|
|
default_participants,
|
|
);
|
|
|
|
return None;
|
|
}
|
|
|
|
|
|
let result = VerifyingShares::<T>::clear_prefix(
|
|
(network_curve, previous_dkg_index),
|
|
T::RemovalLimit::get(),
|
|
cursor_slice,
|
|
);
|
|
|
|
weight.saturating_accrue(
|
|
T::DbWeight::get().reads_writes(
|
|
(result.backend as u64).saturating_add(1),
|
|
result.backend as u64
|
|
)
|
|
);
|
|
|
|
let maybe_cursor = result.maybe_cursor;
|
|
|
|
if maybe_cursor.is_none() {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
VerifyingSharesParticipants::<T>::insert(
|
|
&network_curve,
|
|
&previous_dkg_index,
|
|
default_participants,
|
|
);
|
|
}
|
|
|
|
maybe_cursor
|
|
},
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
|
|
|
|
if consensus.state.participants.count_ones::<AuthIndex>() < min_threshold {
|
|
dkg_qualification.nullify_indexes();
|
|
} else {
|
|
let key_members = ParticipantsBitmap::<T>::from_bitmask(&consensus.members);
|
|
dkg_qualification.intersect_indexes(&key_members);
|
|
}
|
|
})
|
|
}
|
|
|
|
fn prepare_round7_storage(
|
|
network_curve: NetworkCurve,
|
|
min_threshold: AuthIndex,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
let dkg_index = dkg_qualification.get_dkg_index();
|
|
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, _| None,
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
|
|
let participants =
|
|
VerifyingSharesParticipants::<T>::get(&network_curve, dkg_index);
|
|
|
|
dkg_qualification.intersect_indexes(&participants);
|
|
|
|
if dkg_qualification.count_ones::<AuthIndex>() < min_threshold {
|
|
dkg_qualification.nullify_indexes();
|
|
return;
|
|
}
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
|
|
let verifying_key_slice = consensus.state.element_bytes.as_slice();
|
|
|
|
if verifying_key_slice.len() != network_curve.element_bytes_len() {
|
|
dkg_qualification.nullify_indexes();
|
|
return;
|
|
}
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let mut all_exodus_sessions = CurrentExodusSessions::<T>::get();
|
|
let mut rotation_requests = BoundedVec::<
|
|
(ExodusRequest<T>, ExodusContext<NetworkIdOf<T>>),
|
|
<T::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks,
|
|
>::with_max_capacity();
|
|
|
|
let mut rotation_failed = false;
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
for (network_type, network_ids) in T::NetworkDataHandler::iter_types_by_curve(&network_curve) {
|
|
let processed_network_type = matches!(network_type, NetworkType::Evm);
|
|
if !processed_network_type { continue; }
|
|
|
|
let mut process_iteration = || -> Option<()> {
|
|
let element_len = network_curve.element_bytes_len();
|
|
let evm_public_key = EvmBytes32::from_slice(
|
|
&verifying_key_slice[1..element_len]
|
|
);
|
|
|
|
let (request, context) = Self::prepare_evm_rotation(
|
|
evm_public_key,
|
|
network_ids.as_ref(),
|
|
&mut all_exodus_sessions
|
|
)?;
|
|
|
|
if !request.r#type.is_evm_rotation() { return None; }
|
|
|
|
dkg_qualification.try_push_rotation(network_type, context)?;
|
|
rotation_requests.try_push((request, context)).ok()?;
|
|
|
|
Some(())
|
|
};
|
|
|
|
if process_iteration().is_none() {
|
|
rotation_failed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if rotation_failed {
|
|
dkg_qualification.nullify_indexes();
|
|
} else {
|
|
for (exodus_request, exodus_context) in rotation_requests {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
ExodusRequests::<T>::insert(
|
|
(network_curve, exodus_context),
|
|
exodus_request
|
|
);
|
|
}
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
CurrentExodusSessions::<T>::put(all_exodus_sessions);
|
|
|
|
dkg_qualification.set_verifying_key(consensus.state.element_bytes);
|
|
}
|
|
})
|
|
}
|
|
|
|
fn prepare_finalization_storage(
|
|
network_curve: NetworkCurve,
|
|
dkg_qualification: &mut PendingDkgAuthorities<T>,
|
|
maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
|
|
) -> (Weight, bool) {
|
|
Self::prepare_round_storage(
|
|
network_curve,
|
|
maybe_cursor,
|
|
|_, _| None,
|
|
|weight| {
|
|
weight.saturating_accrue(T::DbWeight::get().reads(2));
|
|
let dkg_index = ActiveDkgAuthorities::<T>::get(&network_curve)
|
|
.get_dkg_index();
|
|
|
|
let is_initial_dkg_round =
|
|
!ActiveVerifyingKey::<T>::contains_key(network_curve);
|
|
|
|
let all_rotations_have_signatures = dkg_qualification
|
|
.iter_rotations()
|
|
.fold(true, |accumulator, (network_type, exodus_context)| {
|
|
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
|
ExodusRequests::<T>::remove((&network_curve, &exodus_context));
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let has_signature = ExodusSignedRotations::<T>::contains_key(
|
|
dkg_index,
|
|
network_type,
|
|
);
|
|
|
|
accumulator && has_signature
|
|
});
|
|
|
|
if all_rotations_have_signatures || is_initial_dkg_round {
|
|
let verifying_key = dkg_qualification.get_verifying_key();
|
|
let activated_state: ReadyDkgAuthorities<T> = (&*dkg_qualification).into();
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().reads(1));
|
|
let qualified_authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
|
|
weight.saturating_accrue(T::DbWeight::get().writes(3));
|
|
ActiveDkgAuthorities::<T>::insert(&network_curve, activated_state);
|
|
ActiveVerifyingKey::<T>::insert(&network_curve, verifying_key);
|
|
ActiveAuthorities::<T>::set(&network_curve, qualified_authorities);
|
|
} else {
|
|
dkg_qualification.nullify_indexes();
|
|
}
|
|
})
|
|
}
|
|
|
|
fn prepare_evm_rotation(
|
|
evm_public_key: EvmBytes32,
|
|
network_ids: &[NetworkIdOf<T>],
|
|
evm_exodus_sessions: &mut ExodusSessionsMap<T>,
|
|
) -> Option<(ExodusRequest<T>, ExodusContext<NetworkIdOf<T>>)> {
|
|
if network_ids.len() >= 128 { return None; }
|
|
|
|
let exodus_session_size = core::mem::size_of::<ExodusSession>();
|
|
let packed_sessions_size = exodus_session_size * network_ids.len();
|
|
|
|
let evm_xor_key = PerfectSlotMapperU128::get_lcg_xor_mask(network_ids)?;
|
|
let (bit_shift, bit_mask) = PerfectSlotMapperU128::get_bits_metadata()?;
|
|
|
|
let mut evm_sessions_bitmask: u128 = 0;
|
|
let mut maybe_best_exodus_context: Option<ExodusContext<NetworkIdOf<T>>> = None;
|
|
let mut evm_packed_sessions = EvmSessionsBundle::with_max_capacity();
|
|
evm_packed_sessions.bounded_resize(packed_sessions_size, 0u8);
|
|
|
|
let mut evm_slots = [0usize; 128];
|
|
for (i, network_id) in network_ids.iter().enumerate() {
|
|
let slot_index =
|
|
PerfectSlotMapperU128::get_lcg_based_slot_index(
|
|
bit_mask,
|
|
bit_shift,
|
|
evm_xor_key,
|
|
network_id
|
|
);
|
|
|
|
if slot_index > 127 { return None; }
|
|
|
|
evm_sessions_bitmask |= 1u128 << slot_index;
|
|
evm_slots[i] = slot_index;
|
|
}
|
|
|
|
for (i, &network_id) in network_ids.into_iter().enumerate() {
|
|
let exodus_session = match evm_exodus_sessions.get_mut(&network_id) {
|
|
Some(session_ref) => {
|
|
let current = *session_ref;
|
|
*session_ref = current.saturating_add(1);
|
|
current
|
|
},
|
|
None => {
|
|
evm_exodus_sessions.try_insert(network_id, 1).ok()?;
|
|
0
|
|
}
|
|
};
|
|
|
|
if maybe_best_exodus_context
|
|
.as_ref()
|
|
.map_or(true, |c| exodus_session > c.session) {
|
|
maybe_best_exodus_context = Some(
|
|
ExodusContext::<NetworkIdOf<T>>::new(
|
|
network_id,
|
|
exodus_session
|
|
)
|
|
);
|
|
}
|
|
|
|
let lcg_slot_index = evm_slots[i];
|
|
|
|
let lower_bits = (1u128 << lcg_slot_index) - 1;
|
|
let slot_index = (evm_sessions_bitmask & lower_bits).count_ones() as usize;
|
|
|
|
let offset_start = slot_index * exodus_session_size;
|
|
let offset_end = offset_start + exodus_session_size;
|
|
|
|
if offset_end > evm_packed_sessions.len() {
|
|
return None;
|
|
}
|
|
|
|
evm_packed_sessions[offset_start..offset_end]
|
|
.copy_from_slice(&exodus_session.to_be_bytes());
|
|
}
|
|
|
|
let best_exodus_context = maybe_best_exodus_context?;
|
|
|
|
let rotation_request =
|
|
ExodusRequest::<T>::evm_rotation(
|
|
evm_public_key,
|
|
evm_xor_key,
|
|
evm_sessions_bitmask,
|
|
evm_packed_sessions
|
|
);
|
|
|
|
Some((rotation_request, best_exodus_context))
|
|
}
|
|
|
|
fn validate_round0_package_size(
|
|
dkg_package: & DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_zero_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round1_package_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let DkgPackage::Round1(round1) = dkg_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_first_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let expected_count = get_byzantium_quorum_threshold(authorities.len());
|
|
|
|
let expected_package_len = round1_package_size(
|
|
expected_count,
|
|
network_curve.element_bytes_len(),
|
|
network_curve.signature_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
if round1.package.len() != expected_package_len {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authority = authorities
|
|
.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round2_package_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let DkgPackage::Round2(round2) = dkg_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_second_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let bundle_packages_count = round2.bundle.bitmask
|
|
.iter()
|
|
.map(|byte| byte.count_ones() as usize)
|
|
.sum::<usize>()
|
|
.saturating_add(1);
|
|
|
|
if bundle_packages_count != state.count_ones::<usize>() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let has_invalid_round2_recipients = round2.bundle.bitmask
|
|
.iter()
|
|
.enumerate()
|
|
.any(|(byte_idx, &byte)| {
|
|
if byte == 0 { return false; }
|
|
(0..8).any(|bit_idx| {
|
|
if (byte & (1u8 << bit_idx)) == 0 { return false; }
|
|
let global_index = byte_idx * 8 + bit_idx;
|
|
if global_index == authority_index as usize { return true; }
|
|
!state.contains_index(global_index)
|
|
})
|
|
});
|
|
|
|
if has_invalid_round2_recipients {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let padded_participants = state.highest_bit::<usize>()
|
|
.map(|bit_pos| bit_pos.saturating_add(1))
|
|
.unwrap_or_default();
|
|
|
|
let expected_blob_len = round2_encrypted_package_size(
|
|
padded_participants,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
if round2.bundle.blob.len() != expected_blob_len {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round3_complaints_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let DkgPackage::Round3(round3_complaints) = dkg_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_third_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let estimated_indices_len = state
|
|
.highest_bit::<usize>()
|
|
.map(|bit_index| bit_index.saturating_add(1).div_ceil(8))
|
|
.unwrap_or_default();
|
|
|
|
if estimated_indices_len != round3_complaints.indices.len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let has_invalid_accused_indexes = round3_complaints.indices
|
|
.iter()
|
|
.enumerate()
|
|
.any(|(byte_idx, &byte)| {
|
|
if byte == 0 { return false; }
|
|
(0..8).any(|bit_idx| {
|
|
if (byte & (1u8 << bit_idx)) == 0 { return false; }
|
|
let global_index = byte_idx * 8 + bit_idx;
|
|
if global_index == authority_index as usize { return true; }
|
|
!state.contains_index(global_index)
|
|
})
|
|
});
|
|
|
|
if has_invalid_accused_indexes {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities
|
|
.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round4_justifications_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let DkgPackage::Round4(round4) = dkg_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_fourth_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let bundle_packages_count = round4.bundle.bitmask
|
|
.iter()
|
|
.map(|byte| byte.count_ones() as usize)
|
|
.sum::<usize>()
|
|
.saturating_add(1);
|
|
|
|
if bundle_packages_count > state.count_ones::<usize>() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let has_invalid_justifications = round4.bundle.bitmask
|
|
.iter()
|
|
.enumerate()
|
|
.any(|(byte_idx, &byte)| {
|
|
if byte == 0 { return false; }
|
|
(0..8).any(|bit_idx| {
|
|
if (byte & (1u8 << bit_idx)) == 0 { return false; }
|
|
let global_index = byte_idx * 8 + bit_idx;
|
|
if global_index == authority_index as usize { return true; }
|
|
!state.contains_index(global_index)
|
|
})
|
|
});
|
|
|
|
if has_invalid_justifications {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let padded_participants = state.highest_bit::<usize>()
|
|
.map(|bit_pos| bit_pos.saturating_add(1))
|
|
.unwrap_or_default();
|
|
|
|
let expected_blob_len = round2_package_size(
|
|
padded_participants,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
if round4.bundle.blob.len() != expected_blob_len {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round5_package_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let DkgPackage::Round5(round5) = dkg_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_fifth_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let estimated_max_size = network_curve
|
|
.element_bytes_len()
|
|
.saturating_add(ExodusHash::len_bytes());
|
|
|
|
if dkg_package.bytes_len() as usize != estimated_max_size {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let has_non_qualified_member = round5.key_members
|
|
.iter()
|
|
.enumerate()
|
|
.any(|(byte_idx, &byte)| {
|
|
if byte == 0 { return false; }
|
|
(0..8).any(|bit_idx| {
|
|
if (byte & (1u8 << bit_idx)) == 0 { return false; }
|
|
let global_index = byte_idx * 8 + bit_idx;
|
|
!state.contains_index(global_index)
|
|
})
|
|
});
|
|
|
|
if has_non_qualified_member {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_round6_package_size(
|
|
dkg_package: &DkgPackage<NetworkCurve, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(), InvalidTransaction> {
|
|
let network_curve = dkg_package.get_network_curve();
|
|
let authority_index = dkg_package.get_authority_index();
|
|
let package_dkg_index = dkg_package.get_dkg_index();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let state = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if !state.is_sixth_phase() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if state.get_dkg_index() != package_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
let estimated_proof_count = state
|
|
.count_ones::<usize>()
|
|
.next_power_of_two()
|
|
.trailing_zeros();
|
|
|
|
if dkg_package.bytes_len() != estimated_proof_count {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = dkg_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = dkg_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_exodus_nonce_commitment_size_and_get_metadata<'a>(
|
|
exodus_package: &'a ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(ExodusRequest<T>, RoastSession), InvalidTransaction> {
|
|
let ExodusPackage::NonceCommitment(package) = exodus_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_dkg_index = exodus_package.get_dkg_index();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
if package.hiding_commitment.len() != network_curve.element_bytes_len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if package.binding_commitment.len() != network_curve.element_bytes_len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
network_curve.is_nonce_valid_point(&package.hiding_commitment)
|
|
.map_err(|_| InvalidTransaction::BadProof)?;
|
|
|
|
network_curve.is_nonce_valid_point(&package.binding_commitment)
|
|
.map_err(|_| InvalidTransaction::BadProof)?;
|
|
|
|
if QualificationDkgState::<T>::get(&network_curve).is_dkg_pending() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);
|
|
|
|
if !active_state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
if active_state.get_dkg_index() != exodus_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = exodus_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = ActiveAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = exodus_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let exodus_request =
|
|
ExodusRequests::<T>::get((&network_curve, exodus_context))
|
|
.ok_or(InvalidTransaction::BadProof)?;
|
|
|
|
let roast_session =
|
|
match RoastSessions::<T>::get(exodus_context, &authority_index) {
|
|
Some(_) => return Err(InvalidTransaction::BadProof),
|
|
None => exodus_request.next_roast_session,
|
|
};
|
|
|
|
Ok((exodus_request, roast_session))
|
|
}
|
|
|
|
fn validate_exodus_group_commitment_size_and_get_metadata<'a>(
|
|
exodus_package: &'a ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<RoastSession, InvalidTransaction> {
|
|
let ExodusPackage::GroupCommitment(package) = exodus_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_dkg_index = exodus_package.get_dkg_index();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if package.group_commitment.len() != network_curve.element_bytes_len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if QualificationDkgState::<T>::get(&network_curve).is_dkg_pending() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);
|
|
|
|
if !active_state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
if active_state.get_dkg_index() != exodus_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = exodus_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = ActiveAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = exodus_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
RoastSessions::<T>::get(exodus_context, &authority_index)
|
|
.ok_or(InvalidTransaction::BadProof)
|
|
}
|
|
|
|
fn validate_exodus_signature_share_size_and_get_metadata<'a>(
|
|
exodus_package: &'a ExodusPackage<NetworkCurve, NetworkIdOf<T>, BlockNumberFor<T>, T>,
|
|
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
|
|
) -> Result<(RoastSession, DkgIndex), InvalidTransaction> {
|
|
let ExodusPackage::SignatureShare(package) = exodus_package else {
|
|
return Err(InvalidTransaction::BadProof);
|
|
};
|
|
|
|
let network_curve = exodus_package.get_network_curve();
|
|
let authority_index = exodus_package.get_authority_index();
|
|
let exodus_dkg_index = exodus_package.get_dkg_index();
|
|
let exodus_context = exodus_package.get_exodus_context();
|
|
|
|
if package.signature_share.len() != network_curve.scalar_bytes_len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if package.self_binding_factor.len() != network_curve.scalar_bytes_len() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
if !T::NetworkDataHandler::curve_exists(&network_curve) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let qualification_state = QualificationDkgState::<T>::get(&network_curve);
|
|
if qualification_state.is_dkg_pending() {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);
|
|
|
|
if !active_state.contains_index(authority_index) {
|
|
return Err(InvalidTransaction::BadSigner);
|
|
}
|
|
|
|
if active_state.get_dkg_index() != exodus_dkg_index {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let current_block = T::BlockNumberProvider::current_block_number();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let expiration_block = exodus_package.get_block_number()
|
|
.saturating_add(block_longevity);
|
|
|
|
if current_block >= expiration_block {
|
|
return Err(InvalidTransaction::Stale);
|
|
}
|
|
|
|
let authorities = ActiveAuthorities::<T>::get(&network_curve);
|
|
let authority = authorities.get(authority_index as usize)
|
|
.ok_or(InvalidTransaction::BadSigner)?;
|
|
|
|
let encoded_package = exodus_package.encode();
|
|
if !authority.verify(&encoded_package, signature) {
|
|
return Err(InvalidTransaction::BadProof);
|
|
}
|
|
|
|
let roast_session =
|
|
RoastSessions::<T>::get(exodus_context, &authority_index)
|
|
.ok_or(InvalidTransaction::BadProof)?;
|
|
|
|
Ok((roast_session, active_state.get_dkg_index()))
|
|
}
|
|
|
|
fn start_exodus(
|
|
current_block: BlockNumberFor<T>,
|
|
) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>>>> {
|
|
let converted_block: usize = current_block.unique_saturated_into();
|
|
let network_curve = T::NetworkDataHandler::network_for_block(converted_block)
|
|
.map(|(_, network)| network.curve)
|
|
.ok_or(ExodusError::NoStoredNetworks)?;
|
|
|
|
let qualification = QualificationDkgState::<T>::get(&network_curve);
|
|
|
|
if qualification.is_dkg_pending() {
|
|
Self::dkg_start(network_curve, current_block, qualification)
|
|
} else {
|
|
Self::exodus_start(network_curve, current_block)
|
|
}
|
|
}
|
|
|
|
fn exodus_start(
|
|
network_curve: NetworkCurve,
|
|
current_block: BlockNumberFor<T>,
|
|
) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>>>> {
|
|
let network_encoded = network_curve.encode();
|
|
let exodus_lock_key = Self::create_storage_key(b"exodus-lock-", &network_encoded);
|
|
let lock_until = rt_offchain::Duration::from_millis(MIN_LOCK_GUARD_PERIOD);
|
|
|
|
let mut exodus_lock = StorageLock::<Time>::with_deadline(&exodus_lock_key, lock_until);
|
|
let _guard = exodus_lock
|
|
.try_lock()
|
|
.map_err(|_| ExodusError::OffchainTimeoutPeriod)?;
|
|
|
|
let authorities = ActiveAuthorities::<T>::get(&network_curve);
|
|
let active_authorities = ActiveDkgAuthorities::<T>::get(&network_curve);
|
|
|
|
let results = Self::get_local_authorities(authorities.into_iter())
|
|
.map(move |(authority_index, authority_key)| {
|
|
Self::exodus_run_process(
|
|
authority_index,
|
|
authority_key,
|
|
network_curve,
|
|
current_block,
|
|
&active_authorities,
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
Ok(results)
|
|
}
|
|
|
|
fn exodus_run_process(
|
|
authority_index: AuthIndex,
|
|
authority_key: T::AuthorityId,
|
|
network_curve: NetworkCurve,
|
|
current_block: BlockNumberFor<T>,
|
|
active_authorities: &ReadyDkgAuthorities<T>
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
if !active_authorities.contains_index(authority_index) {
|
|
return Err(ExodusError::InvalidParticipantId)
|
|
}
|
|
|
|
let dkg_index = active_authorities.get_dkg_index();
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let dkg_prefix = Self::create_dkg_based_prefix(dkg_index);
|
|
let round_metadata = DkgStorage::default()
|
|
.with_network_curve(network_curve)
|
|
.with_current_block(current_block)
|
|
.with_block_longevity(block_longevity)
|
|
.read_local_storage(&dkg_prefix, authority_index);
|
|
|
|
let Some(DkgStorageEnum::Round5 { secret_share, .. }) =
|
|
round_metadata.into_inner() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_5,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let exodus_storage = ExodusStorage::default()
|
|
.with_current_block(current_block)
|
|
.with_block_longevity(block_longevity)
|
|
.with_network_curve(network_curve)
|
|
.with_authority_index(authority_index);
|
|
|
|
let mut exodus_records = exodus_storage.read_local_storage();
|
|
|
|
let max_authorities = ActiveAuthorities::<T>::decode_len(&network_curve)
|
|
.ok_or(ExodusError::NoActiveAuthorities)?;
|
|
|
|
let threshold = get_byzantium_quorum_threshold(max_authorities);
|
|
|
|
let mut exodus_requests_by_network = BoundedBTreeMap::<
|
|
NetworkIdOf<T>,
|
|
BoundedBTreeMap<ExodusSession, ExodusRequest<T>, T::MaxExodusRequestCount>,
|
|
<T::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks,
|
|
>::new();
|
|
|
|
for (context, request) in ExodusRequests::<T>::iter_prefix((&network_curve,)) {
|
|
match exodus_requests_by_network.get_mut(&context.network_id) {
|
|
Some(session_set) => {
|
|
if let Err(_) = session_set.try_insert(context.session, request) {
|
|
log::warn!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ Max exodus sessions {} reached limit for network #{:?}",
|
|
T::MaxExodusRequestCount::get(),
|
|
context.network_id
|
|
);
|
|
}
|
|
},
|
|
None => {
|
|
let mut new_session_set = BoundedBTreeMap::new();
|
|
if let Ok(_) = new_session_set.try_insert(context.session, request) {
|
|
if let Err(_) = exodus_requests_by_network.try_insert(context.network_id, new_session_set) {
|
|
log::warn!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ Max unique networks limit {} exceeds for network #{:?}",
|
|
<T::NetworkDataHandler as NetworkDataBasicHandler>::MaxNetworks::get(),
|
|
context.network_id
|
|
);
|
|
}
|
|
} else {
|
|
log::warn!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ Max exodus sessions {} reached limit for network #{:?}",
|
|
T::MaxExodusRequestCount::get(),
|
|
context.network_id
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut success_count = 0;
|
|
let mut failed_count = 0;
|
|
|
|
for (network_id, exodus_metadata) in exodus_requests_by_network {
|
|
let base_context = ExodusContext::<NetworkIdOf<T>>::default()
|
|
.with_network_id(network_id);
|
|
|
|
let is_requests_empty = exodus_metadata.is_empty();
|
|
let mut best_candidate = None;
|
|
|
|
for (exodus_session, request) in exodus_metadata {
|
|
let context = base_context.with_exodus_session(exodus_session);
|
|
|
|
if exodus_records.is_exodus_session_banned(&context) {
|
|
continue;
|
|
}
|
|
|
|
if let Some(roast_session) = RoastSessions::<T>::get(context, &authority_index) {
|
|
let state = RoastSessionStates::<T>::get(context, roast_session);
|
|
let participants = GroupCommitmentsConsensus::<T>::get(context, roast_session).participants;
|
|
let commitment_consensus_reached = participants.count_ones::<usize>() >= threshold.div_ceil(2);
|
|
|
|
if state.status == RoastStatus::PartialSignatures
|
|
&& state.nonce_committers.contains(authority_index)
|
|
&& !state.partial_signers.contains(authority_index)
|
|
&& participants.contains(authority_index)
|
|
&& commitment_consensus_reached
|
|
&& !exodus_records.is_waiting_period_for_share(&context, ¤t_block)
|
|
{
|
|
best_candidate = Some((2, context, request, state.status, roast_session));
|
|
break;
|
|
}
|
|
|
|
if (state.status == RoastStatus::GroupCommitments ||
|
|
state.status == RoastStatus::PartialSignatures)
|
|
&& best_candidate.as_ref().map(|data| data.0 < 1).unwrap_or(true)
|
|
&& state.nonce_committers.contains(authority_index)
|
|
&& !state.partial_signers.contains(authority_index)
|
|
&& !participants.contains(authority_index)
|
|
&& !exodus_records.is_waiting_period_for_group(&context, ¤t_block)
|
|
{
|
|
best_candidate = Some((1, context, request, RoastStatus::GroupCommitments, roast_session));
|
|
}
|
|
} else {
|
|
let active_roast_session = request.next_roast_session;
|
|
let state = RoastSessionStates::<T>::get(&context, active_roast_session);
|
|
|
|
if best_candidate.is_none()
|
|
&& state.status == RoastStatus::NonceCommitments
|
|
&& !state.nonce_committers.contains(authority_index)
|
|
&& !state.partial_signers.contains(authority_index)
|
|
&& !state.group_committers.contains(authority_index)
|
|
&& !exodus_records.is_waiting_period_for_nonce(&context, ¤t_block)
|
|
{
|
|
best_candidate = Some((0, context, request, state.status, active_roast_session));
|
|
}
|
|
}
|
|
}
|
|
|
|
let exodus_result = if let Some(metadata) = best_candidate {
|
|
let (_, context, request, roast_status, roast_session) = metadata;
|
|
|
|
match roast_status {
|
|
RoastStatus::NonceCommitments => {
|
|
Self::exodus_run_nonce_commitment(
|
|
&exodus_storage,
|
|
&mut exodus_records,
|
|
secret_share,
|
|
&authority_key,
|
|
context,
|
|
roast_session,
|
|
dkg_index
|
|
)
|
|
},
|
|
RoastStatus::GroupCommitments => {
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
if let Some(message) = request.get_message(&mut message_buffer, context) {
|
|
Self::exodus_run_group_commitment(
|
|
&exodus_storage,
|
|
&mut exodus_records,
|
|
&message,
|
|
&authority_key,
|
|
context,
|
|
roast_session,
|
|
dkg_index,
|
|
)
|
|
} else {
|
|
Err(ExodusError::CouldNotConstructMessage(authority_index))
|
|
}
|
|
},
|
|
RoastStatus::PartialSignatures => {
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
if let Some(message) = request.get_message(&mut message_buffer, context) {
|
|
Self::exodus_run_signature_share(
|
|
&exodus_storage,
|
|
&mut exodus_records,
|
|
&message,
|
|
secret_share,
|
|
&authority_key,
|
|
context,
|
|
roast_session,
|
|
dkg_index
|
|
)
|
|
} else {
|
|
Err(ExodusError::CouldNotConstructMessage(authority_index))
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if is_requests_empty {
|
|
exodus_storage.force_clear_local_storage();
|
|
Ok(ExodusOk::ExodusEmptyRequests(authority_index, network_curve))
|
|
} else {
|
|
Ok(ExodusOk::ExodusWaitingState(authority_index, network_curve))
|
|
}
|
|
};
|
|
|
|
match exodus_result {
|
|
Ok(ok_result) => {
|
|
success_count += 1;
|
|
log::trace!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ EXODUS processing succeeded: #{:?}.",
|
|
ok_result,
|
|
);
|
|
},
|
|
Err(err_result) => {
|
|
failed_count += 1;
|
|
log::trace!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ EXODUS processing failed: {:?}.",
|
|
err_result
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(ExodusOk::ExodusRequestsFinished(
|
|
authority_index,
|
|
network_curve,
|
|
success_count,
|
|
failed_count
|
|
))
|
|
}
|
|
|
|
fn dkg_start(
|
|
network_curve: NetworkCurve,
|
|
current_block: BlockNumberFor<T>,
|
|
dkg_qualification: PendingDkgAuthorities<T>,
|
|
) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>>>> {
|
|
let network_encoded = network_curve.encode();
|
|
let network_lock_key = Self::create_storage_key(b"network-lock-", &network_encoded);
|
|
let lock_until = rt_offchain::Duration::from_millis(MIN_LOCK_GUARD_PERIOD);
|
|
|
|
let mut network_lock = StorageLock::<Time>::with_deadline(&network_lock_key, lock_until);
|
|
let _guard = network_lock
|
|
.try_lock()
|
|
.map_err(|_| ExodusError::OffchainTimeoutPeriod)?;
|
|
|
|
let authorities = QualificationAuthorities::<T>::get(&network_curve);
|
|
|
|
let results = Self::get_local_authorities(authorities.into_iter())
|
|
.map(move |(authority_index, authority_key)| {
|
|
Self::dkg_run_process(
|
|
authority_index,
|
|
authority_key,
|
|
network_curve,
|
|
current_block,
|
|
&dkg_qualification
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
Ok(results)
|
|
}
|
|
|
|
fn dkg_run_process(
|
|
authority_index: AuthIndex,
|
|
authority_key: T::AuthorityId,
|
|
network_curve: NetworkCurve,
|
|
current_block: BlockNumberFor<T>,
|
|
qualification_authorities: &PendingDkgAuthorities<T>
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let block_longevity: BlockNumberFor<T> =
|
|
T::UnsignedLongevity::get().unique_saturated_into();
|
|
|
|
let dkg_storage = DkgStorage::default()
|
|
.with_network_curve(network_curve)
|
|
.with_current_block(current_block)
|
|
.with_authority_index(authority_index)
|
|
.with_block_longevity(block_longevity);
|
|
|
|
match qualification_authorities.get_phase() {
|
|
DkgPhase::Round0 => Self::dkg_run_round0(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round1 => Self::dkg_run_round1(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round2 => Self::dkg_run_round2(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round3 => Self::dkg_run_round3(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round4 => Self::dkg_run_round4(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round5 => Self::dkg_run_round5(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
DkgPhase::Round6 => Self::dkg_run_round6(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
authority_key,
|
|
),
|
|
_ => Self::dkg_run_reset(
|
|
dkg_storage,
|
|
qualification_authorities,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn exodus_run_nonce_commitment(
|
|
exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
exodus_records: &mut ExodusRecords<NetworkIdOf<T>, BlockNumberFor<T>>,
|
|
secret_share: &[u8],
|
|
authority_key: &T::AuthorityId,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
roast_session: RoastSession,
|
|
active_dkg_index: DkgIndex,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let authority_index = exodus_storage.authority_index;
|
|
let network_curve = exodus_storage.network_curve;
|
|
let exodus_context_encoded = exodus_storage.encode();
|
|
|
|
let rng_context = [exodus_context_encoded.as_slice(), secret_share];
|
|
|
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
|
let (nonce, hiding_commitment, binding_commitment) = network_curve
|
|
.generate_nonce_commitment(secret_share, &mut rng)?;
|
|
|
|
exodus_storage.prepare_signed_call_with_context(
|
|
authority_key,
|
|
active_dkg_index,
|
|
exodus_records,
|
|
|package_context| {
|
|
let package = package_context.with_exodus_nonce_commitment(
|
|
exodus_context,
|
|
hiding_commitment,
|
|
binding_commitment,
|
|
)?;
|
|
Ok(ExodusPackage::NonceCommitment(package))
|
|
},
|
|
|exodus_package, signature| {
|
|
let call = Call::<T>::register_nonce_commitment { exodus_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
|exodus_records, next_release_block| {
|
|
exodus_records.insert_exodus_nonce(
|
|
exodus_context,
|
|
next_release_block,
|
|
nonce,
|
|
);
|
|
}
|
|
)?;
|
|
|
|
Ok(ExodusOk::ExodusNonceCommitted(
|
|
exodus_context.session,
|
|
exodus_context.network_id,
|
|
roast_session,
|
|
authority_index,
|
|
network_curve
|
|
))
|
|
}
|
|
|
|
fn exodus_run_group_commitment(
|
|
exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
exodus_records: &mut ExodusRecords<NetworkIdOf<T>, BlockNumberFor<T>>,
|
|
message: &[u8],
|
|
authority_key: &T::AuthorityId,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
roast_session: RoastSession,
|
|
active_dkg_index: DkgIndex,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let authority_index = exodus_storage.authority_index;
|
|
let network_curve = exodus_storage.network_curve;
|
|
|
|
let session_commitments =
|
|
NonceCommitments::<T>::iter_prefix((exodus_context, roast_session))
|
|
.collect::<BTreeMap<AuthIndex, _>>();
|
|
|
|
let nonce_commitments = session_commitments
|
|
.iter()
|
|
.map(|(auth_index, nonces)| {
|
|
(*auth_index, (nonces.hiding.as_ref(), nonces.binding.as_ref()))
|
|
})
|
|
.collect::<BTreeMap<AuthIndex, _>>();
|
|
|
|
let verifying_key = ActiveVerifyingKey::<T>::get(&network_curve);
|
|
|
|
let (
|
|
group_commitment,
|
|
self_binding_factor,
|
|
binding_factors_proof,
|
|
binding_factors_root,
|
|
) = network_curve.generate_group_commitment(
|
|
authority_index,
|
|
&nonce_commitments,
|
|
&verifying_key,
|
|
&message,
|
|
)?;
|
|
|
|
exodus_storage.prepare_signed_call_with_context(
|
|
authority_key,
|
|
active_dkg_index,
|
|
exodus_records,
|
|
|package_context| {
|
|
let package = package_context.with_exodus_group_commitment(
|
|
exodus_context,
|
|
group_commitment,
|
|
binding_factors_root,
|
|
)?;
|
|
|
|
Ok(ExodusPackage::GroupCommitment(package))
|
|
},
|
|
|exodus_package, signature| {
|
|
let call = Call::<T>::register_group_commitment { exodus_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
|exodus_records, next_release_block| {
|
|
exodus_records.insert_exodus_group(
|
|
exodus_context,
|
|
next_release_block,
|
|
self_binding_factor,
|
|
binding_factors_proof,
|
|
)
|
|
}
|
|
)?;
|
|
|
|
Ok(ExodusOk::ExodusGroupCommitted(
|
|
exodus_context.session,
|
|
exodus_context.network_id,
|
|
roast_session,
|
|
authority_index,
|
|
network_curve,
|
|
))
|
|
}
|
|
|
|
fn exodus_run_signature_share(
|
|
exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
exodus_records: &mut ExodusRecords<NetworkIdOf<T>, BlockNumberFor<T>>,
|
|
message: &[u8],
|
|
secret_share: &[u8],
|
|
authority_key: &T::AuthorityId,
|
|
exodus_context: ExodusContext<NetworkIdOf<T>>,
|
|
roast_session: RoastSession,
|
|
active_dkg_index: DkgIndex,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let authority_index = exodus_storage.authority_index;
|
|
let network_curve = exodus_storage.network_curve;
|
|
|
|
let session_commitments =
|
|
NonceCommitments::<T>::iter_prefix((exodus_context, roast_session))
|
|
.collect::<BTreeMap<AuthIndex, _>>();
|
|
|
|
let nonce_commitments = session_commitments
|
|
.iter()
|
|
.map(|(auth_index, nonces)| {
|
|
(*auth_index, (nonces.hiding.as_ref(), nonces.binding.as_ref()))
|
|
})
|
|
.collect::<BTreeMap<AuthIndex, _>>();
|
|
|
|
let signing_package = network_curve.init_signing_package(&nonce_commitments, &message)?;
|
|
let secret_nonce = exodus_records
|
|
.get_secret_nonce(&exodus_context)
|
|
.ok_or(ExodusError::TransactionSubmissionFailed(authority_index))?;
|
|
|
|
let signature_share = network_curve.partial_sign_message(
|
|
&signing_package,
|
|
secret_nonce,
|
|
&secret_share,
|
|
)?;
|
|
|
|
let exodus_group_record = match exodus_records
|
|
.get_group_record(&exodus_context) {
|
|
Some(exodus_group_record) => exodus_group_record,
|
|
None => {
|
|
let verifying_key = ActiveVerifyingKey::<T>::get(&network_curve);
|
|
|
|
let (
|
|
_group_commitment,
|
|
self_binding_factor,
|
|
binding_factors_proof,
|
|
_binding_factors_root,
|
|
) = network_curve.generate_group_commitment(
|
|
authority_index,
|
|
&nonce_commitments,
|
|
&verifying_key,
|
|
&message,
|
|
)?;
|
|
|
|
ExodusGroup { self_binding_factor, binding_factors_proof }
|
|
}
|
|
};
|
|
|
|
exodus_storage.prepare_signed_call_with_context(
|
|
authority_key,
|
|
active_dkg_index,
|
|
exodus_records,
|
|
|package_context| {
|
|
let package = package_context.with_exodus_signature_share(
|
|
exodus_context,
|
|
signature_share,
|
|
exodus_group_record.binding_factors_proof,
|
|
exodus_group_record.self_binding_factor,
|
|
)?;
|
|
|
|
Ok(ExodusPackage::SignatureShare(package))
|
|
},
|
|
|exodus_package, signature| {
|
|
let call = Call::<T>::register_signature_share { exodus_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
|exodus_records, next_release_block| {
|
|
exodus_records.insert_exodus_share(
|
|
exodus_context,
|
|
next_release_block
|
|
);
|
|
}
|
|
)?;
|
|
|
|
Ok(ExodusOk::ExodusSignedPartially(
|
|
exodus_context.session,
|
|
exodus_context.network_id,
|
|
roast_session,
|
|
authority_index,
|
|
network_curve,
|
|
))
|
|
}
|
|
|
|
fn dkg_run_round0(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
let max_signers = QualificationAuthorities::<T>::decode_len(&network_curve)
|
|
.map(|max_signers| max_signers as AuthIndex)
|
|
.ok_or(ExodusError::InvalidMaxSigners(0))?;
|
|
|
|
let min_signers = get_byzantium_quorum_threshold(max_signers);
|
|
|
|
if qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_0,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if Round0Packages::<T>::contains_key(&network_curve, authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_0,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let mut round0_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND0_PREFIX, authority_index);
|
|
|
|
if round0_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_0,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let dkg_storage_encoded = dkg_storage.encode();
|
|
let qualification_state_encoded = qualification_state.encode();
|
|
|
|
let rng_context = [
|
|
dkg_storage_encoded.as_slice(),
|
|
qualification_state_encoded.as_slice(),
|
|
];
|
|
|
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
|
let (secret_package, public_package) = network_curve
|
|
.dkg_part1(authority_index, max_signers, min_signers, &mut rng)?;
|
|
|
|
let package_hash = ExodusHash::from(blake2_256(&public_package));
|
|
|
|
let mut round1_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND1_PREFIX, authority_index);
|
|
|
|
round1_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round1 { secret_package, public_package },
|
|
)?;
|
|
|
|
dkg_storage.try_write_local_storage(&round1_metadata);
|
|
|
|
round0_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round0 { package_hash }
|
|
)?;
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round0_metadata,
|
|
|context| {
|
|
Ok(DkgPackage::Round0(context.with_dkg_round0(package_hash)))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round0_package { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
}
|
|
)?;
|
|
|
|
dkg_storage.get_dkg_result(round0_metadata)
|
|
}
|
|
|
|
fn dkg_run_round1(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if Round1Packages::<T>::get(&network_curve).contains(authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let round0_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND0_PREFIX, authority_index);
|
|
|
|
let Some(DkgStorageEnum::Round0 { package_hash }) =
|
|
round0_metadata.into_inner() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let mut round1_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND1_PREFIX, authority_index);
|
|
|
|
if !round1_metadata.storage_exists() {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if round1_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let (stored_hash_matches, external_hash_matches) = {
|
|
let Some(DkgStorageEnum::Round1 { public_package, .. }) =
|
|
round1_metadata.into_inner() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let stored_package_hash = ExodusHash::from(blake2_256(public_package));
|
|
let external_package_hash = Round0Packages::<T>::get(
|
|
&network_curve,
|
|
&authority_index
|
|
);
|
|
|
|
(
|
|
stored_package_hash.eq(package_hash),
|
|
external_package_hash.eq(package_hash)
|
|
)
|
|
};
|
|
|
|
if !stored_hash_matches || !external_hash_matches {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let public_package_vec = match round1_metadata.into_inner() {
|
|
Some(DkgStorageEnum::Round1 { public_package, .. }) => {
|
|
public_package.to_vec()
|
|
}
|
|
_ => {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_1,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
};
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round1_metadata,
|
|
|context| {
|
|
let package = context.with_dkg_round1(public_package_vec)?;
|
|
Ok(DkgPackage::Round1(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round1_package { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
}
|
|
)?;
|
|
|
|
dkg_storage.get_dkg_result(round1_metadata)
|
|
}
|
|
|
|
fn dkg_run_round2(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_2,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if Round2Packages::<T>::get(&network_curve).contains(authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_2,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let mut round1_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND1_PREFIX, authority_index);
|
|
|
|
if !round1_metadata.storage_exists() {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_2,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let mut round2_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND2_PREFIX, authority_index);
|
|
|
|
if round2_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_2,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let round1_packages =
|
|
Self::gather_incoming_offchain_round1_packages(
|
|
&qualification_state.get_indexes(),
|
|
authority_index,
|
|
network_curve,
|
|
);
|
|
|
|
let round1_secret_package = round1_metadata.try_update_max_signers(
|
|
|round1_secret_package| -> ExodusResult<Vec<u8>> {
|
|
network_curve.dkg_narrow_round_max_signers(
|
|
round1_secret_package,
|
|
round1_packages.len().saturating_add(1) as AuthIndex,
|
|
ROUND_NUMBER_1,
|
|
)
|
|
})?;
|
|
|
|
let (secret_package, public_packages) = network_curve
|
|
.dkg_part2(round1_secret_package, &round1_packages)?;
|
|
|
|
let padded_participants = qualification_state
|
|
.highest_bit::<usize>()
|
|
.map(|bit_pos| bit_pos.saturating_add(1))
|
|
.unwrap_or_default();
|
|
|
|
let bitmask_len = padded_participants.div_ceil(8);
|
|
let blob_len = round2_encrypted_package_size(
|
|
padded_participants,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
let max_package_size = blob_len.saturating_div(padded_participants);
|
|
|
|
let mut bitmask = vec![0u8; bitmask_len];
|
|
let mut blob = vec![0u8; blob_len];
|
|
|
|
Self::ecdh_encrypt_round2_packages(
|
|
&round1_packages,
|
|
&public_packages,
|
|
round1_secret_package,
|
|
&mut bitmask,
|
|
&mut blob,
|
|
max_package_size,
|
|
authority_index,
|
|
qualification_state.get_dkg_index(),
|
|
network_curve,
|
|
);
|
|
|
|
round2_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round2 { secret_package, public_packages },
|
|
)?;
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round2_metadata,
|
|
|context| {
|
|
let encryption_bundle = BundledPackages { bitmask, blob };
|
|
let package = context.with_dkg_round2(encryption_bundle)?;
|
|
Ok(DkgPackage::Round2(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_encrypted_round2_packages { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
}
|
|
)?;
|
|
|
|
dkg_storage.try_write_local_storage(&round1_metadata);
|
|
dkg_storage.get_dkg_result(round2_metadata)
|
|
}
|
|
|
|
fn dkg_run_round3(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_3,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if Complaints::<T>::get(&network_curve).contains_key(&authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_3,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let round1_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND1_PREFIX, authority_index);
|
|
|
|
let Some(DkgStorageEnum::Round1 { secret_package, .. }) =
|
|
round1_metadata.into_inner() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_3,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let mut round3_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND3_PREFIX, authority_index);
|
|
|
|
if round3_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_3,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let round1_packages =
|
|
Self::gather_incoming_offchain_round1_packages(
|
|
&qualification_state.get_indexes(),
|
|
authority_index,
|
|
network_curve,
|
|
);
|
|
|
|
let (indices, decrypted) =
|
|
Self::ecdh_decrypt_round2_packages(
|
|
&qualification_state,
|
|
&round1_packages,
|
|
&secret_package,
|
|
authority_index,
|
|
network_curve,
|
|
);
|
|
|
|
round3_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round3 { decrypted },
|
|
)?;
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round3_metadata,
|
|
|context| {
|
|
let package = context.with_dkg_round3(indices)?;
|
|
Ok(DkgPackage::Round3(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round3_complaints { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
}
|
|
)?;
|
|
|
|
dkg_storage.get_dkg_result(round3_metadata)
|
|
}
|
|
|
|
fn dkg_run_round4(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_4,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
if Justifications::<T>::get(&network_curve).contains_key(&authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_4,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let round2_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND2_PREFIX, authority_index);
|
|
|
|
let Some(DkgStorageEnum::Round2 { public_packages, .. }) =
|
|
round2_metadata.into_inner() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_4,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let mut round4_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND4_PREFIX, authority_index);
|
|
|
|
if round4_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_4,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let padded_participants = qualification_state
|
|
.highest_bit::<usize>()
|
|
.map(|bit_pos| bit_pos.saturating_add(1))
|
|
.unwrap_or_default();
|
|
|
|
let bitmask_len = padded_participants.div_ceil(8);
|
|
let blob_len = round2_package_size(
|
|
padded_participants,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
let round2_package_size = blob_len.saturating_div(padded_participants);
|
|
|
|
let mut bitmask = vec![0u8; bitmask_len];
|
|
let mut blob = vec![0u8; blob_len];
|
|
|
|
for (&accuser_index, bitmap) in Complaints::<T>::get(&network_curve).iter() {
|
|
if !bitmap.contains(authority_index) { continue; }
|
|
if !qualification_state.contains_index(accuser_index) { continue; }
|
|
if authority_index == accuser_index { continue; }
|
|
|
|
let round2_package = match public_packages.get(&accuser_index) {
|
|
Some(round2_package) => round2_package,
|
|
None => continue,
|
|
};
|
|
|
|
Self::prepare_bundled_data(
|
|
&mut bitmask,
|
|
&mut blob,
|
|
&round2_package.encode(),
|
|
round2_package_size,
|
|
accuser_index,
|
|
);
|
|
}
|
|
|
|
round4_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round4,
|
|
)?;
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round4_metadata,
|
|
|context| {
|
|
let justification_bundle = BundledPackages { bitmask, blob };
|
|
let package = context.with_dkg_round4(justification_bundle)?;
|
|
Ok(DkgPackage::Round4(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round4_justifications { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
)?;
|
|
|
|
dkg_storage.get_dkg_result(round4_metadata)
|
|
}
|
|
|
|
fn dkg_run_round5(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_5,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let verification_found_for_self =
|
|
VerifyingKeyConsensus::<T>::iter_values()
|
|
.any(|consensus| consensus.state.participants.contains(authority_index));
|
|
|
|
if verification_found_for_self {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_5,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let dkg_index = qualification_state.get_dkg_index();
|
|
let dkg_based_prefix = Self::create_dkg_based_prefix(dkg_index);
|
|
|
|
let mut round_metadata = dkg_storage.read_local_storage(
|
|
&dkg_based_prefix,
|
|
authority_index,
|
|
);
|
|
|
|
if round_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_5,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let mut round1_packages =
|
|
Self::gather_incoming_offchain_round1_packages(
|
|
&qualification_state.get_indexes(),
|
|
authority_index,
|
|
network_curve,
|
|
);
|
|
|
|
let mut round3_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND3_PREFIX, authority_index);
|
|
|
|
let Some(DkgStorageEnum::Round3 { decrypted, .. }) =
|
|
round3_metadata.into_inner_mut() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_3,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
Self::validate_compliants_and_justifications(
|
|
&qualification_state,
|
|
&mut round1_packages,
|
|
decrypted,
|
|
network_curve,
|
|
authority_index,
|
|
);
|
|
|
|
decrypted.retain(|auth_idx, _| {
|
|
qualification_state.contains_index(*auth_idx)
|
|
&& round1_packages.contains_key(auth_idx)
|
|
});
|
|
|
|
let mut round2_metadata = dkg_storage
|
|
.read_local_storage(DKG_ROUND2_PREFIX, authority_index);
|
|
|
|
let round2_secret_package = round2_metadata.try_update_max_signers(
|
|
|secret_package| -> ExodusResult<Vec<u8>> {
|
|
network_curve.dkg_narrow_round_max_signers(
|
|
secret_package,
|
|
round1_packages.len().saturating_add(1) as AuthIndex,
|
|
ROUND_NUMBER_2,
|
|
)
|
|
})?;
|
|
|
|
let (
|
|
secret_share,
|
|
verifying_key,
|
|
merkle_proof,
|
|
merkle_root,
|
|
) = network_curve.dkg_part3(
|
|
authority_index,
|
|
&round2_secret_package,
|
|
&round1_packages,
|
|
&decrypted,
|
|
)?;
|
|
|
|
round_metadata.update_with_block(
|
|
Some(dkg_storage.get_release_block()),
|
|
DkgStorageEnum::Round5 { secret_share, merkle_proof }
|
|
)?;
|
|
|
|
let padded_participants = qualification_state
|
|
.highest_bit::<usize>()
|
|
.map(|bit_pos| bit_pos.saturating_add(1))
|
|
.unwrap_or_default();
|
|
|
|
let bitmask_len = padded_participants.div_ceil(8);
|
|
|
|
let mut key_members_bitmask = vec![0u8; bitmask_len];
|
|
|
|
for &member_index in round1_packages.keys() {
|
|
let global_index = member_index as usize;
|
|
let byte_index = global_index >> 3usize;
|
|
let bit_index = global_index & 7usize;
|
|
|
|
if let Some(byte) = key_members_bitmask.get_mut(byte_index) {
|
|
*byte |= 1u8 << bit_index;
|
|
}
|
|
}
|
|
|
|
let self_global_index = authority_index as usize;
|
|
if let Some(byte) = key_members_bitmask.get_mut(self_global_index >> 3) {
|
|
*byte |= 1 << (self_global_index & 7);
|
|
}
|
|
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round_metadata,
|
|
|context| {
|
|
let package = context.with_dkg_round5(
|
|
verifying_key,
|
|
key_members_bitmask,
|
|
merkle_root,
|
|
)?;
|
|
Ok(DkgPackage::Round5(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round5_verifying_package { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
)?;
|
|
|
|
dkg_storage.try_write_local_storage(&round2_metadata);
|
|
dkg_storage.get_dkg_result(round_metadata)
|
|
}
|
|
|
|
fn dkg_run_round6(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
authority_key: T::AuthorityId,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
if !qualification_state.contains_index(authority_index) {
|
|
return Ok(ExodusOk::DkgNotPartOfRound(
|
|
ROUND_NUMBER_6,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let dkg_index = qualification_state.get_dkg_index();
|
|
|
|
if VerifyingSharesParticipants::<T>::get(&network_curve, &dkg_index)
|
|
.contains(authority_index) {
|
|
return Ok(ExodusOk::DkgRoundAlreadyPassed(
|
|
ROUND_NUMBER_6,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let dkg_based_prefix = Self::create_dkg_based_prefix(
|
|
qualification_state.get_dkg_index(),
|
|
);
|
|
|
|
let mut round_metadata = dkg_storage.read_local_storage(
|
|
&dkg_based_prefix,
|
|
authority_index,
|
|
);
|
|
|
|
if round_metadata.is_waiting_period() {
|
|
return Ok(ExodusOk::DkgRoundWaitingPeriod(
|
|
ROUND_NUMBER_6,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
}
|
|
|
|
let Some(DkgStorageEnum::Round5 { secret_share, merkle_proof }) =
|
|
round_metadata.into_inner_mut() else {
|
|
return Ok(ExodusOk::DkgRoundNothingStored(
|
|
ROUND_NUMBER_6,
|
|
authority_index,
|
|
network_curve,
|
|
));
|
|
};
|
|
|
|
let verifying_share = network_curve
|
|
.dkg_derive_verifying_share(secret_share)?;
|
|
|
|
let owned_merkle_proof = core::mem::take(merkle_proof);
|
|
let current_dkg_index = qualification_state.get_dkg_index();
|
|
|
|
dkg_storage.prepare_signed_call_with_context(
|
|
&authority_key,
|
|
current_dkg_index,
|
|
&mut round_metadata,
|
|
|context| {
|
|
let package = context.with_dkg_round6(verifying_share, owned_merkle_proof)?;
|
|
Ok(DkgPackage::Round6(package))
|
|
},
|
|
|dkg_package, signature| {
|
|
let call = Call::<T>::register_round6_public_share_package { dkg_package, signature };
|
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
|
.map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
|
|
},
|
|
)?;
|
|
|
|
dkg_storage.get_dkg_result(round_metadata)
|
|
}
|
|
|
|
fn dkg_run_reset(
|
|
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
) -> ExodusResult<ExodusOk<NetworkCurve, NetworkIdOf<T>>> {
|
|
let network_curve = dkg_storage.network_curve;
|
|
let authority_index = dkg_storage.authority_index;
|
|
|
|
let qualification_dkg_index = qualification_state.get_dkg_index();
|
|
let active_dkg_index = ActiveDkgAuthorities::<T>::get(&network_curve)
|
|
.get_dkg_index();
|
|
|
|
let dkg_indexes_to_purge = if active_dkg_index < qualification_dkg_index {
|
|
// some failed qualification rounds accumulated
|
|
active_dkg_index.saturating_add(1)..=qualification_dkg_index
|
|
} else {
|
|
// qualification passed succesfully
|
|
let previous_active_dkg_index = active_dkg_index
|
|
.checked_sub(1)
|
|
.ok_or(ExodusError::UnexpectedRound)?;
|
|
|
|
previous_active_dkg_index..=previous_active_dkg_index
|
|
};
|
|
|
|
let mut removed_count = 0;
|
|
for dkg_index in dkg_indexes_to_purge {
|
|
let dkg_prefixed_index = Self::create_dkg_based_prefix(dkg_index);
|
|
|
|
let storage_exists = {
|
|
dkg_storage
|
|
.read_local_storage(&dkg_prefixed_index, authority_index)
|
|
.storage_exists()
|
|
};
|
|
|
|
if storage_exists {
|
|
[DKG_ROUND0_PREFIX, DKG_ROUND1_PREFIX, DKG_ROUND2_PREFIX, DKG_ROUND3_PREFIX, DKG_ROUND4_PREFIX]
|
|
.iter()
|
|
.for_each(|prefix| dkg_storage.clear_local_storage(prefix));
|
|
|
|
Self::clear_incoming_offchain_packages(
|
|
network_curve,
|
|
&[ROUND_NUMBER_1, ROUND_NUMBER_2, ROUND_NUMBER_4],
|
|
T::MaxAuthorities::get() as AuthIndex,
|
|
);
|
|
|
|
dkg_storage.clear_local_storage(&dkg_prefixed_index);
|
|
removed_count += 1;
|
|
|
|
log::trace!(
|
|
target: PALLET_LOG_TARGET,
|
|
"🗺️ EXODUS processing succeeded: #{:?}.",
|
|
ExodusOk::DkgLocalStoragePurged::<NetworkCurve, NetworkIdOf<T>>(
|
|
authority_index,
|
|
network_curve,
|
|
dkg_index,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
Ok(ExodusOk::DkgPreviousPurged(authority_index, network_curve, removed_count))
|
|
}
|
|
|
|
pub fn validate_compliants_and_justifications(
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
round1_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
|
|
round2_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
|
|
network_curve: NetworkCurve,
|
|
authority_index: AuthIndex,
|
|
) {
|
|
let round2_package_size = round2_package_size(
|
|
1,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
let mut kicked = sp_std::collections::btree_set::BTreeSet::new();
|
|
let mut all_justifications = sp_std::collections::btree_map::BTreeMap::new();
|
|
|
|
for (&accuser_index, accused_bitmap) in Complaints::<T>::get(network_curve).iter() {
|
|
if !qualification_state.contains_index(accuser_index) {
|
|
round1_packages.remove(&accuser_index);
|
|
round2_packages.remove(&accuser_index);
|
|
continue;
|
|
}
|
|
|
|
for accused_index in accused_bitmap.iter::<AuthIndex>() {
|
|
if !qualification_state.contains_index(accused_index) { continue; }
|
|
if kicked.contains(&accused_index) { continue; }
|
|
if accused_index == authority_index { continue; }
|
|
|
|
let justifications = all_justifications.entry(accused_index)
|
|
.or_insert_with(|| {
|
|
let key = Self::create_offchain_dkg_key(network_curve, accused_index, ROUND_NUMBER_4);
|
|
StorageValueRef::persistent(&key).get::<BundledPackages>().ok().flatten()
|
|
});
|
|
|
|
let maybe_justification = justifications.as_ref().and_then(|bundle| {
|
|
Self::locate_package_bounds(bundle, round2_package_size, accuser_index)
|
|
.and_then(|(start, end)| {
|
|
bundle.blob.get(start..end).and_then(|raw_slice| {
|
|
Vec::<u8>::decode(&mut &raw_slice[..]).ok()
|
|
})
|
|
})
|
|
});
|
|
|
|
let maybe_round1_package = round1_packages.get(&accused_index);
|
|
|
|
let (round1_package, round2_package) =
|
|
match (maybe_round1_package, maybe_justification) {
|
|
(Some(round1_package), Some(round2_package)) => {
|
|
(round1_package, round2_package)
|
|
},
|
|
_ => {
|
|
round1_packages.remove(&accused_index);
|
|
round2_packages.remove(&accused_index);
|
|
kicked.insert(accused_index);
|
|
continue;
|
|
}
|
|
};
|
|
|
|
match network_curve.dkg_verify_private_package(
|
|
accuser_index,
|
|
round1_package,
|
|
&round2_package,
|
|
) {
|
|
Ok(_) => {
|
|
if accuser_index != authority_index { continue; }
|
|
round2_packages.insert(
|
|
accused_index,
|
|
round2_package,
|
|
);
|
|
},
|
|
Err(_) => {
|
|
round1_packages.remove(&accused_index);
|
|
round2_packages.remove(&accused_index);
|
|
kicked.insert(accused_index);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn locate_package_bounds(
|
|
bundle: &BundledPackages,
|
|
single_bytes_len: usize,
|
|
authority_index: AuthIndex,
|
|
) -> Option<(usize, usize)> {
|
|
let global_index = authority_index as usize;
|
|
let byte_index = global_index >> 3usize;
|
|
let bit_index = global_index & 7usize;
|
|
let bit_mask = 1u8 << bit_index;
|
|
|
|
let bundle_bitmask_by_index = bundle.bitmask.get(byte_index)?;
|
|
if (bundle_bitmask_by_index & bit_mask) == 0 {
|
|
return None;
|
|
}
|
|
|
|
let start_offset = global_index * single_bytes_len;
|
|
let end_offset = start_offset + single_bytes_len;
|
|
|
|
if end_offset > bundle.blob.len() {
|
|
return None;
|
|
}
|
|
|
|
Some((start_offset, end_offset))
|
|
}
|
|
|
|
pub fn gather_incoming_offchain_round1_packages(
|
|
qualified_authorities: &ParticipantsBitmap<T>,
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
) -> BTreeMap<AuthIndex, Vec<u8>> {
|
|
qualified_authorities.iter::<AuthIndex>()
|
|
.filter(|&index| index != authority_index)
|
|
.filter_map(|index| {
|
|
let key = Self::create_offchain_dkg_key(
|
|
network_curve,
|
|
index,
|
|
ROUND_NUMBER_1,
|
|
);
|
|
|
|
let round1_package = StorageValueRef::persistent(&key)
|
|
.get::<Vec<u8>>()
|
|
.ok()??;
|
|
|
|
Some((index, round1_package))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn clear_incoming_offchain_packages(
|
|
network_curve: NetworkCurve,
|
|
rounds: &[u8],
|
|
max_signers: AuthIndex,
|
|
) {
|
|
(0..max_signers).for_each(|index| rounds.iter().for_each(|&round| {
|
|
let key = Self::create_offchain_dkg_key(network_curve, index, round);
|
|
StorageValueRef::persistent(&key).clear();
|
|
}));
|
|
}
|
|
|
|
fn ecdh_decrypt_round2_packages(
|
|
qualification_state: &PendingDkgAuthorities<T>,
|
|
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
|
|
round1_secret_package: &Vec<u8>,
|
|
authority_index: AuthIndex,
|
|
network_curve: NetworkCurve,
|
|
) -> (Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>) {
|
|
let dkg_index = qualification_state.get_dkg_index();
|
|
|
|
let bitmask_len = qualification_state
|
|
.highest_bit::<usize>()
|
|
.map(|bit_pos| {
|
|
bit_pos.saturating_add(1).div_ceil(8)
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
let encryption_size = round2_encrypted_package_size(
|
|
1,
|
|
network_curve.scalar_bytes_len(),
|
|
network_curve.header_bytes_len(),
|
|
);
|
|
|
|
round1_packages
|
|
.iter()
|
|
.filter(|(&sender_index, _)| sender_index != authority_index)
|
|
.map(|(&sender_index, round1_sender_package)| {
|
|
let key = Self::create_offchain_dkg_key(network_curve, sender_index, ROUND_NUMBER_2);
|
|
|
|
let decryption_result = StorageValueRef::persistent(&key)
|
|
.get::<BundledPackages>()
|
|
.ok()
|
|
.flatten()
|
|
.and_then(|bundle| {
|
|
Self::locate_package_bounds(&bundle, encryption_size, authority_index)
|
|
.and_then(|(start, end)| {
|
|
bundle.blob.get(start..end).and_then(|raw_slice| {
|
|
EncryptionData::decode(&mut &raw_slice[..]).ok()
|
|
})
|
|
})
|
|
})
|
|
.ok_or(sender_index)
|
|
.and_then(|encrypted_round2_package: EncryptionData<T>| {
|
|
let info = network_curve.ecdh_prepare_additional_info(
|
|
sender_index,
|
|
authority_index,
|
|
dkg_index,
|
|
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
|
);
|
|
|
|
network_curve
|
|
.ecdh_get_cipher_from_keys(
|
|
round1_sender_package,
|
|
round1_secret_package,
|
|
&info,
|
|
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
|
)
|
|
.and_then(|cipher| {
|
|
network_curve.ecdh_decrypt_package(
|
|
&cipher,
|
|
&encrypted_round2_package,
|
|
&info,
|
|
)
|
|
})
|
|
.and_then(|decrypted| {
|
|
network_curve.dkg_verify_private_package(
|
|
authority_index,
|
|
round1_sender_package,
|
|
&decrypted,
|
|
).map(|_| decrypted)
|
|
})
|
|
.map_err(|_| sender_index)
|
|
})
|
|
.map(|decrypted_package| (sender_index, decrypted_package));
|
|
|
|
decryption_result
|
|
})
|
|
.fold(
|
|
(vec![0u8; bitmask_len], BTreeMap::new()),
|
|
|(mut accused_indexes, mut decrypted), decryption_result| {
|
|
match decryption_result {
|
|
Ok((sender_index, package)) => { decrypted.insert(sender_index, package); },
|
|
Err(sender_index) => {
|
|
let global_index = sender_index as usize;
|
|
let byte_index = global_index >> 3usize;
|
|
|
|
if let Some(byte) = accused_indexes.get_mut(byte_index) {
|
|
let bit_index = global_index & 7usize;
|
|
*byte |= 1u8 << bit_index;
|
|
}
|
|
},
|
|
}
|
|
(accused_indexes, decrypted)
|
|
}
|
|
)
|
|
}
|
|
|
|
fn ecdh_encrypt_round2_packages(
|
|
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
|
|
round2_packages: &BTreeMap<AuthIndex, Vec<u8>>,
|
|
round1_secret_package: &[u8],
|
|
bitmask: &mut Vec<u8>,
|
|
blob: &mut Vec<u8>,
|
|
max_size: usize,
|
|
authority_index: AuthIndex,
|
|
dkg_index: DkgIndex,
|
|
network_curve: NetworkCurve,
|
|
) {
|
|
let authority_index_bytes = authority_index.to_le_bytes();
|
|
let dkg_index_bytes = dkg_index.to_le_bytes();
|
|
let network_curve_bytes = [network_curve as u8];
|
|
|
|
let rng_context = [
|
|
round1_secret_package,
|
|
authority_index_bytes.as_slice(),
|
|
dkg_index_bytes.as_slice(),
|
|
network_curve_bytes.as_slice(),
|
|
];
|
|
let mut rng = Self::get_offchain_rng(&rng_context);
|
|
|
|
for (&receiver_index, round2_package) in round2_packages.iter() {
|
|
if receiver_index == authority_index { continue; }
|
|
|
|
let round1_receiver_package = match round1_packages
|
|
.get(&receiver_index) {
|
|
Some(package) => package,
|
|
None => continue,
|
|
};
|
|
|
|
let info = network_curve.ecdh_prepare_additional_info(
|
|
authority_index,
|
|
receiver_index,
|
|
dkg_index,
|
|
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
|
);
|
|
|
|
let encrypted_data: EncryptionData<T> = match network_curve
|
|
.ecdh_get_cipher_from_keys(
|
|
&round1_receiver_package,
|
|
&round1_secret_package,
|
|
&info,
|
|
sp_std::marker::PhantomData::<EncryptionData<T>>,
|
|
)
|
|
.and_then(|cipher| network_curve.ecdh_encrypt_package(
|
|
&cipher,
|
|
&info,
|
|
&round2_package,
|
|
&mut rng,
|
|
)) {
|
|
Ok(encrypted_data) => encrypted_data,
|
|
Err(_) => break,
|
|
};
|
|
|
|
Self::prepare_bundled_data(
|
|
bitmask,
|
|
blob,
|
|
&encrypted_data.encode(),
|
|
max_size,
|
|
receiver_index,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn prepare_bundled_data(
|
|
bitmask: &mut Vec<u8>,
|
|
blob: &mut Vec<u8>,
|
|
encoded_bytes: &[u8],
|
|
max_size: usize,
|
|
authority_index: AuthIndex,
|
|
) {
|
|
let global_index = authority_index as usize;
|
|
let byte_index = global_index >> 3usize;
|
|
let bit_index = global_index & 7usize;
|
|
|
|
if let Some(byte) = bitmask.get_mut(byte_index) {
|
|
if encoded_bytes.len() > max_size { return; }
|
|
|
|
let start_offset = global_index.saturating_mul(max_size);
|
|
let end_offset = start_offset.saturating_add(max_size);
|
|
|
|
if end_offset > blob.len() { return; }
|
|
|
|
let target_slice = &mut blob[start_offset..end_offset];
|
|
target_slice[..encoded_bytes.len()].copy_from_slice(&encoded_bytes);
|
|
|
|
*byte |= 1 << bit_index;
|
|
}
|
|
}
|
|
|
|
fn get_offchain_rng(context: &[&[u8]]) -> ChaCha20Rng {
|
|
let seed = sp_io::offchain::random_seed();
|
|
let seeded_context = sp_std::iter::once(&seed[..])
|
|
.chain(context.iter().copied());
|
|
|
|
let hashed_seed = cumulative_hash::<SubstrateBlake2Hasher, _>(seeded_context);
|
|
ChaCha20Rng::from_seed(hashed_seed.to_fixed_bytes())
|
|
}
|
|
|
|
fn create_storage_key(first: &[u8], second: &[u8]) -> Vec<u8> {
|
|
let mut key = DB_STORAGE_PREFIX.to_vec();
|
|
key.extend(first);
|
|
key.extend(second);
|
|
key
|
|
}
|
|
|
|
fn create_dkg_based_prefix(dkg_index: DkgIndex) -> Vec<u8> {
|
|
let dkg_bytes = dkg_index.to_le_bytes();
|
|
let total_size = DKG_SECRET_PREFIX.len() + dkg_bytes.len();
|
|
|
|
let mut prefix = Vec::with_capacity(total_size);
|
|
|
|
prefix.extend_from_slice(DKG_SECRET_PREFIX);
|
|
prefix.extend_from_slice(&dkg_bytes);
|
|
|
|
prefix
|
|
}
|
|
|
|
fn create_offchain_dkg_key(
|
|
network_curve: NetworkCurve,
|
|
authority_index: AuthIndex,
|
|
round_number: u8,
|
|
) -> Vec<u8> {
|
|
let mut index_key = DB_STORAGE_PREFIX.to_vec();
|
|
index_key.extend_from_slice(OFFCHAIN_INDEX_KEY);
|
|
index_key.extend_from_slice(&network_curve.encode());
|
|
index_key.extend_from_slice(&authority_index.encode());
|
|
index_key.extend_from_slice(&round_number.encode());
|
|
|
|
index_key
|
|
}
|
|
|
|
fn get_local_authorities(
|
|
authorities: impl Iterator<Item = T::AuthorityId>,
|
|
) -> impl Iterator<Item = (AuthIndex, T::AuthorityId)> {
|
|
let mut local_authorities = T::AuthorityId::all();
|
|
local_authorities.sort();
|
|
|
|
authorities
|
|
.enumerate()
|
|
.filter_map(move |(index, authority)| {
|
|
local_authorities
|
|
.binary_search(&authority)
|
|
.ok()
|
|
.map(|_| (index as AuthIndex, authority))
|
|
})
|
|
}
|
|
|
|
fn start_dkg_qualification(
|
|
authorities: Vec::<T::AuthorityId>,
|
|
network_curves: impl Iterator<Item = NetworkCurve>,
|
|
) {
|
|
let authorities_len = authorities.len();
|
|
let mut next_authorities =
|
|
WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
|
authorities,
|
|
Some("exodus reached maximum number of authorities"),
|
|
);
|
|
|
|
let block_number = T::BlockNumberProvider::current_block_number();
|
|
let mut peekable_curves = network_curves.peekable();
|
|
|
|
while let Some(network_curve) = peekable_curves.next() {
|
|
let active_authorities = ActiveAuthorities::<T>::get(&network_curve);
|
|
let keys_changed = active_authorities != next_authorities;
|
|
|
|
if keys_changed {
|
|
let converted_reset_period: BlockNumberFor<T> =
|
|
BLOCKS_FOR_DKG_RESET.unique_saturated_into();
|
|
|
|
let release_block = converted_reset_period
|
|
.saturating_add(block_number);
|
|
|
|
let authorities_to_insert = if peekable_curves.peek().is_none() {
|
|
sp_std::mem::take(&mut next_authorities)
|
|
} else {
|
|
next_authorities.clone()
|
|
};
|
|
|
|
QualificationAuthorities::<T>::insert(
|
|
&network_curve,
|
|
authorities_to_insert,
|
|
);
|
|
|
|
QualificationDkgState::<T>::mutate(&network_curve, |state| {
|
|
state.new_dkg(authorities_len);
|
|
state.set_release_block(release_block);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
pub fn api_get_evm_rotation(
|
|
dkg_index_u64: u64,
|
|
network_type: NetworkType,
|
|
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
|
|
let dkg_index: DkgIndex = dkg_index_u64.unique_saturated_into();
|
|
let (context, rotation) =
|
|
ExodusSignedRotations::<T>::get(dkg_index, network_type)?;
|
|
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
let message = rotation.get_message(&mut message_buffer, context)?;
|
|
|
|
let nonce_bytes = rotation.nonce[1..].to_vec();
|
|
let signature_bytes = rotation.signature.to_vec();
|
|
|
|
Some((message.to_vec(), nonce_bytes, signature_bytes))
|
|
}
|
|
|
|
pub fn api_get_evm_bridge(
|
|
dkg_index_u64: u64,
|
|
network_id_u64: u64,
|
|
session_u64: u64,
|
|
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
|
|
let dkg_index: DkgIndex = dkg_index_u64.unique_saturated_into();
|
|
let session: ExodusSession = session_u64.unique_saturated_into();
|
|
let network_id: NetworkIdOf<T> = network_id_u64.unique_saturated_into();
|
|
|
|
let context = ExodusContext::<NetworkIdOf<T>>::new(network_id, session);
|
|
let bridge = ExodusSignedBridges::<T>::get(dkg_index, context)?;
|
|
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
let message = bridge.get_message(&mut message_buffer, context)?;
|
|
|
|
let nonce_bytes = bridge.nonce[1..].to_vec();
|
|
let signature_bytes = bridge.signature.to_vec();
|
|
|
|
Some((message.to_vec(), nonce_bytes, signature_bytes))
|
|
}
|
|
|
|
pub fn api_get_evm_governance(
|
|
dkg_index_u64: u64,
|
|
network_id_u64: u64,
|
|
session_u64: u64,
|
|
) -> ghost_exodus_runtime_api::PreparedEvmMessage {
|
|
let dkg_index: DkgIndex = dkg_index_u64.unique_saturated_into();
|
|
let session: ExodusSession = session_u64.unique_saturated_into();
|
|
let network_id: NetworkIdOf<T> = network_id_u64.unique_saturated_into();
|
|
|
|
let context = ExodusContext::<NetworkIdOf<T>>::new(network_id, session);
|
|
let governance = ExodusSignedGovernance::<T>::get(dkg_index, context)?;
|
|
|
|
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
|
|
let message = governance.get_message(&mut message_buffer, context)?;
|
|
|
|
let nonce_bytes = governance.nonce[1..].to_vec();
|
|
let signature_bytes = governance.signature.to_vec();
|
|
|
|
Some((message.to_vec(), nonce_bytes, signature_bytes))
|
|
}
|
|
|
|
pub fn api_pending_exodus_request_count() -> u32 {
|
|
ExodusRequests::<T>::count()
|
|
}
|
|
|
|
pub fn api_is_qualification_ongoing(network_curve: NetworkCurve) -> bool {
|
|
QualificationDkgState::<T>::get(network_curve).is_dkg_pending()
|
|
}
|
|
}
|
|
|
|
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
|
type Public = T::AuthorityId;
|
|
}
|
|
|
|
impl<T: Config> BlockNumberProvider for Pallet<T> {
|
|
type BlockNumber = BlockNumberFor<T>;
|
|
fn current_block_number() -> Self::BlockNumber {
|
|
T::BlockNumberProvider::current_block_number()
|
|
}
|
|
}
|
|
|
|
impl<T: Config> EvmGovernanceRegistrar<NetworkIdOf<T>, EvmAddress, GovernanceAction> for Pallet<T> {
|
|
fn register_evm_governance_action(
|
|
network_id: NetworkIdOf<T>,
|
|
target_address: EvmAddress,
|
|
governance_action: GovernanceAction,
|
|
) -> DispatchResult {
|
|
Self::do_register_evm_governance(
|
|
network_id,
|
|
target_address,
|
|
governance_action,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|
type Key = T::AuthorityId;
|
|
|
|
fn on_genesis_session<'a, I: 'a>(validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
|
{
|
|
use strum::IntoEnumIterator;
|
|
|
|
let mut authorities = validators.map(|x| x.1).collect::<Vec<_>>();
|
|
authorities.sort();
|
|
|
|
Self::start_dkg_qualification(authorities, NetworkCurve::iter());
|
|
}
|
|
|
|
fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, queued_validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
|
{
|
|
let mut authorities = queued_validators
|
|
.map(|(_, authority_id)| authority_id)
|
|
.collect::<Vec<_>>();
|
|
|
|
authorities.sort();
|
|
|
|
let network_curves = T::NetworkDataHandler::iter_curves();
|
|
Self::start_dkg_qualification(authorities, network_curves);
|
|
}
|
|
|
|
fn on_before_session_ending() {}
|
|
|
|
fn on_disabled(_validator_index: u32) {
|
|
// NOTE: what should we do if validator is disabled?
|
|
// It seems like nothing... Because disabling is not permanent
|
|
// but temporary at max for one epoch. But it's not final
|
|
// solution.
|
|
}
|
|
}
|