ghost-node/pallets/exodus/src/lib.rs

4916 lines
176 KiB
Rust

// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::{
pallet_prelude::*,
traits::{
Currency, DisabledValidators, ValidatorSet, ValidatorSetWithIdentification,
OneSessionHandler, WithdrawReasons, ExistenceRequirement,
},
};
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_threshold, SubstrateBlake2Hasher,
hash_chain::sequential_hash,
networks::{NetworkData, NetworkCurve, NetworkType},
bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap},
};
#[cfg(test)]
use ghost_traits::exodus::IdentifierConverter;
use ghost_traits::{
hashing::GhostHasher,
bounded_bitmap::{
BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader,
BoundedBitmapIterable,
},
exodus::{
MerkleTreeBuilder, DistributedKeyGeneration, EllipticCurveDiffieHellman,
FlexibleRoundOptimizedSchnorrThresholdSignature,
},
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 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 = 420;
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_len(len: usize) -> usize {
if len <= 127 { 1 }
else if len <= 16383 { 2 }
else { 3 }
}
const fn signature_bytes_len(
element_bytes_len: usize,
scalar_bytes_len: usize,
)-> usize {
let signature_bytes_len = element_bytes_len.saturating_add(scalar_bytes_len);
let signature_vec_prefix = postcard_varint_len(signature_bytes_len);
signature_bytes_len.saturating_add(signature_vec_prefix)
}
const fn round1_package_size(
commitments_count: usize,
element_bytes_len: usize,
scalar_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_len(commitments_count);
signature_bytes_len(element_bytes_len, scalar_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)
.saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize);
let ciphertext_vec_prefix = postcard_varint_len(ciphertext_len);
ciphertext_len.saturating_add(ciphertext_vec_prefix)
}
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 = postcard_varint_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_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>;
type QualifyingState<T> = PendingDkgAuthorities<ParticipantsBitmap<T>, BlockNumberFor<T>>;
type ActivatedState<T> = ReadyDkgAuthorities<ParticipantsBitmap<T>>;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type NetworkIdOf<T> =
<<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;
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 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_threshold(max_authorities);
round1_package_size(
min_authorities as usize,
ELEMENT_MAX_BYTES as usize,
SCALAR_MAX_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_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 {
signature_bytes_len(
ELEMENT_MAX_BYTES as usize,
SCALAR_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: Currency<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>;
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,
},
Round4JustificatPackage {
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_session: ExodusSession,
network_curve: NetworkCurve,
roast_session: RoastSession,
},
GroupCommitmentRegistered {
authority_index: AuthIndex,
exodus_session: ExodusSession,
network_curve: NetworkCurve,
roast_session: RoastSession,
},
PartialSignatureRegistered {
authority_index: AuthIndex,
exodus_session: ExodusSession,
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,
TooManyPackages,
WrongNetworkType,
InvalidMerkleProof,
NetworkDoesNotExist,
NoActiveAuthorities,
InvalidSignatureShare,
VerifyingShareNotFound,
DkgAuthoritiesInProgress,
PackagesAlreadyRegistered,
DkgAuthoritiesNotInitialized,
Round0PackageInvalidProof,
Round0PackageAlreadyRegistered,
Round1PackageBadHash,
Round1PackageInvalidProof,
Round1PackageAlreadyRegistered,
Round1PackageInvalidLength,
Round2PackagesInvalidProof,
Round2PackagesAlreadyRegistered,
Round2PackagesWrongCoefficients,
Round3PackageInvalidProof,
Round3PackagesAlreadyRegistere,
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)]
pub(super) type CurrentExodus<T: Config> =
StorageValue<_, ExodusSession, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn exodus_requests)]
pub(super) type ExodusRequests<T: Config> = StorageDoubleMap<
_,
Twox64Concat, NetworkCurve,
Twox64Concat, ExodusSession,
ExodusRequest<NetworkIdOf<T>, BalanceOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn roast_states)]
pub(super) type RoastSessionStates<T: Config> = StorageDoubleMap<
_,
Twox64Concat, ExodusSession,
Twox64Concat, RoastSession,
RoastSessionState<ParticipantsBitmap<T>>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn nonce_commitments)]
pub(super) type NonceCommitments<T: Config> = StorageDoubleMap<
_,
Twox64Concat, (ExodusSession, RoastSession),
Twox64Concat, AuthIndex,
ExodusSeparatedNonce,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn group_committers)]
pub(super) type GroupCommitters<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, ExodusSession>,
NMapKey<Twox64Concat, RoastSession>,
NMapKey<Blake2_256, 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, ExodusSession,
Twox64Concat, RoastSession,
ConsensusState<ParticipantsBitmap<T>>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn signature_scalars)]
pub(super) type SignatureScalars<T: Config> = StorageDoubleMap<
_,
Twox64Concat, ExodusSession,
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, ExodusSession,
Twox64Concat, AuthIndex,
RoastSession,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn exodus_signatured_rotations)]
pub(super) type ExodusSignedRotations<T: Config> = StorageDoubleMap<
_,
Twox64Concat, (ExodusSession, NetworkType),
Twox64Concat, DkgIndex,
ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn exodus_signed_bridges)]
pub(super) type ExodusSignedBridges<T: Config> = StorageDoubleMap<
_,
Twox64Concat, (ExodusSession, NetworkIdOf<T>),
Twox64Concat, DkgIndex,
ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn exodus_signed_governance)]
pub(super) type ExodusSignedGovernance<T: Config> = StorageDoubleMap<
_,
Twox64Concat, (ExodusSession, NetworkIdOf<T>),
Twox64Concat, DkgIndex,
ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<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 exodus_activity)]
pub(super) type ExodusActivity<T: Config> = StorageMap<
_,
Twox64Concat, AuthIndex,
BlockNumberFor<T>,
ValueQuery,
>;
#[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,
ConsensusState<ParticipantsBitmap<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,
ActivatedState<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,
QualifyingState<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 in T::NetworkDataHandler::iter_types_by_curve(&network_curve) {
let rotation_message_size = match network_type {
NetworkType::Evm => {
let dummy_public_address = EvmBytes32::repeat_byte(69u8);
let dummy_exodus_request =
ExodusRequest::<NetworkIdOf<T>, BalanceOf<T>>::evm_rotation(
0, dummy_public_address, 0,
);
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
dummy_exodus_request
.get_message(&mut message_buffer)
.map(|message| message.len() as u32)
.unwrap_or(u32::MAX)
},
_ => 0,
};
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
let DkgPackage::Round1(round1) = dkg_package else {
return Err(Error::<T>::Round1PackageInvalidProof.into());
};
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 estimated_commitment_count =
network_curve.dkg_verify_proof_of_knowledge(
authority_index,
&round1.package,
).map_err(|_| Error::<T>::Round1PackageInvalidProof)?;
let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_first_phase(), Error::<T>::DkgWrongRound);
state.count_ones::<AuthIndex>().checked_sub(1)
.map(|needed_commitment_count| {
estimated_commitment_count
.eq(&needed_commitment_count)
.then(|| ())
})
.ok_or(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, 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)?;
Self::register_latest_activity(&dkg_package)?;
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
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>::Round3PackagesAlreadyRegistere,
);
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
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>::Round4JustificatPackage {
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
let DkgPackage::Round5(round5) = dkg_package else {
return Err(Error::<T>::Round5PackageInvalidProof.into());
};
let metadata_hashes = [
SubstrateBlake2Hasher::hash(round5.merkle_root.as_ref()),
SubstrateBlake2Hasher::hash(round5.verifying_key.as_ref()),
];
let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
let state = QualificationDkgState::<T>::get(&network_curve);
ensure!(state.is_fifth_phase(), Error::<T>::DkgWrongRound);
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, |state| {
let state_participants_count = state.participants.count_ones::<AuthIndex>();
let latest_participants_count = latest_participants.count_ones::<AuthIndex>();
if state_participants_count < latest_participants_count {
*state = ConsensusState::new(
round5.verifying_key,
latest_participants,
round5.merkle_root,
);
}
});
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, 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)?;
Self::register_latest_activity(&dkg_package)?;
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 consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
network_curve.verify_merkle_proof(
&round6.verifying_share,
&round6.merkle_proof,
consensus.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, 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 (mut exodus_request, roast_session) =
Self::validate_exodus_nonce_commitment_size_and_get_metadata(
&exodus_package,
&signature,
).map_err(|_| Error::<T>::ExodusNoncePackageInvalidProof)?;
Self::register_latest_activity(&exodus_package)?;
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_threshold(max_participants))
.ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;
let exodus_session = package.session;
ensure!(
!RoastSessions::<T>::contains_key(&exodus_session, authority_index),
Error::<T>::ExodusAlreadyInRoastSession,
);
let mut roast_state = RoastSessionStates::<T>::get(&exodus_session, 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_session, exodus_request);
}
RoastSessions::<T>::insert(&exodus_session, &authority_index, roast_session);
RoastSessionStates::<T>::insert(&exodus_session, &roast_session, roast_state);
NonceCommitments::<T>::insert(
(exodus_session, roast_session), authority_index,
ExodusSeparatedNonce::new(
package.hiding_commitment,
package.binding_commitment,
),
);
Self::deposit_event(Event::<T>::NonceCommitmentRegistered {
authority_index,
exodus_session,
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, 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 roast_session =
Self::validate_exodus_group_commitment_size_and_get_metadata(
&exodus_package,
&signature,
).map_err(|_| Error::<T>::ExodusGroupPackageInvalidProof)?;
Self::register_latest_activity(&exodus_package)?;
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_threshold(max_authorities);
let exodus_session = package.session;
ensure!(
ExodusRequests::<T>::contains_key(&network_curve, &exodus_session),
Error::<T>::ExodusRequestNotFound
);
let group_commitment_key =(
exodus_session,
roast_session,
package.binding_factors_root,
&package.group_commitment
);
let mut group_participants = GroupCommitters::<T>::try_mutate(
&group_commitment_key,
|participants| -> Result<ParticipantsBitmap<T>, DispatchError> {
ensure!(
!participants.contains(authority_index),
Error::<T>::ExodusGroupAlreadyRegistered,
);
if participants.is_empty() {
*participants = ParticipantsBitmap::<T>::empty(max_authorities);
}
participants.insert(authority_index);
Ok(participants.clone())
})?;
let consensus_reached = group_participants
.count_ones::<usize>()
.ge(&threshold.div_ceil(2));
let mut roast_state =
RoastSessionStates::<T>::get(&exodus_session, 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,
);
group_participants.insert(authority_index);
roast_state.group_committers.insert(authority_index);
let group_participants_count = group_participants.count_ones::<usize>();
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_session, &roast_session, roast_state);
GroupCommitters::<T>::insert(&group_commitment_key, group_participants.clone());
GroupCommitmentsConsensus::<T>::mutate(&exodus_session, &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_session,
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, 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 (roast_session, active_dkg_index) =
Self::validate_exodus_signature_share_size_and_get_metadata(
&exodus_package,
&signature,
).map_err(|_| Error::<T>::ExodusSharePackageInvalidProof)?;
Self::register_latest_activity(&exodus_package)?;
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_threshold(max_participants))
.ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;
let exodus_session = package.session;
let commitment_consensus =
GroupCommitmentsConsensus::<T>::get(&exodus_session, &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_session)
.ok_or(Error::<T>::ExodusRequestNotFound)?;
ensure!(
match exodus_request.r#type {
ExodusRequestType::EvmRotation { .. } => {
!ExodusSignedRotations::<T>::contains_key(
(exodus_session, NetworkType::Evm), active_dkg_index,
)
},
ExodusRequestType::UtxoRotation => {
!ExodusSignedRotations::<T>::contains_key(
(exodus_session, NetworkType::Utxo), active_dkg_index,
)
}
ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => {
!ExodusSignedBridges::<T>::contains_key(
(exodus_session, network_id), active_dkg_index,
)
}
ExodusRequestType::EvmGovernance { network_id, .. } => {
!ExodusSignedGovernance::<T>::contains_key(
(exodus_session, network_id), active_dkg_index,
)
}
},
Error::<T>::ExodusSignedMessageAlreadyExists,
);
let mut roast_state =
RoastSessionStates::<T>::get(&exodus_session, 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_session, 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)
.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_session, 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.clone(),
).ok_or(Error::<T>::TooManyEntries)?;
Self::deposit_event(Event::<T>::PartialSignatureRegistered {
authority_index,
exodus_session,
network_curve,
roast_session
});
RoastSessions::<T>::remove(&exodus_session, authority_index);
if roast_state.partial_signers.count_ones::<usize>() < threshold {
RoastSessionStates::<T>::insert(&exodus_session, &roast_session, roast_state);
SignatureScalars::<T>::insert(
exodus_session, roast_session,
bounded_signature_scalar,
);
return Ok(());
}
RoastSessionStates::<T>::remove(&exodus_session, &roast_session);
SignatureScalars::<T>::remove(&exodus_session, &roast_session);
GroupCommitmentsConsensus::<T>::remove(&exodus_session, &roast_session);
GroupCommitters::<T>::remove((
&exodus_session, &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_session);
match exodus_request.r#type {
ExodusRequestType::EvmRotation { .. } => {
ExodusSignedRotations::<T>::insert(
(exodus_session, NetworkType::Evm), active_dkg_index,
exodus_signed_message,
);
},
ExodusRequestType::UtxoRotation => {
ExodusSignedRotations::<T>::insert(
(exodus_session, NetworkType::Utxo), active_dkg_index,
exodus_signed_message,
);
}
ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => {
ExodusSignedBridges::<T>::insert(
(exodus_session, network_id), active_dkg_index,
exodus_signed_message,
);
}
ExodusRequestType::EvmGovernance { network_id, .. } => {
ExodusSignedGovernance::<T>::insert(
(exodus_session, network_id), active_dkg_index,
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 _imbalance = T::Currency::withdraw(
&who,
amount,
WithdrawReasons::TRANSFER,
ExistenceRequirement::AllowDeath,
)?;
Self::do_register_evm_bridge_out_exodus(
network_id,
amount,
bounty,
receiver,
)?;
Self::deposit_event(Event::<T>::EvmBridgeOutRegistered {
who,
network_id,
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() {
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_threshold(qualification_len);
weight.saturating_accrue(T::DbWeight::get().reads(1));
let maybe_cursor = DkgMaybeCursor::<T>::get(&network_curve);
let converted_round_period: BlockNumberFor<T> =
T::DkgRoundPeriod::get().unique_saturated_into();
let release_block = dkg_qualification
.get_block()
.saturating_add(converted_round_period);
if maybe_cursor.is_none() && current_block < release_block { return weight; }
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, &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>();
if threshold_reached || dkg_qualification.is_dkg_vacant() {
dkg_qualification.next_phase();
} else {
dkg_qualification.restart_dkg();
}
dkg_qualification.set_block(current_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 or failed initialization 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 network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round0_package_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_round1_package { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round1_package_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_encrypted_round2_packages { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round2_package_size(
dkg_package,
signature,
)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_round3_complaints { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round3_complaints_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_round4_justifications { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round4_justifications_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_round5_verifying_package { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round5_package_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_round6_public_share_package { dkg_package, signature } => {
let network_curve = dkg_package.get_network_curve();
let authority_index = dkg_package.get_authority_index();
Self::validate_round6_package_size(dkg_package, signature)?;
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides((network_curve, authority_index))
.propagate(true)
.build()
},
Call::register_nonce_commitment { exodus_package, signature } => {
let network_curve = exodus_package.get_network_curve();
let authority_index = exodus_package.get_authority_index();
let exodus_session = exodus_package.get_exodus_session();
let (_, roast_session) =
Self::validate_exodus_nonce_commitment_size_and_get_metadata(
exodus_package,
signature,
)?;
let nonce_metadata = (network_curve, authority_index, exodus_session, roast_session);
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides(nonce_metadata)
.propagate(true)
.build()
},
Call::register_group_commitment { exodus_package, signature } => {
let network_curve = exodus_package.get_network_curve();
let authority_index = exodus_package.get_authority_index();
let exodus_session = exodus_package.get_exodus_session();
let roast_session =
Self::validate_exodus_group_commitment_size_and_get_metadata(
exodus_package,
signature,
)?;
let group_metadata = (network_curve, authority_index, exodus_session, roast_session);
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides(group_metadata)
.propagate(true)
.build()
}
Call::register_signature_share { exodus_package, signature } => {
let network_curve = exodus_package.get_network_curve();
let authority_index = exodus_package.get_authority_index();
let exodus_session = exodus_package.get_exodus_session();
let (roast_session, _) =
Self::validate_exodus_signature_share_size_and_get_metadata(
exodus_package,
signature,
)?;
let signature_share_metadata = (network_curve, authority_index, exodus_session, roast_session);
ValidTransaction::with_tag_prefix("GhostExodus")
.priority(T::UnsignedPriority::get())
.longevity(T::UnsignedLongevity::get())
.and_provides(signature_share_metadata)
.propagate(true)
.build()
}
_ => InvalidTransaction::Call.into(),
}
}
}
}
impl<T: Config> Pallet<T> {
fn do_register_evm_bridge_out_exodus(
network_id: NetworkIdOf<T>,
amount: BalanceOf<T>,
bounty: Perbill,
receiver: EvmAddress,
) -> 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 = CurrentExodus::<T>::get();
let request = ExodusRequest::evm_bridge_out(
exodus_session,
network_id,
amount,
bounty,
receiver
);
ExodusRequests::<T>::insert(&network.curve, &exodus_session, request);
CurrentExodus::<T>::put(exodus_session.saturating_add(1));
Ok(())
}
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(_) => DkgMaybeCursor::<T>::remove(&network_curve), // never should happen
}
weight.saturating_accrue(T::DbWeight::get().writes(1));
false
},
None => {
on_complete_fn(&mut weight);
true
}
};
(weight, fully_cleared)
}
fn prepare_round0_storage(
network_curve: NetworkCurve,
dkg_qualification: &mut QualifyingState<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 QualifyingState<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 QualifyingState<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 QualifyingState<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 QualifyingState<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 QualifyingState<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 mut failed_authorities = ParticipantsBitmap::<T>::empty_from(
&qualified_indexes,
);
let mut active_authorities = ParticipantsBitmap::<T>::empty_from(
&qualified_indexes,
);
let mut complaints_matrix: BTreeMap<AuthIndex, ParticipantsBitmap<T>> =
BTreeMap::new();
complaints_bitmap.iter().for_each(|(&accuser_index, offenders_bitmap)| {
offenders_bitmap.iter().for_each(|offended_index| {
complaints_matrix
.entry(offended_index)
.or_insert_with(|| {
ParticipantsBitmap::<T>::empty_from(&qualified_indexes)
})
.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();
}
if let Some(accusers_bitmap) = complaints_matrix.get(&offended_index) {
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,
dkg_qualification: &mut QualifyingState<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,
|_, weight| {
let Some(prev_dkg_index) = dkg_index.checked_sub(1) else {
return None;
};
weight.saturating_accrue(T::DbWeight::get().writes(1));
VerifyingSharesParticipants::<T>::insert(
&network_curve,
&prev_dkg_index,
default_participants,
);
None
},
|weight| {
weight.saturating_accrue(T::DbWeight::get().reads(1));
let state = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);
dkg_qualification.intersect_indexes(&state.participants);
})
}
fn prepare_round7_storage(
network_curve: NetworkCurve,
min_threshold: AuthIndex,
dkg_qualification: &mut QualifyingState<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.element_bytes.as_slice();
weight.saturating_accrue(T::DbWeight::get().reads(2));
let mut exodus_session = CurrentExodus::<T>::get();
for network_type in T::NetworkDataHandler::iter_types_by_curve(&network_curve) {
let exodus_request = match network_type {
NetworkType::Evm => {
if verifying_key_slice.len() < 33 {
dkg_qualification.nullify_indexes();
return;
}
let parity_byte = verifying_key_slice[0];
let parity = if parity_byte % 2 == 0 { 0u8 } else { 1u8 };
let public_key = EvmBytes32::from_slice(&verifying_key_slice[1..33]);
ExodusRequest::evm_rotation(exodus_session, public_key, parity)
}
_ => {
continue;
}
};
weight.saturating_accrue(T::DbWeight::get().writes(1));
ExodusRequests::<T>::insert(&network_curve, &exodus_session, exodus_request);
if let None = dkg_qualification
.try_push_rotation_session(exodus_session, network_type) {
break;
}
exodus_session = exodus_session.saturating_add(1);
}
CurrentExodus::<T>::put(exodus_session);
weight.saturating_accrue(T::DbWeight::get().writes(1));
dkg_qualification.set_verifying_key(consensus.element_bytes);
})
}
fn prepare_finalization_storage(
network_curve: NetworkCurve,
dkg_qualification: &mut QualifyingState<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_rotation_sessions()
.all(|rotation_session| {
let session = rotation_session.exodus_session;
let r#type = rotation_session.network_type;
weight.saturating_accrue(T::DbWeight::get().reads(1));
if ExodusRequests::<T>::contains_key(&network_curve, &session) {
return false
};
weight.saturating_accrue(T::DbWeight::get().reads(1));
ExodusSignedRotations::<T>::contains_key((session, r#type), dkg_index)
});
if all_rotations_have_signatures || is_initial_dkg_round {
let verifying_key = dkg_qualification.get_verifying_key();
let activated_state: ActivatedState<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 register_latest_activity(
package_ref: &impl PackageMetadata<NetworkCurve>,
) -> DispatchResult {
let authority_index = package_ref.get_authority_index();
let current_block = T::BlockNumberProvider::current_block_number();
ExodusActivity::<T>::try_mutate(&authority_index,
|stored_block| -> DispatchResult {
ensure!(
*stored_block < current_block,
Error::<T>::PackagesAlreadyRegistered,
);
*stored_block = current_block;
Ok(())
})?;
Ok(())
}
fn validate_round0_package_size(
dkg_package: & DkgPackage<NetworkCurve, 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();
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.contains_index(authority_index) {
return Err(InvalidTransaction::BadSigner);
}
let authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, 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();
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.contains_index(authority_index) {
return Err(InvalidTransaction::BadSigner);
}
let authorities = QualificationAuthorities::<T>::get(&network_curve);
let expected_count = get_byzantium_threshold(authorities.len());
let expected_package_len = round1_package_size(
expected_count,
network_curve.element_bytes_len(),
network_curve.scalar_bytes_len(),
network_curve.header_bytes_len(),
);
if round1.package.len() != expected_package_len {
return Err(InvalidTransaction::BadProof);
}
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, 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();
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.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 authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, 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();
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.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 authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, 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();
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.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 authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, 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();
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.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 authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, 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();
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.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 authority = QualificationAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, T>,
signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
) -> Result<(ExodusRequest<NetworkIdOf<T>, BalanceOf<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();
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);
}
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);
}
let authority = ActiveAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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, &package.session)
.ok_or(InvalidTransaction::BadProof)?;
let roast_session =
match RoastSessions::<T>::get(&package.session, &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, 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();
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);
}
let authority = ActiveAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.ok_or(InvalidTransaction::BadSigner)?;
let encoded_package = exodus_package.encode();
if !authority.verify(&encoded_package, signature) {
return Err(InvalidTransaction::BadProof);
}
RoastSessions::<T>::get(&package.session, &authority_index)
.ok_or(InvalidTransaction::BadProof)
}
fn validate_exodus_signature_share_size_and_get_metadata<'a>(
exodus_package: &'a ExodusPackage<NetworkCurve, 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();
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);
}
let authority = ActiveAuthorities::<T>::get(&network_curve)
.get(authority_index as usize)
.cloned()
.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(&package.session, &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>>>> {
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>>>> {
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: &ActivatedState<T>
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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_threshold(max_authorities);
let mut exodus_requests = ExodusRequests::<T>::iter_prefix(&network_curve)
.collect::<Vec<(ExodusSession, ExodusRequest<_, _>)>>();
exodus_requests.sort_by_key(|&(session, _)| session);
let is_requests_empty = exodus_requests.len() == 0;
let mut share_candidate = None;
let mut group_candidate = None;
let mut nonce_candidate = None;
for (session, request) in exodus_requests.iter() {
if exodus_records.is_exodus_session_banned(session) {
continue;
}
if let Some(roast_session) = RoastSessions::<T>::get(session, &authority_index) {
let state = RoastSessionStates::<T>::get(session, roast_session);
let participants = GroupCommitmentsConsensus::<T>::get(&session, 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(session, &current_block)
{
share_candidate = Some((*session, request, state.status, roast_session));
break;
}
if group_candidate.is_none()
&& (
(state.status == RoastStatus::GroupCommitments
|| state.status == RoastStatus::PartialSignatures)
)
&& state.nonce_committers.contains(authority_index)
&& !state.partial_signers.contains(authority_index)
&& !participants.contains(authority_index)
&& !exodus_records.is_waiting_period_for_group(session, &current_block)
{
group_candidate = Some((*session, request, RoastStatus::GroupCommitments, roast_session));
break;
}
} else {
let active_roast_session = request.next_roast_session;
let state = RoastSessionStates::<T>::get(session, active_roast_session);
if nonce_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(session, &current_block)
{
nonce_candidate = Some((*session, request, state.status, active_roast_session));
break;
}
}
}
let target_session = share_candidate
.or(group_candidate)
.or(nonce_candidate);
if let Some((session, request, roast_status, roast_session)) = target_session {
match roast_status {
RoastStatus::NonceCommitments => {
Self::exodus_run_nonce_commitment(
&exodus_storage,
&mut exodus_records,
secret_share,
&authority_key,
session,
)?;
Ok(ExodusOk::ExodusNonceCommitted(
session,
roast_session,
authority_index,
network_curve,
))
},
RoastStatus::GroupCommitments => {
let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
if let Some(message) = request.get_message(&mut message_buffer) {
let group_commitment_result =
Self::exodus_run_group_commitment(
&exodus_storage,
&mut exodus_records,
&message,
&authority_key,
session,
roast_session,
);
group_commitment_result
.and_then(|_| {
Ok(ExodusOk::ExodusGroupCommitted(
session,
roast_session,
authority_index,
network_curve,
))
})
.or_else(|err| {
exodus_records.exclude_exodus_session(session);
exodus_storage.try_write_local_storage(&exodus_records);
Err(err)
})
} 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) {
let signature_share_result =
Self::exodus_run_signature_share(
&exodus_storage,
&mut exodus_records,
&message,
secret_share,
&authority_key,
session,
roast_session,
);
signature_share_result
.and_then(|_| {
Ok(ExodusOk::ExodusSignedPartially(
session,
roast_session,
authority_index,
network_curve,
))
})
.or_else(|err| {
exodus_records.exclude_exodus_session(session);
exodus_storage.try_write_local_storage(&exodus_records);
Err(err)
})
} 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))
}
}
}
fn dkg_start(
network_curve: NetworkCurve,
current_block: BlockNumberFor<T>,
dkg_qualification: QualifyingState<T>,
) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve>>>> {
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: &QualifyingState<T>
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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<BlockNumberFor<T>>,
secret_share: &[u8],
authority_key: &T::AuthorityId,
exodus_session: ExodusSession,
) -> ExodusResult<()> {
let authority_index = exodus_storage.authority_index;
let network_curve = exodus_storage.network_curve;
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
let mut rng = Self::get_offchain_rng();
#[cfg(any(test, feature = "runtime-benchmarks"))]
let mut rng = Self::get_offchain_rng(authority_index);
let (nonce, hiding_commitment, binding_commitment) = network_curve
.generate_nonce_commitment(secret_share, &mut rng)?;
exodus_storage.prepare_signed_call_with_context(
authority_key,
exodus_records,
|context| {
let package = context.with_exodus_nonce_commitment(
exodus_session,
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_session,
next_release_block,
nonce,
);
}
)
}
fn exodus_run_group_commitment(
exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
exodus_records: &mut ExodusRecords<BlockNumberFor<T>>,
message: &[u8],
authority_key: &T::AuthorityId,
exodus_session: ExodusSession,
roast_session: RoastSession,
) -> ExodusResult<()> {
let authority_index = exodus_storage.authority_index;
let network_curve = exodus_storage.network_curve;
let session_commitments =
NonceCommitments::<T>::iter_prefix((exodus_session, 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,
exodus_records,
|context| {
let package = context.with_exodus_group_commitment(
exodus_session,
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_session,
next_release_block,
self_binding_factor,
binding_factors_proof,
)
}
)
}
fn exodus_run_signature_share(
exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
exodus_records: &mut ExodusRecords<BlockNumberFor<T>>,
message: &[u8],
secret_share: &[u8],
authority_key: &T::AuthorityId,
exodus_session: ExodusSession,
roast_session: RoastSession,
) -> ExodusResult<()> {
let authority_index = exodus_storage.authority_index;
let network_curve = exodus_storage.network_curve;
let session_commitments =
NonceCommitments::<T>::iter_prefix((exodus_session, 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_session)
.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_session) {
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,
exodus_records,
|context| {
let package = context.with_exodus_signature_share(
exodus_session,
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_session, next_release_block);
}
)
}
fn dkg_run_round0(
dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
qualification_state: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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_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,
));
}
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
let mut rng = Self::get_offchain_rng();
#[cfg(any(test, feature = "runtime-benchmarks"))]
let mut rng = Self::get_offchain_rng(authority_index);
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 }
)?;
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&mut round0_metadata,
|context: PackageContext<NetworkCurve>| {
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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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 Some(DkgStorageEnum::Round1 { public_package, .. }) =
round1_metadata.into_inner() else {
return Ok(ExodusOk::DkgRoundNothingStored(
ROUND_NUMBER_1,
authority_index,
network_curve,
));
};
let public_package_clone = public_package.clone();
let stored_package_hash = ExodusHash::from(blake2_256(&public_package_clone));
let external_package_hash = Round0Packages::<T>::get(&network_curve, &authority_index);
let stored_hash_matches = stored_package_hash.eq(package_hash);
let external_hash_matches = external_package_hash.eq(package_hash);
if !stored_hash_matches || !external_hash_matches {
return Ok(ExodusOk::DkgRoundNothingStored(
ROUND_NUMBER_1,
authority_index,
network_curve,
));
}
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&mut round1_metadata,
|context| {
let package = context.with_dkg_round1(public_package_clone)?;
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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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 },
)?;
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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 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 round2_packages =
Self::gather_incoming_offchain_packages(
network_curve,
authority_index,
ROUND_NUMBER_2,
&qualification_state.get_indexes(),
blob_len.saturating_div(padded_participants),
|mut raw_slice| EncryptionData::decode(&mut raw_slice).ok(),
);
let (indices, decrypted) =
Self::ecdh_decrypt_round2_packages(
&round1_packages,
&secret_package,
&round2_packages,
authority_index,
bitmask_len,
qualification_state.get_dkg_index(),
network_curve,
);
round3_metadata.update_with_block(
Some(dkg_storage.get_release_block()),
DkgStorageEnum::Round3 { decrypted },
)?;
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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,
)?;
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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(|state| 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 }
)?;
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&mut round_metadata,
|context| {
let package = context.with_dkg_round5(verifying_key, 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: &QualifyingState<T>,
authority_key: T::AuthorityId,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
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);
dkg_storage.prepare_signed_call_with_context(
&authority_key,
&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: &QualifyingState<T>,
) -> ExodusResult<ExodusOk<NetworkCurve>> {
let network_curve = dkg_storage.network_curve;
let authority_index = dkg_storage.authority_index;
match qualification_state.get_dkg_index().checked_sub(1) {
Some(dkg_index) => {
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);
}
Ok(ExodusOk::DkgPreviousPurged(authority_index, network_curve))
},
None => Err(ExodusError::UnexpectedRound),
}
}
pub fn validate_compliants_and_justifications(
qualification_state: &QualifyingState<T>,
round1_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
round2_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
network_curve: NetworkCurve,
authority_index: AuthIndex,
) {
let padded_participants = qualification_state
.highest_bit::<usize>()
.map(|bit_pos| bit_pos.saturating_add(1))
.unwrap_or_default();
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);
for (&accuser_index, accused_bitmap) in Complaints::<T>::get(network_curve).iter() {
if !qualification_state.contains_index(accuser_index) { continue; }
if accuser_index == authority_index { continue; }
let raw_round2_packages: BTreeMap<AuthIndex, Vec<u8>> =
Self::gather_incoming_offchain_packages(
network_curve,
accuser_index,
ROUND_NUMBER_4,
&accused_bitmap,
round2_package_size,
|mut raw_slice| Vec::<u8>::decode(&mut raw_slice).ok(),
);
let mut kicked = sp_std::collections::btree_set::BTreeSet::new();
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 maybe_round1_package = round1_packages.get(&accused_index);
let maybe_round2_package = raw_round2_packages.get(&accused_index);
let (round1_package, round2_package) =
match (maybe_round1_package, maybe_round2_package) {
(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.clone(),
);
},
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()
}
pub fn gather_incoming_offchain_packages<R, E, F>(
network_curve: NetworkCurve,
authority_index: AuthIndex,
round: u8,
participants: &ParticipantsBitmap<T>,
single_bytes_len: usize,
decode_fn: F
) -> E
where
E: FromIterator<(AuthIndex, R)>,
F: Fn(&[u8]) -> Option<R>,
{
participants
.iter::<AuthIndex>()
.filter(|&index| index != authority_index)
.filter_map(|index| {
let key = Self::create_offchain_dkg_key(network_curve, index, round);
let bundle = StorageValueRef::persistent(&key)
.get::<BundledPackages>()
.ok()??;
let (start_offset, end_offset) =
Self::locate_package_bounds(
&bundle,
single_bytes_len,
authority_index,
)?;
let raw_encrypted_slice = &bundle.blob[start_offset..end_offset];
let decoded_data = decode_fn(raw_encrypted_slice)?;
Some((index, decoded_data))
})
.collect::<E>()
}
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(
round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
round1_secret_package: &Vec<u8>,
round2_packages: &BTreeMap<AuthIndex, EncryptionData<T>>,
authority_index: AuthIndex,
bitvec_len: usize,
dkg_index: DkgIndex,
network_curve: NetworkCurve,
) -> (Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>) {
round2_packages
.iter()
.filter(|(&sender_index, _)| sender_index != authority_index)
.filter_map(|(sender_index, encrypted_round2_packages)| {
let round1_sender_package = round1_packages.get(&sender_index)?;
let info = network_curve.ecdh_prepare_additional_info(
*sender_index,
authority_index,
dkg_index,
sp_std::marker::PhantomData::<EncryptionData<T>>,
);
let decryption_result = 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_packages,
&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));
Some(decryption_result)
})
.fold(
(vec![0u8; bitvec_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,
) {
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
let mut rng = Self::get_offchain_rng();
#[cfg(any(test, feature = "runtime-benchmarks"))]
let mut rng = Self::get_offchain_rng(authority_index);
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;
}
}
#[cfg(any(test, feature = "runtime-benchmarks"))]
fn get_offchain_rng(authority_index: AuthIndex) -> ChaCha20Rng {
let mut seed = [0u8; 32];
let unique_id = (authority_index as u64).saturating_add(1);
let index_bytes = unique_id.to_le_bytes();
for (i, &byte) in index_bytes.iter().enumerate() {
if i < seed.len() {
seed[i] ^= byte;
}
}
rand_chacha::ChaCha20Rng::from_seed(seed)
}
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
fn get_offchain_rng() -> ChaCha20Rng {
let seed = sp_io::offchain::random_seed();
ChaCha20Rng::from_seed(seed)
}
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
.into_iter()
.enumerate()
.filter_map(move |(index, authority)| {
local_authorities
.binary_search(&authority)
.ok()
.map(|location| (index as AuthIndex, local_authorities[location].clone()))
})
}
fn start_dkg_qualification(
authorities: Vec::<T::AuthorityId>,
network_curves: impl Iterator<Item = NetworkCurve>,
) {
let authorities_len = authorities.len();
let next_authorities =
WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
authorities,
Some("exodus reached maximum number of authorities"),
);
let block_number = T::BlockNumberProvider::current_block_number();
for network_curve in network_curves {
let active_authorities = ActiveAuthorities::<T>::get(&network_curve);
let keys_changed = active_authorities != next_authorities;
if keys_changed {
QualificationAuthorities::<T>::insert(&network_curve, next_authorities.clone());
QualificationDkgState::<T>::mutate(&network_curve, |state| {
state.new_dkg(authorities_len);
state.set_block(block_number);
});
}
}
}
}
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> 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.
}
}