4916 lines
176 KiB
Rust
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, ¤t_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, ¤t_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, ¤t_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.
|
|
}
|
|
}
|