From e28294a12a0386eb67739efe3146bf9ae30b648f Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Fri, 9 Jun 2023 14:44:24 -0600 Subject: [PATCH 1/2] Modify proposers to only come from validator set --- dkg-runtime-primitives/src/lib.rs | 23 +- dkg-runtime-primitives/src/traits.rs | 27 +- pallets/dkg-metadata/src/lib.rs | 120 ++++++++- pallets/dkg-metadata/src/mock.rs | 8 + pallets/dkg-proposal-handler/src/mock.rs | 21 +- pallets/dkg-proposals/src/benchmarking.rs | 40 +-- pallets/dkg-proposals/src/lib.rs | 301 ++++++++++------------ pallets/dkg-proposals/src/mock.rs | 69 +++-- pallets/dkg-proposals/src/tests.rs | 116 +++------ pallets/dkg-proposals/src/weights.rs | 62 ----- parachain/runtime/rococo/src/lib.rs | 20 +- standalone/runtime/src/lib.rs | 26 +- 12 files changed, 434 insertions(+), 399 deletions(-) diff --git a/dkg-runtime-primitives/src/lib.rs b/dkg-runtime-primitives/src/lib.rs index b31105a65..c786f57e5 100644 --- a/dkg-runtime-primitives/src/lib.rs +++ b/dkg-runtime-primitives/src/lib.rs @@ -107,7 +107,7 @@ pub type MaxReporters = CustomU32Getter<100>; /// Max size for signatures pub type MaxSignatureLength = CustomU32Getter<512>; -/// Max size for signatures +/// Max size for keys pub type MaxKeyLength = CustomU32Getter<512>; /// Max votes to store onchain @@ -157,6 +157,25 @@ pub struct AggregatedMisbehaviourReports< pub signatures: BoundedVec, MaxReporters>, } +#[derive(Eq, PartialEq, Clone, Encode, Decode, Debug, TypeInfo, codec::MaxEncodedLen)] +pub struct AggregatedProposerSetVotes< + DKGId: AsRef<[u8]>, + MaxSignatureLength: Get + Debug + Clone + TypeInfo, + MaxVoters: Get + Debug + Clone + TypeInfo, + VoteLength: Get + Debug + Clone + TypeInfo, +> { + /// The round id the proposer vote is valid for. + pub session_id: u64, + /// The proposer + pub proposer: DKGId, + /// The encoded vote + pub vote: BoundedVec, + /// A list of voters + pub voters: BoundedVec, + /// A list of signed encoded votes + pub signatures: BoundedVec, MaxVoters>, +} + impl> Default for OffchainSignedProposals { fn default() -> Self { Self { proposals: Default::default() } @@ -318,5 +337,7 @@ sp_api::decl_runtime_apis! { fn refresh_nonce() -> u32; /// Returns true if we should execute an new keygen. fn should_execute_new_keygen() -> bool; + /// Returns true if we should execute an proposer set voting protocol. + fn should_submit_proposer_set_vote() -> bool; } } diff --git a/dkg-runtime-primitives/src/traits.rs b/dkg-runtime-primitives/src/traits.rs index 29b92ce11..665d89b69 100644 --- a/dkg-runtime-primitives/src/traits.rs +++ b/dkg-runtime-primitives/src/traits.rs @@ -13,30 +13,41 @@ // limitations under the License. // use frame_support::dispatch::DispatchResultWithPostInfo; +use sp_runtime::BoundedVec; use sp_std::vec::Vec; pub trait OnAuthoritySetChangeHandler { - fn on_authority_set_changed( - authority_accounts: Vec, - authority_ids: Vec, - ); + fn on_authority_set_changed(authority_accounts: &[AccountId], authority_ids: &[AuthorityId]); } impl OnAuthoritySetChangeHandler for () { - fn on_authority_set_changed( - _authority_accounts: Vec, - _authority_ids: Vec, - ) { + fn on_authority_set_changed(_authority_accounts: &[AccountId], _authority_ids: &[AuthorityId]) { } } +/// A trait for fetching the current and pravious DKG Public Key. pub trait GetDKGPublicKey { fn dkg_key() -> Vec; fn previous_dkg_key() -> Vec; } +/// A trait for fetching the current proposer set. +pub trait GetProposerSet { + fn get_previous_proposer_set() -> Vec; + fn get_previous_external_proposer_accounts() -> Vec<(AccountId, BoundedVec)>; +} + +impl GetProposerSet for () { + fn get_previous_proposer_set() -> Vec { + Vec::new() + } + fn get_previous_external_proposer_accounts() -> Vec<(A, BoundedVec)> { + Vec::new() + } +} + /// A trait for when the DKG Public Key get changed. /// /// This is used to notify the runtime that the DKG signer has changed. diff --git a/pallets/dkg-metadata/src/lib.rs b/pallets/dkg-metadata/src/lib.rs index ff28b0913..ffd187796 100644 --- a/pallets/dkg-metadata/src/lib.rs +++ b/pallets/dkg-metadata/src/lib.rs @@ -102,11 +102,11 @@ use dkg_runtime_primitives::{ OFFCHAIN_PUBLIC_KEY_SIG, OFFCHAIN_PUBLIC_KEY_SIG_LOCK, SUBMIT_GENESIS_KEYS_AT, SUBMIT_KEYS_AT, }, - traits::{GetDKGPublicKey, OnAuthoritySetChangeHandler}, + traits::{GetDKGPublicKey, GetProposerSet, OnAuthoritySetChangeHandler}, utils::{ecdsa, to_slice_33, verify_signer_from_set_ecdsa}, - AggregatedMisbehaviourReports, AggregatedPublicKeys, AuthorityIndex, AuthoritySet, - ConsensusLog, MisbehaviourType, ProposalHandlerTrait, RefreshProposal, RefreshProposalSigned, - DKG_ENGINE_ID, + AggregatedMisbehaviourReports, AggregatedProposerSetVotes, AggregatedPublicKeys, + AuthorityIndex, AuthoritySet, ConsensusLog, MisbehaviourType, ProposalHandlerTrait, + RefreshProposal, RefreshProposalSigned, DKG_ENGINE_ID, }; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -212,12 +212,17 @@ pub mod pallet { Self::DKGId, >; + /// Utility trait for handling DKG public key changes type OnDKGPublicKeyChangeHandler: OnDKGPublicKeyChangeHandler< dkg_runtime_primitives::AuthoritySetId, >; + /// Proposer handler trait type ProposalHandler: ProposalHandlerTrait; + /// Trait for fetching proposer set data + type ProposerSetView: GetProposerSet; + /// A type that gives allows the pallet access to the session progress type NextSessionRotation: EstimateNextSessionRotation; @@ -294,6 +299,19 @@ pub mod pallet { + PartialOrd + Ord; + /// Length of encoded proposer vote + #[pallet::constant] + type VoteLength: Get + + Default + + TypeInfo + + MaxEncodedLen + + Debug + + Clone + + Eq + + PartialEq + + PartialOrd + + Ord; + /// The origin which may forcibly reset parameters or otherwise alter /// privileged attributes. type ForceOrigin: EnsureOrigin; @@ -429,6 +447,11 @@ pub mod pallet { #[pallet::getter(fn should_execute_new_keygen)] pub type ShouldExecuteNewKeygen = StorageValue<_, bool, ValueQuery>; + /// Should we submit a vote for the new DKG governor if we are a proposer + #[pallet::storage] + #[pallet::getter(fn should_submit_proposer_vote)] + pub type ShouldSubmitProposerVote = StorageValue<_, bool, ValueQuery>; + /// Holds public key for next session #[pallet::storage] #[pallet::getter(fn next_dkg_public_key)] @@ -680,6 +703,8 @@ pub mod pallet { reporters: Vec, offender: T::DKGId, }, + /// Proposer votes submitted + ProposerSetVotesSubmitted { voters: Vec, signatures: Vec>, vote: Vec }, /// Refresh DKG Keys Finished (forcefully). RefreshKeysFinished { next_authority_set_id: dkg_runtime_primitives::AuthoritySetId }, /// NextKeygenThreshold updated @@ -1233,6 +1258,37 @@ pub mod pallet { Err(Error::::InvalidMisbehaviourReports.into()) } + /// Submits an aggregated proposer vote signature to the chain. + /// This can be submitted by anyone. The signatures must be valid against + /// the current set of proposers. + /// + /// The purpose of this extrinsic is to jumpstart the automation of the + /// proposer set update process on any cross-chain application leveraging the + /// DKG. When the DKG fails to rotate, the proposer set will eventually have the + /// opportunity to reset the state of any application that relies on the DKG for + /// governance. + #[pallet::weight(0)] + #[pallet::call_index(7)] + pub fn submit_proposer_set_votes( + origin: OriginFor, + votes: AggregatedProposerSetVotes< + T::DKGId, + T::MaxSignatureLength, + T::MaxReporters, + T::VoteLength, + >, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + let _valid_voters = Self::process_proposer_votes(votes); + // Self::deposit_event(Event::ProposerSetVotesSubmitted { + // signatures: votes.signatures, + // voters: valid_voters, + // vote: votes.vote, + // }); + + Ok(().into()) + } + /// Attempts to remove an authority from all possible jails (keygen & signing). /// This can only be called by the controller of the authority in jail. The /// origin must map directly to the authority in jail. @@ -1242,7 +1298,7 @@ pub mod pallet { /// /// * `origin` - The account origin. #[pallet::weight(::WeightInfo::unjail())] - #[pallet::call_index(7)] + #[pallet::call_index(8)] pub fn unjail(origin: OriginFor) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; let authority = @@ -1270,7 +1326,7 @@ pub mod pallet { /// * `origin` - The account origin. /// * `authority` - The authority to be removed from the keygen jail. #[pallet::weight(::WeightInfo::force_unjail_keygen())] - #[pallet::call_index(8)] + #[pallet::call_index(9)] pub fn force_unjail_keygen( origin: OriginFor, authority: T::DKGId, @@ -1288,7 +1344,7 @@ pub mod pallet { /// * `origin` - The account origin. /// * `authority` - The authority to be removed from the signing jail. #[pallet::weight(::WeightInfo::force_unjail_signing())] - #[pallet::call_index(9)] + #[pallet::call_index(10)] pub fn force_unjail_signing( origin: OriginFor, authority: T::DKGId, @@ -1305,7 +1361,7 @@ pub mod pallet { /// automatically increments the authority ID. It uses `change_authorities` /// to execute the rotation forcefully. #[pallet::weight(0)] - #[pallet::call_index(10)] + #[pallet::call_index(11)] pub fn force_change_authorities(origin: OriginFor) -> DispatchResultWithPostInfo { T::ForceOrigin::ensure_origin(origin)?; let next_authorities = NextAuthorities::::get(); @@ -1323,6 +1379,7 @@ pub mod pallet { // to sign our own key as a means of jumpstarting the mechanism. if let Some(pub_key) = next_pub_key { RefreshInProgress::::put(true); + ShouldSubmitProposerVote::::put(true); let uncompressed_pub_key = Self::decompress_public_key(pub_key.1.into()).unwrap_or_default(); let next_nonce = Self::refresh_nonce() + 1u32; @@ -1351,7 +1408,7 @@ pub mod pallet { /// /// Note that, this will clear the next public key and its signature, if any. #[pallet::weight(0)] - #[pallet::call_index(11)] + #[pallet::call_index(12)] pub fn trigger_emergency_keygen(origin: OriginFor) -> DispatchResultWithPostInfo { T::ForceOrigin::ensure_origin(origin)?; // Clear the next public key, if any, to ensure that the keygen protocol runs and we @@ -1573,10 +1630,13 @@ impl Pallet { signed_payload.extend_from_slice(reports.session_id.to_be_bytes().as_ref()); signed_payload.extend_from_slice(reports.offender.as_ref()); - // TODO: Verify signer from set over the best authorities set (compute it on chain) let verifying_set: Vec = verifying_set .iter() - .map(|x| ecdsa::Public(to_slice_33(&x.encode()).unwrap_or([0u8; 33]))) + .map(|x| match to_slice_33(x.encode().as_ref()) { + Some(x) => ecdsa::Public(x), + None => ecdsa::Public([0u8; 33]), + }) + .filter(|x| x.0 != [0u8; 33]) .collect(); let (_, success) = verify_signer_from_set_ecdsa(verifying_set, &signed_payload, signature); @@ -1589,6 +1649,40 @@ impl Pallet { valid_reporters } + pub fn process_proposer_votes( + votes: AggregatedProposerSetVotes< + T::DKGId, + T::MaxSignatureLength, + T::MaxReporters, + T::VoteLength, + >, + ) -> Vec { + let mut valid_voters = Vec::new(); + for (inx, signature) in votes.signatures.iter().enumerate() { + let mut signed_payload = Vec::new(); + signed_payload.extend_from_slice(votes.vote.as_ref()); + let previous_proposer_set: Vec = + T::ProposerSetView::get_previous_external_proposer_accounts() + .iter() + .map(|x: &(T::AccountId, BoundedVec)| { + match to_slice_33(x.1.encode().as_ref()) { + Some(x) => ecdsa::Public(x), + None => ecdsa::Public([0u8; 33]), + } + }) + .filter(|x| x.0 != [0u8; 33]) + .collect(); + let (_, success) = + verify_signer_from_set_ecdsa(previous_proposer_set, &signed_payload, signature); + + if success && !valid_voters.contains(&votes.voters[inx]) { + valid_voters.push(votes.voters[inx].clone()); + } + } + + valid_voters + } + pub fn store_consensus_log( authority_ids: BoundedVec, next_authority_ids: BoundedVec, @@ -1628,7 +1722,7 @@ impl Pallet { T::AccountId, dkg_runtime_primitives::AuthoritySetId, T::DKGId, - >>::on_authority_set_changed(new_authorities_accounts.clone(), new_authority_ids.clone()); + >>::on_authority_set_changed(&new_authorities_accounts, &new_authority_ids); // Set refresh in progress to false RefreshInProgress::::put(false); // Update the next thresholds for the next session @@ -1801,7 +1895,7 @@ impl Pallet { T::AccountId, dkg_runtime_primitives::AuthoritySetId, T::DKGId, - >>::on_authority_set_changed(authority_account_ids.to_vec(), authorities.to_vec()); + >>::on_authority_set_changed(authority_account_ids, authorities); } /// An offchain function that collects the genesis DKG public key diff --git a/pallets/dkg-metadata/src/mock.rs b/pallets/dkg-metadata/src/mock.rs index cbf9cdd85..5b4a8bd5c 100644 --- a/pallets/dkg-metadata/src/mock.rs +++ b/pallets/dkg-metadata/src/mock.rs @@ -14,6 +14,7 @@ // construct_runtime requires this #![allow(clippy::from_over_into, clippy::unwrap_used)] +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, traits::GenesisBuild, BasicExternalities, @@ -126,6 +127,11 @@ where } } +parameter_types! { + #[derive(Default, Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd, MaxEncodedLen)] + pub const VoteLength: u32 = 64; +} + impl pallet_dkg_metadata::Config for Test { type DKGId = DKGId; type RuntimeEvent = RuntimeEvent; @@ -148,6 +154,8 @@ impl pallet_dkg_metadata::Config for Test { type MaxSignatureLength = MaxSignatureLength; type MaxReporters = MaxReporters; type MaxAuthorities = MaxAuthorities; + type VoteLength = VoteLength; + type ProposerSetView = (); type WeightInfo = (); } diff --git a/pallets/dkg-proposal-handler/src/mock.rs b/pallets/dkg-proposal-handler/src/mock.rs index d7ef29167..6f9bd059e 100644 --- a/pallets/dkg-proposal-handler/src/mock.rs +++ b/pallets/dkg-proposal-handler/src/mock.rs @@ -14,7 +14,7 @@ // #![allow(clippy::unwrap_used)] use crate as pallet_dkg_proposal_handler; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; pub use dkg_runtime_primitives::{ crypto::AuthorityId as DKGId, ConsensusLog, MaxAuthorities, MaxKeyLength, MaxReporters, MaxSignatureLength, DKG_ENGINE_ID, @@ -22,7 +22,7 @@ pub use dkg_runtime_primitives::{ use frame_support::{parameter_types, traits::Everything, BoundedVec, PalletId}; use frame_system as system; use frame_system::EnsureRoot; -use pallet_dkg_proposals::DKGEcdsaToEthereum; +use pallet_dkg_proposals::DKGEcdsaToEthereumAddress; use sp_core::{sr25519::Signature, H256}; use sp_runtime::{ impl_opaque_keys, @@ -166,9 +166,7 @@ where parameter_types! { #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxAuthorityProposers : u32 = 100; - #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxExternalProposerAccounts : u32 = 100; + pub const MaxProposers : u32 = 100; } impl pallet_dkg_proposal_handler::Config for Test { @@ -184,7 +182,7 @@ impl pallet_dkg_proposal_handler::Config for Test { impl pallet_dkg_proposals::Config for Test { type AdminOrigin = frame_system::EnsureRoot; - type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereum; + type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereumAddress; type DKGId = DKGId; type ChainIdentifier = ChainIdentifier; type MaxProposalLength = MaxProposalLength; @@ -195,8 +193,8 @@ impl pallet_dkg_proposals::Config for Test { type Period = Period; type MaxVotes = MaxVotes; type MaxResources = MaxResources; - type MaxAuthorityProposers = MaxAuthorityProposers; - type MaxExternalProposerAccounts = MaxExternalProposerAccounts; + type MaxProposers = MaxProposers; + type ExternalProposerAccountSize = MaxKeyLength; type WeightInfo = (); } @@ -243,6 +241,11 @@ impl pallet_session::Config for Test { type WeightInfo = (); } +parameter_types! { + #[derive(Default, Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd, MaxEncodedLen)] + pub const VoteLength: u32 = 64; +} + impl pallet_dkg_metadata::Config for Test { type DKGId = DKGId; type RuntimeEvent = RuntimeEvent; @@ -265,6 +268,8 @@ impl pallet_dkg_metadata::Config for Test { type MaxSignatureLength = MaxSignatureLength; type MaxReporters = MaxReporters; type MaxAuthorities = MaxAuthorities; + type VoteLength = VoteLength; + type ProposerSetView = DKGProposals; type WeightInfo = (); } diff --git a/pallets/dkg-proposals/src/benchmarking.rs b/pallets/dkg-proposals/src/benchmarking.rs index 502126646..32fc77a65 100644 --- a/pallets/dkg-proposals/src/benchmarking.rs +++ b/pallets/dkg-proposals/src/benchmarking.rs @@ -97,28 +97,6 @@ benchmarks! { assert_last_event::(Event::ChainWhitelisted{ chain_id}.into()); } - add_proposer { - let admin = RawOrigin::Root; - let v: T::AccountId = account("account", 0, SEED); - let another = vec![1u8, 2, 3]; - - - }: _(admin, v.clone(), another) - verify { - assert_last_event::(Event::ProposerAdded{ proposer_id: v}.into()); - } - - remove_proposer { - let admin = RawOrigin::Root; - let v: T::AccountId = account("account", 0, SEED); - let another = vec![1u8, 2, 3]; - - crate::Pallet::::register_proposer(v.clone(), another).unwrap(); - }: _(admin, v.clone()) - verify { - assert_last_event::(Event::ProposerRemoved{ proposer_id: v}.into()); - } - acknowledge_proposal { let c in 1 .. 500; let caller: T::AccountId = whitelisted_caller(); @@ -132,12 +110,14 @@ benchmarks! { kind: ProposalKind::AnchorUpdate, data: vec![].try_into().unwrap(), }); - Proposers::::insert(caller.clone(), true); + let mut proposers: BoundedVec = vec![caller.clone()].try_into().expect("Failed to create proposers"); + Proposers::::put(proposers.clone()); Pallet::::whitelist(chain_id).unwrap(); Pallet::::set_proposer_threshold(10).unwrap(); for i in 1..9 { let who: T::AccountId = account("account", i, SEED); - Proposers::::insert(who.clone(), true); + proposers.try_push(who.clone()).expect("Failed to push proposer"); + Proposers::::put(proposers.clone()); Pallet::::commit_vote(who, i.into(), chain_id, &proposal, true).unwrap(); } }: _(RawOrigin::Signed(caller.clone()), nonce.into(), chain_id, resource_id, proposal.clone()) @@ -159,12 +139,14 @@ benchmarks! { kind: ProposalKind::AnchorUpdate, data: vec![].try_into().unwrap(), }); - Proposers::::insert(caller.clone(), true); + let mut proposers: BoundedVec = vec![caller.clone()].try_into().expect("Failed to create proposers"); + Proposers::::put(proposers.clone()); Pallet::::whitelist(chain_id).unwrap(); Pallet::::set_proposer_threshold(10).unwrap(); for i in 1..9 { let who: T::AccountId = account("account", i, SEED); - Proposers::::insert(who.clone(), true); + proposers.try_push(who.clone()).expect("Failed to push proposer"); + Proposers::::put(proposers.clone()); Pallet::::commit_vote(who, i.into(), chain_id, &proposal, false).unwrap(); } }: _(RawOrigin::Signed(caller.clone()), nonce.into(), chain_id, resource_id, proposal.clone()) @@ -184,12 +166,14 @@ benchmarks! { kind: ProposalKind::AnchorUpdate, data: vec![].try_into().unwrap(), }); - Proposers::::insert(caller.clone(), true); + let mut proposers: BoundedVec = vec![caller.clone()].try_into().expect("Failed to create proposers"); + Proposers::::put(proposers.clone()); Pallet::::whitelist(chain_id).unwrap(); Pallet::::set_proposer_threshold(10).unwrap(); for i in 1..9 { let who: T::AccountId = account("account", i, SEED); - Proposers::::insert(who.clone(), true); + proposers.try_push(who.clone()).expect("Failed to push proposer"); + Proposers::::put(proposers.clone()); Pallet::::commit_vote(who, i.into(), chain_id, &proposal, false).unwrap(); } diff --git a/pallets/dkg-proposals/src/lib.rs b/pallets/dkg-proposals/src/lib.rs index 798a1fe4d..a094d2a3a 100644 --- a/pallets/dkg-proposals/src/lib.rs +++ b/pallets/dkg-proposals/src/lib.rs @@ -96,8 +96,8 @@ mod tests; pub mod types; pub mod utils; use dkg_runtime_primitives::{ - traits::OnAuthoritySetChangeHandler, ProposalHandlerTrait, ProposalNonce, ResourceId, - TypedChainId, + traits::{GetProposerSet, OnAuthoritySetChangeHandler}, + ProposalHandlerTrait, ProposalNonce, ResourceId, TypedChainId, }; use frame_support::{ pallet_prelude::{ensure, DispatchResultWithPostInfo}, @@ -127,6 +127,7 @@ pub mod pallet { types::{ProposalVotes, DKG_DEFAULT_PROPOSER_THRESHOLD}, weights::WeightInfo, }; + use dkg_runtime_primitives::{ proposal::{Proposal, ProposalKind}, ProposalNonce, @@ -157,6 +158,7 @@ pub mod pallet { /// Authority identifier type type DKGId: Member + Parameter + RuntimeAppPublic + MaybeSerializeDeserialize; + /// Convert DKG AuthorityId to a form that would end up in the Merkle Tree. /// /// For instance for ECDSA (secp256k1) we want to store uncompressed public keys (65 bytes) @@ -165,6 +167,9 @@ pub mod pallet { /// efficiency reasons. type DKGAuthorityToMerkleLeaf: Convert>; + /// The handler for proposals + type ProposalHandler: ProposalHandlerTrait; + /// The identifier for this chain. /// This must be unique and must not collide with existing IDs within a /// set of bridged chains. @@ -186,15 +191,20 @@ pub mod pallet { #[pallet::constant] type MaxResources: Get + TypeInfo; - /// The max authority proposers that can be stored in storage + /// The max proposers that can be stored in storage #[pallet::constant] - type MaxAuthorityProposers: Get + TypeInfo; + type MaxProposers: Get + TypeInfo; - /// The max external proposer accounts that can be stored in storage + /// The size of an external proposer account (i.e. 64-byte Ethereum public key) #[pallet::constant] - type MaxExternalProposerAccounts: Get + TypeInfo; - - type ProposalHandler: ProposalHandlerTrait; + type ExternalProposerAccountSize: Get + + Debug + + Clone + + Eq + + PartialEq + + PartialOrd + + Ord + + TypeInfo; /// Max length of a proposal #[pallet::constant] @@ -233,34 +243,32 @@ pub mod pallet { /// Tracks current proposer set #[pallet::storage] - #[pallet::getter(fn proposers)] - pub type Proposers = StorageMap<_, Blake2_128Concat, T::AccountId, bool, ValueQuery>; + #[pallet::getter(fn previous_proposers)] + pub type PreviousProposers = + StorageValue<_, BoundedVec, ValueQuery>; - /// Tracks current proposer set external accounts - /// Currently meant to store Ethereum compatible 64-bytes ECDSA public keys + /// Tracks previous proposer set external accounts #[pallet::storage] - #[pallet::getter(fn external_proposer_accounts)] - pub type ExternalProposerAccounts = StorageMap< + #[pallet::getter(fn previous_external_proposer_accounts)] + pub type PreviousExternalProposerAccounts = StorageValue< _, - Blake2_128Concat, - T::AccountId, - BoundedVec, + BoundedVec<(T::AccountId, BoundedVec), T::MaxProposers>, ValueQuery, >; - /// Tracks the authorities that are proposers so we can properly update the proposer set - /// across sessions and authority changes. + /// Tracks current proposer set #[pallet::storage] - #[pallet::getter(fn authority_proposers)] - pub type AuthorityProposers = - StorageValue<_, BoundedVec, ValueQuery>; + #[pallet::getter(fn proposers)] + pub type Proposers = + StorageValue<_, BoundedVec, ValueQuery>; /// Tracks current proposer set external accounts + /// Meant to store Ethereum compatible 64-bytes ECDSA public keys #[pallet::storage] - #[pallet::getter(fn external_authority_proposer_accounts)] - pub type ExternalAuthorityProposerAccounts = StorageValue< + #[pallet::getter(fn external_proposer_accounts)] + pub type ExternalProposerAccounts = StorageValue< _, - BoundedVec, T::MaxAuthorityProposers>, + BoundedVec<(T::AccountId, BoundedVec), T::MaxProposers>, ValueQuery, >; @@ -289,6 +297,16 @@ pub mod pallet { pub type Resources = StorageMap<_, Blake2_256, ResourceId, BoundedVec>; + /// Previous proposer set merkle root + #[pallet::storage] + #[pallet::getter(fn previous_proposer_set_root)] + pub type PreviousProposerSetMerkleRoot = StorageValue<_, [u8; 32], ValueQuery>; + + /// Next proposer set merkle root + #[pallet::storage] + #[pallet::getter(fn next_proposer_set_root)] + pub type ActiveProposerSetMerkleRoot = StorageValue<_, [u8; 32], ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { @@ -298,8 +316,12 @@ pub mod pallet { ChainWhitelisted { chain_id: TypedChainId }, /// Proposer added to set ProposerAdded { proposer_id: T::AccountId }, + /// Queued proposer added to set + QueuedProposerAdded { proposer_id: T::AccountId }, /// Proposer removed from set ProposerRemoved { proposer_id: T::AccountId }, + /// Queued proposer removed from set + QueuedProposerRemoved { proposer_id: T::AccountId }, /// Vote submitted in favour of proposal VoteFor { kind: ProposalKind, @@ -339,7 +361,7 @@ pub mod pallet { proposal_nonce: ProposalNonce, }, /// Proposers have been reset - AuthorityProposersReset { proposers: Vec }, + ProposersReset { proposers: Vec }, } // Errors inform users that something went wrong. @@ -381,6 +403,8 @@ pub mod pallet { ProposerCountIsZero, /// Input is out of bounds OutOfBounds, + /// Queued proposer action set is at capacity + QueuedProposerActionsFull, } #[pallet::genesis_config] @@ -419,9 +443,12 @@ pub mod pallet { Resources::::insert(*r_id, bounded_input); } - for proposer in self.initial_proposers.iter() { - Proposers::::insert(proposer, true); - } + let bounded_proposers: BoundedVec = self + .initial_proposers + .clone() + .try_into() + .expect("Genesis proposers is too large"); + Proposers::::put(bounded_proposers); } } @@ -435,7 +462,7 @@ pub mod pallet { /// # /// - O(1) lookup and insert /// # - #[pallet::weight(0)] + #[pallet::weight(::WeightInfo::set_threshold(0))] #[pallet::call_index(0)] pub fn set_threshold(origin: OriginFor, threshold: u32) -> DispatchResultWithPostInfo { Self::ensure_admin(origin)?; @@ -447,7 +474,7 @@ pub mod pallet { /// # /// - O(1) write /// # - #[pallet::weight(1)] + #[pallet::weight(::WeightInfo::set_resource(0))] #[pallet::call_index(1)] pub fn set_resource( origin: OriginFor, @@ -466,7 +493,7 @@ pub mod pallet { /// # /// - O(1) removal /// # - #[pallet::weight(2)] + #[pallet::weight(::WeightInfo::remove_resource())] #[pallet::call_index(2)] pub fn remove_resource(origin: OriginFor, id: ResourceId) -> DispatchResultWithPostInfo { Self::ensure_admin(origin)?; @@ -478,7 +505,7 @@ pub mod pallet { /// # /// - O(1) lookup and insert /// # - #[pallet::weight(3)] + #[pallet::weight(::WeightInfo::whitelist_chain())] #[pallet::call_index(3)] pub fn whitelist_chain( origin: OriginFor, @@ -488,37 +515,6 @@ pub mod pallet { Self::whitelist(chain_id) } - /// Adds a new proposer to the proposer set. - /// - /// # - /// - O(1) lookup and insert - /// # - #[pallet::weight(4)] - #[pallet::call_index(4)] - pub fn add_proposer( - origin: OriginFor, - native_account: T::AccountId, - external_account: Vec, - ) -> DispatchResultWithPostInfo { - Self::ensure_admin(origin)?; - Self::register_proposer(native_account, external_account) - } - - /// Removes an existing proposer from the set. - /// - /// # - /// - O(1) lookup and removal - /// # - #[pallet::weight(5)] - #[pallet::call_index(5)] - pub fn remove_proposer( - origin: OriginFor, - v: T::AccountId, - ) -> DispatchResultWithPostInfo { - Self::ensure_admin(origin)?; - Self::unregister_proposer(v) - } - /// Commits a vote in favour of the provided proposal. /// /// If a proposal with the given nonce and source chain ID does not @@ -528,8 +524,8 @@ pub mod pallet { /// # /// - weight of proposed call, regardless of whether execution is performed /// # - #[pallet::weight(6)] - #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::acknowledge_proposal(0))] + #[pallet::call_index(4)] pub fn acknowledge_proposal( origin: OriginFor, nonce: ProposalNonce, @@ -550,8 +546,8 @@ pub mod pallet { /// # /// - Fixed, since execution of proposal should not be included /// # - #[pallet::weight(7)] - #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::reject_proposal(0))] + #[pallet::call_index(5)] pub fn reject_proposal( origin: OriginFor, nonce: ProposalNonce, @@ -575,8 +571,8 @@ pub mod pallet { /// # /// - weight of proposed call, regardless of whether execution is performed /// # - #[pallet::weight(8)] - #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::eval_vote_state(0))] + #[pallet::call_index(6)] pub fn eval_vote_state( origin: OriginFor, nonce: ProposalNonce, @@ -600,7 +596,7 @@ impl Pallet { /// Checks if who is a proposer pub fn is_proposer(who: &T::AccountId) -> bool { - Self::proposers(who) + Self::proposers().contains(who) } /// Asserts if a resource is registered @@ -618,6 +614,7 @@ impl Pallet { /// Set a new voting threshold pub fn set_proposer_threshold(threshold: u32) -> DispatchResultWithPostInfo { ensure!(threshold > 0, Error::::InvalidThreshold); + ensure!(threshold <= Proposers::::get().len() as u32, Error::::InvalidThreshold); ProposerThreshold::::put(threshold); Self::deposit_event(Event::ProposerThresholdChanged { new_threshold: threshold }); Ok(().into()) @@ -648,37 +645,6 @@ impl Pallet { Ok(().into()) } - /// Adds a new proposer to the set. Requires both an account ID and an ECDSA public key. - pub fn register_proposer( - proposer: T::AccountId, - external_account: Vec, - ) -> DispatchResultWithPostInfo { - ensure!(!Self::is_proposer(&proposer), Error::::ProposerAlreadyExists); - // Add the proposer to the set - Proposers::::insert(&proposer, true); - // Add the proposer's public key to the set - let bounded_external_account: BoundedVec<_, _> = - external_account.try_into().map_err(|_| Error::::OutOfBounds)?; - ExternalProposerAccounts::::insert(&proposer, bounded_external_account); - ProposerCount::::mutate(|i| *i += 1); - - Self::deposit_event(Event::ProposerAdded { proposer_id: proposer }); - Ok(().into()) - } - - /// Removes a proposer from the set - pub fn unregister_proposer(proposer: T::AccountId) -> DispatchResultWithPostInfo { - ensure!(Self::is_proposer(&proposer), Error::::ProposerInvalid); - // Remove the proposer - Proposers::::remove(&proposer); - // Remove the proposer's external account - ExternalProposerAccounts::::remove(&proposer); - // Decrement the proposer count - ProposerCount::::mutate(|i| *i -= 1); - Self::deposit_event(Event::ProposerRemoved { proposer_id: proposer }); - Ok(().into()) - } - // *** Proposal voting and execution methods *** /// Commits a vote for a proposal. If the proposal doesn't exist it will be @@ -741,7 +707,6 @@ impl Pallet { let now = >::block_number(); ensure!(!votes.is_complete(), Error::::ProposalAlreadyComplete); ensure!(!votes.is_expired(now), Error::::ProposalExpired); - let status = votes.try_to_complete(ProposerThreshold::::get(), ProposerCount::::get()); Votes::::insert(src_chain_id, (nonce, prop.clone()), votes.clone()); @@ -836,8 +801,11 @@ impl Pallet { /// to be fixed or changed. #[allow(dead_code)] fn create_proposer_set_update() { + // Store the current proposer set root in the previous slot while we update it. + PreviousProposerSetMerkleRoot::::put(Self::next_proposer_set_root()); // Merkleize the new proposer set - let mut proposer_set_merkle_root = Self::get_proposer_set_tree_root(); + let proposer_set_merkle_root = Self::get_proposer_set_tree_root(); + ActiveProposerSetMerkleRoot::::put(proposer_set_merkle_root); // Increment the nonce, we increment first because the nonce starts at 0 let curr_proposal_nonce = Self::proposer_set_update_proposal_nonce(); let new_proposal_nonce = curr_proposal_nonce.saturating_add(1u32); @@ -857,13 +825,15 @@ impl Pallet { let num_of_proposers = Self::proposer_count(); - proposer_set_merkle_root + let mut proposer_set_update_proposal = vec![]; + proposer_set_update_proposal.extend_from_slice(&proposer_set_merkle_root); + proposer_set_update_proposal .extend_from_slice(&average_session_length_in_millisecs.to_be_bytes()); - proposer_set_merkle_root.extend_from_slice(&num_of_proposers.to_be_bytes()); - proposer_set_merkle_root.extend_from_slice(&new_proposal_nonce.to_be_bytes()); + proposer_set_update_proposal.extend_from_slice(&num_of_proposers.to_be_bytes()); + proposer_set_update_proposal.extend_from_slice(&new_proposal_nonce.to_be_bytes()); match T::ProposalHandler::handle_unsigned_proposer_set_update_proposal( - proposer_set_merkle_root, + proposer_set_update_proposal, dkg_runtime_primitives::ProposalAction::Sign(0), ) { Ok(()) => {}, @@ -879,31 +849,30 @@ impl Pallet { /// Returns the leaves of the proposer set merkle tree. /// /// It is expected that the size of the returned vector is a power of 2. - pub fn pre_process_for_merkleize() -> Vec> { + pub fn pre_process_for_merkleize() -> Vec<[u8; 32]> { let height = Self::get_proposer_set_tree_height(); - let proposer_keys = ExternalProposerAccounts::::iter_keys(); // Check for each key that the proposer is valid (should return true) - let mut base_layer: Vec> = proposer_keys - .filter(|v| ExternalProposerAccounts::::contains_key(v)) - .map(|x| keccak_256(&ExternalProposerAccounts::::get(x)[..]).to_vec()) + let mut base_layer: Vec<[u8; 32]> = ExternalProposerAccounts::::get() + .into_iter() + .map(|accounts| keccak_256(&accounts.1)) .collect(); // Pad base_layer to have length 2^height let two = 2; while base_layer.len() != two.saturating_pow(height.try_into().unwrap_or_default()) { - base_layer.push(keccak_256(&[0u8]).to_vec()); + base_layer.push(keccak_256(&[0u8])); } base_layer } /// Computes the next layer of the merkle tree by hashing the previous layer. - pub fn next_layer(curr_layer: Vec>) -> Vec> { - let mut layer_above: Vec> = Vec::new(); + pub fn next_layer(curr_layer: Vec<[u8; 32]>) -> Vec<[u8; 32]> { + let mut layer_above: Vec<[u8; 32]> = Vec::new(); let mut index = 0; while index < curr_layer.len() { - let mut input_to_hash_as_vec: Vec = curr_layer[index].clone(); + let mut input_to_hash_as_vec: Vec = curr_layer[index].to_vec(); input_to_hash_as_vec.extend_from_slice(&curr_layer[index + 1][..]); let input_to_hash_as_slice = &input_to_hash_as_vec[..]; - layer_above.push(keccak_256(input_to_hash_as_slice).to_vec()); + layer_above.push(keccak_256(input_to_hash_as_slice)); index += 2; } layer_above @@ -925,14 +894,17 @@ impl Pallet { } /// Computes the merkle root of the proposer set tree - pub fn get_proposer_set_tree_root() -> Vec { + pub fn get_proposer_set_tree_root() -> [u8; 32] { let mut curr_layer = Self::pre_process_for_merkleize(); let mut height = Self::get_proposer_set_tree_height(); while height > 0 { curr_layer = Self::next_layer(curr_layer); height -= 1; } - curr_layer[0].clone() + + let mut root = [0u8; 32]; + root.copy_from_slice(&curr_layer[0][..]); + root } } @@ -943,63 +915,61 @@ impl /// Called when the authority set has changed. /// /// On new authority sets, we need to: - /// - Remove the old authorities from the proposer set and their ECDSA keys from the external - /// proposer accounts + /// - Store the current proposers and their ECDSA keys in the previous proposer set collections + /// for the emergency fallback mechanism to function. + /// - Remove the old authorities from the current proposer set and their ECDSA keys from the + /// external proposer accounts /// - Add the new authorities to the proposer set and their ECDSA keys to the external proposer /// accounts + /// - Apply all queued proposer actions which were queued during the previous epoch. These + /// actions contain additions and substracTtions from the proposer set (basically + /// non-authority proposers). /// - Create a new proposer set update proposal by merkleizing the new proposer set /// - Submit the new proposet set update to the `pallet-dkg-proposal-handler` - fn on_authority_set_changed(authorities: Vec, authority_ids: Vec) { + fn on_authority_set_changed(authorities: &[T::AccountId], authority_ids: &[T::DKGId]) { + // Set previous values to be current before any changes + PreviousProposers::::put(Proposers::::get()); + PreviousExternalProposerAccounts::::put(ExternalProposerAccounts::::get()); // Get the new external accounts for the new authorities by converting // their DKGIds to data meant for merkle tree insertion (i.e. Ethereum addresses) let new_external_accounts = authority_ids .iter() .map(|id| T::DKGAuthorityToMerkleLeaf::convert(id.clone())) + .map(|id| { + let bounded_external_account: BoundedVec = + id.try_into().expect("External account outside limits!"); + bounded_external_account + }) .collect::>(); - // Remove old authorities and their external accounts from the list - let old_authority_proposers = Self::authority_proposers(); - for old_authority_account in old_authority_proposers { - ProposerCount::::put(Self::proposer_count().saturating_sub(1)); - Proposers::::remove(&old_authority_account); - ExternalProposerAccounts::::remove(&old_authority_account); - } - // Add new authorities and their external accounts to the list - for (authority_account, external_account) in - authorities.iter().zip(new_external_accounts.iter()) - { - ProposerCount::::put(Self::proposer_count().saturating_add(1)); - Proposers::::insert(authority_account, true); - - let bounded_external_account: BoundedVec<_, _> = - external_account.clone().try_into().expect("External account outside limits!"); - ExternalProposerAccounts::::insert(authority_account, bounded_external_account); - } - // Update the new authorities that are also proposers - let bounded_authorities: BoundedVec<_, _> = authorities + // Set all new values + ProposerCount::::put(authorities.len() as u32); + let bounded_proposers: BoundedVec = + authorities.to_vec().try_into().expect("Too many authorities!"); + Proposers::::put(bounded_proposers); + let external_accounts_tuple: Vec<( + T::AccountId, + BoundedVec, + )> = authorities + .to_vec() + .into_iter() + .zip(new_external_accounts.into_iter()) + .collect::>(); + let bounded_external_accounts: BoundedVec< + (T::AccountId, BoundedVec), + T::MaxProposers, + > = external_accounts_tuple .try_into() - .expect("This should never happen, bounded authorities outside limits!"); - AuthorityProposers::::put(bounded_authorities.clone()); - // Update the external accounts of the new authorities - let mut bounded_new_external_accounts: BoundedVec<_, _> = Default::default(); - for item in new_external_accounts { - let bounded_item: BoundedVec<_, _> = - item.try_into().expect("New External account outside limits!"); - bounded_new_external_accounts - .try_push(bounded_item) - .expect("New External account count outside limits!"); - } - ExternalAuthorityProposerAccounts::::put(bounded_new_external_accounts); - Self::deposit_event(Event::::AuthorityProposersReset { - proposers: bounded_authorities.into(), - }); + .expect("Too many external proposer accounts!"); + ExternalProposerAccounts::::put(bounded_external_accounts); + Self::deposit_event(Event::::ProposersReset { proposers: authorities.to_vec() }); // Create the new proposer set merkle tree and update proposal Self::create_proposer_set_update(); } } /// Convert DKG secp256k1 public keys into Ethereum addresses -pub struct DKGEcdsaToEthereum; -impl Convert> for DKGEcdsaToEthereum { +pub struct DKGEcdsaToEthereumAddress; +impl Convert> for DKGEcdsaToEthereumAddress { fn convert(a: dkg_runtime_primitives::crypto::AuthorityId) -> Vec { use k256::{ecdsa::VerifyingKey, elliptic_curve::sec1::ToEncodedPoint}; let _x = VerifyingKey::from_sec1_bytes(sp_core::crypto::ByteArray::as_slice(&a)); @@ -1016,3 +986,14 @@ impl Convert> for DKGEcdsaT .unwrap_or_default() } } + +impl GetProposerSet for Pallet { + fn get_previous_proposer_set() -> Vec { + PreviousProposers::::get().into_iter().collect() + } + + fn get_previous_external_proposer_accounts( + ) -> Vec<(T::AccountId, BoundedVec)> { + PreviousExternalProposerAccounts::::get().into_iter().collect() + } +} diff --git a/pallets/dkg-proposals/src/mock.rs b/pallets/dkg-proposals/src/mock.rs index d7e434c4b..bfd969eb7 100644 --- a/pallets/dkg-proposals/src/mock.rs +++ b/pallets/dkg-proposals/src/mock.rs @@ -17,7 +17,7 @@ use super::*; use crate as pallet_dkg_proposals; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; pub use dkg_runtime_primitives::{ crypto::AuthorityId as DKGId, ConsensusLog, MaxAuthorities, MaxKeyLength, MaxProposalLength, MaxReporters, MaxSignatureLength, DKG_ENGINE_ID, @@ -158,6 +158,11 @@ where } } +parameter_types! { + #[derive(Default, Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd, MaxEncodedLen)] + pub const VoteLength: u32 = 64; +} + impl pallet_dkg_metadata::Config for Test { type DKGId = DKGId; type RuntimeEvent = RuntimeEvent; @@ -180,6 +185,8 @@ impl pallet_dkg_metadata::Config for Test { type MaxSignatureLength = MaxSignatureLength; type MaxReporters = MaxReporters; type MaxAuthorities = MaxAuthorities; + type VoteLength = VoteLength; + type ProposerSetView = DKGProposals; type WeightInfo = (); } @@ -246,9 +253,7 @@ parameter_types! { #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] pub const MaxResources : u32 = 1000; #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxAuthorityProposers : u32 = 1000; - #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxExternalProposerAccounts : u32 = 1000; + pub const MaxProposers : u32 = 1000; } impl pallet_dkg_proposal_handler::Config for Test { @@ -264,7 +269,7 @@ impl pallet_dkg_proposal_handler::Config for Test { impl pallet_dkg_proposals::Config for Test { type AdminOrigin = frame_system::EnsureRoot; - type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereum; + type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereumAddress; type DKGId = DKGId; type ChainIdentifier = ChainIdentifier; type RuntimeEvent = RuntimeEvent; @@ -275,8 +280,8 @@ impl pallet_dkg_proposals::Config for Test { type Period = Period; type MaxVotes = MaxVotes; type MaxResources = MaxResources; - type MaxAuthorityProposers = MaxAuthorityProposers; - type MaxExternalProposerAccounts = MaxExternalProposerAccounts; + type MaxProposers = MaxProposers; + type ExternalProposerAccountSize = MaxKeyLength; type WeightInfo = (); } @@ -300,8 +305,8 @@ pub fn mock_pub_key(id: u8) -> AccountId { sr25519::Public::from_raw([id; 32]) } -pub fn mock_ecdsa_key(id: u8) -> Vec { - DKGEcdsaToEthereum::convert(mock_dkg_id(id)) +pub fn mock_ecdsa_address(id: u8) -> Vec { + DKGEcdsaToEthereumAddress::convert(mock_dkg_id(id)) } pub(crate) fn roll_to(n: u64) { @@ -419,27 +424,37 @@ pub fn new_test_ext_initialized( ) -> sp_io::TestExternalities { let mut t = ExtBuilder::build(); t.execute_with(|| { + // Whitelist chain + assert_ok!(DKGProposals::whitelist_chain(RuntimeOrigin::root(), src_chain_id)); + + let proposers = vec![ + (mock_pub_key(PROPOSER_A), mock_ecdsa_address(PROPOSER_A)), + (mock_pub_key(PROPOSER_B), mock_ecdsa_address(PROPOSER_B)), + (mock_pub_key(PROPOSER_C), mock_ecdsa_address(PROPOSER_C)), + ]; + let bounded_proposers: BoundedVec = proposers + .iter() + .map(|x| x.0) + .collect::>() + .try_into() + .expect("Too many proposers"); + Proposers::::put(bounded_proposers); + let bounded_external_accounts: BoundedVec< + (AccountId, BoundedVec), + MaxProposers, + > = proposers + .iter() + .map(|x| (x.0.clone(), x.1.clone().try_into().expect("Key size too large"))) + .collect::)>>() + .try_into() + .expect("Too many proposers"); + ExternalProposerAccounts::::put(bounded_external_accounts); + ProposerCount::::put(3); + // Set and check threshold assert_ok!(DKGProposals::set_threshold(RuntimeOrigin::root(), TEST_THRESHOLD)); assert_eq!(DKGProposals::proposer_threshold(), TEST_THRESHOLD); - // Add proposers - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_A), - mock_ecdsa_key(PROPOSER_A) - )); - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_B), - mock_ecdsa_key(PROPOSER_B) - )); - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_C), - mock_ecdsa_key(PROPOSER_C) - )); - // Whitelist chain - assert_ok!(DKGProposals::whitelist_chain(RuntimeOrigin::root(), src_chain_id)); + // Set and check resource ID mapped to some junk data assert_ok!(DKGProposals::set_resource(RuntimeOrigin::root(), r_id, resource)); assert!(DKGProposals::resource_exists(r_id), "{}", true); diff --git a/pallets/dkg-proposals/src/tests.rs b/pallets/dkg-proposals/src/tests.rs index 9c1a59227..df2664201 100644 --- a/pallets/dkg-proposals/src/tests.rs +++ b/pallets/dkg-proposals/src/tests.rs @@ -23,13 +23,14 @@ use super::{ *, }; use crate::mock::{ - assert_has_event, manually_set_proposer_count, mock_ecdsa_key, mock_pub_key, + assert_has_event, manually_set_proposer_count, mock_ecdsa_address, mock_pub_key, new_test_ext_initialized, roll_to, CollatorSelection, DKGProposalHandler, ExtBuilder, + MaxProposers, }; use codec::Encode; use core::panic; use dkg_runtime_primitives::{ - DKGPayloadKey, FunctionSignature, ProposalHeader, ProposalNonce, TypedChainId, + DKGPayloadKey, FunctionSignature, MaxKeyLength, ProposalHeader, ProposalNonce, TypedChainId, }; use frame_support::{assert_err, assert_noop, assert_ok}; use std::vec; @@ -158,77 +159,41 @@ fn set_get_threshold() { new_test_ext().execute_with(|| { assert_eq!(ProposerThreshold::::get(), 1); + let proposers = vec![ + (mock_pub_key(PROPOSER_A), mock_ecdsa_address(PROPOSER_A)), + (mock_pub_key(PROPOSER_B), mock_ecdsa_address(PROPOSER_B)), + (mock_pub_key(PROPOSER_C), mock_ecdsa_address(PROPOSER_C)), + ]; + let bounded_proposers: BoundedVec = proposers + .iter() + .map(|x| x.0) + .collect::>() + .try_into() + .expect("Too many proposers"); + Proposers::::put(bounded_proposers); + let bounded_external_accounts: BoundedVec< + (AccountId, BoundedVec), + MaxProposers, + > = proposers + .iter() + .map(|x| (x.0.clone(), x.1.clone().try_into().expect("Key size too large"))) + .collect::)>>() + .try_into() + .expect("Too many proposers"); + ExternalProposerAccounts::::put(bounded_external_accounts); + assert_ok!(DKGProposals::set_threshold(RuntimeOrigin::root(), TEST_THRESHOLD)); assert_eq!(ProposerThreshold::::get(), TEST_THRESHOLD); - assert_ok!(DKGProposals::set_threshold(RuntimeOrigin::root(), 5)); - assert_eq!(ProposerThreshold::::get(), 5); + assert_ok!(DKGProposals::set_threshold(RuntimeOrigin::root(), 3)); + assert_eq!(ProposerThreshold::::get(), 3); assert_events(vec![ RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerThresholdChanged { new_threshold: TEST_THRESHOLD, }), RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerThresholdChanged { - new_threshold: 5, - }), - ]); - }) -} - -#[test] -fn add_remove_relayer() { - new_test_ext().execute_with(|| { - assert_ok!(DKGProposals::set_threshold(RuntimeOrigin::root(), TEST_THRESHOLD,)); - assert_eq!(DKGProposals::proposer_count(), 0); - - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_A), - mock_ecdsa_key(PROPOSER_A) - )); - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_B), - mock_ecdsa_key(PROPOSER_B) - )); - assert_ok!(DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_C), - mock_ecdsa_key(PROPOSER_C) - )); - assert_eq!(DKGProposals::proposer_count(), 3); - - // Already exists - assert_noop!( - DKGProposals::add_proposer( - RuntimeOrigin::root(), - mock_pub_key(PROPOSER_A), - mock_ecdsa_key(PROPOSER_A) - ), - Error::::ProposerAlreadyExists - ); - - // Confirm removal - assert_ok!(DKGProposals::remove_proposer(RuntimeOrigin::root(), mock_pub_key(PROPOSER_B))); - assert_eq!(DKGProposals::proposer_count(), 2); - assert_noop!( - DKGProposals::remove_proposer(RuntimeOrigin::root(), mock_pub_key(PROPOSER_B)), - Error::::ProposerInvalid - ); - assert_eq!(DKGProposals::proposer_count(), 2); - - assert_events(vec![ - RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerAdded { - proposer_id: mock_pub_key(PROPOSER_A), - }), - RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerAdded { - proposer_id: mock_pub_key(PROPOSER_B), - }), - RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerAdded { - proposer_id: mock_pub_key(PROPOSER_C), - }), - RuntimeEvent::DKGProposals(pallet_dkg_proposals::Event::ProposerRemoved { - proposer_id: mock_pub_key(PROPOSER_B), + new_threshold: 3, }), ]); }) @@ -426,6 +391,7 @@ fn create_unsucessful_proposal() { status: ProposalStatus::Rejected, expiry: ProposalLifetime::get() + 1, }; + assert_eq!(prop, expected); assert_eq!(Balances::free_balance(mock_pub_key(PROPOSER_B)), 0); @@ -654,7 +620,7 @@ fn should_reset_proposers_if_authorities_changed_during_a_session_change() { assert_has_event(RuntimeEvent::Session(pallet_session::Event::NewSession { session_index: 1, })); - assert_has_event(RuntimeEvent::DKGProposals(crate::Event::AuthorityProposersReset { + assert_has_event(RuntimeEvent::DKGProposals(crate::Event::ProposersReset { proposers: vec![ mock_pub_key(PROPOSER_A), mock_pub_key(PROPOSER_B), @@ -671,7 +637,7 @@ fn should_reset_proposers_if_authorities_changed() { ExtBuilder::with_genesis_collators().execute_with(|| { CollatorSelection::leave_intent(RuntimeOrigin::signed(mock_pub_key(PROPOSER_D))).unwrap(); roll_to(10); - assert_has_event(RuntimeEvent::DKGProposals(crate::Event::AuthorityProposersReset { + assert_has_event(RuntimeEvent::DKGProposals(crate::Event::ProposersReset { proposers: vec![ mock_pub_key(PROPOSER_A), mock_pub_key(PROPOSER_B), @@ -820,21 +786,21 @@ fn proposers_iter_keys_should_only_contain_active_proposers() { let r_id = derive_resource_id(src_id.underlying_chain_id(), 0x0100, b"remark"); new_test_ext_initialized(src_id, r_id, b"System.remark".to_vec()).execute_with(|| { - assert_eq!(Proposers::::iter_keys().count(), 3); + assert_eq!(Proposers::::get().len(), 3); }); } use sp_io::hashing::keccak_256; //Tests whether proposer root is correct #[test] -fn should_calculate_corrrect_proposer_set_root() { +fn should_calculate_correct_proposer_set_root() { ExtBuilder::with_genesis_collators().execute_with(|| { // Initial proposer set is invulnerables even when another collator exists assert_eq!(DKGProposals::proposer_count(), 3); // Get the three invulnerable proposers' ECDSA keys - let proposer_a_address = mock_ecdsa_key(3); - let proposer_b_address = mock_ecdsa_key(1); - let proposer_c_address = mock_ecdsa_key(2); + let proposer_a_address = mock_ecdsa_address(1); + let proposer_b_address = mock_ecdsa_address(2); + let proposer_c_address = mock_ecdsa_address(3); let leaf0 = keccak_256(&proposer_a_address[..]); let leaf1 = keccak_256(&proposer_b_address[..]); @@ -856,10 +822,10 @@ fn should_calculate_corrrect_proposer_set_root() { roll_to(20); // The fourth collator is now in the proposer set as well assert_eq!(DKGProposals::proposer_count(), 4); - let proposer_a_address = mock_ecdsa_key(3); - let proposer_b_address = mock_ecdsa_key(4); - let proposer_c_address = mock_ecdsa_key(1); - let proposer_d_address = mock_ecdsa_key(2); + let proposer_a_address = mock_ecdsa_address(1); + let proposer_b_address = mock_ecdsa_address(2); + let proposer_c_address = mock_ecdsa_address(3); + let proposer_d_address = mock_ecdsa_address(4); let leaf0 = keccak_256(&proposer_a_address[..]); let leaf1 = keccak_256(&proposer_b_address[..]); let leaf2 = keccak_256(&proposer_c_address[..]); diff --git a/pallets/dkg-proposals/src/weights.rs b/pallets/dkg-proposals/src/weights.rs index b07b4ab1f..454d693a9 100644 --- a/pallets/dkg-proposals/src/weights.rs +++ b/pallets/dkg-proposals/src/weights.rs @@ -35,8 +35,6 @@ pub trait WeightInfo { fn set_resource(c: u32, ) -> Weight; fn remove_resource() -> Weight; fn whitelist_chain() -> Weight; - fn add_proposer() -> Weight; - fn remove_proposer() -> Weight; fn acknowledge_proposal(c: u32, ) -> Weight; fn reject_proposal(c: u32, ) -> Weight; fn eval_vote_state(c: u32, ) -> Weight; @@ -88,36 +86,6 @@ impl WeightInfo for WebbWeight { .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: DKGProposals Proposers (r:1 w:1) - /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: DKGProposals ProposerCount (r:1 w:1) - /// Proof: DKGProposals ProposerCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: DKGProposals ExternalProposerAccounts (r:0 w:1) - /// Proof: DKGProposals ExternalProposerAccounts (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn add_proposer() -> Weight { - // Proof Size summary in bytes: - // Measured: `375` - // Estimated: `3023` - // Minimum execution time: 17_000_000 picoseconds. - Weight::from_parts(17_000_000, 3023) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } - /// Storage: DKGProposals Proposers (r:1 w:1) - /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: DKGProposals ProposerCount (r:1 w:1) - /// Proof: DKGProposals ProposerCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: DKGProposals ExternalProposerAccounts (r:0 w:1) - /// Proof: DKGProposals ExternalProposerAccounts (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn remove_proposer() -> Weight { - // Proof Size summary in bytes: - // Measured: `429` - // Estimated: `3023` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 3023) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) - } /// Storage: DKGProposals Proposers (r:1 w:0) /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: DKGProposals ChainNonces (r:1 w:0) @@ -227,36 +195,6 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: DKGProposals Proposers (r:1 w:1) - /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: DKGProposals ProposerCount (r:1 w:1) - /// Proof: DKGProposals ProposerCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: DKGProposals ExternalProposerAccounts (r:0 w:1) - /// Proof: DKGProposals ExternalProposerAccounts (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn add_proposer() -> Weight { - // Proof Size summary in bytes: - // Measured: `375` - // Estimated: `3023` - // Minimum execution time: 17_000_000 picoseconds. - Weight::from_parts(17_000_000, 3023) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } - /// Storage: DKGProposals Proposers (r:1 w:1) - /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) - /// Storage: DKGProposals ProposerCount (r:1 w:1) - /// Proof: DKGProposals ProposerCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: DKGProposals ExternalProposerAccounts (r:0 w:1) - /// Proof: DKGProposals ExternalProposerAccounts (max_values: None, max_size: Some(1050), added: 3525, mode: MaxEncodedLen) - fn remove_proposer() -> Weight { - // Proof Size summary in bytes: - // Measured: `429` - // Estimated: `3023` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 3023) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) - } /// Storage: DKGProposals Proposers (r:1 w:0) /// Proof: DKGProposals Proposers (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// Storage: DKGProposals ChainNonces (r:1 w:0) diff --git a/parachain/runtime/rococo/src/lib.rs b/parachain/runtime/rococo/src/lib.rs index a5336c442..1ae1f6132 100644 --- a/parachain/runtime/rococo/src/lib.rs +++ b/parachain/runtime/rococo/src/lib.rs @@ -32,7 +32,7 @@ use frame_support::weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, ConstantMultiplier, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }; -use pallet_dkg_proposals::DKGEcdsaToEthereum; +use pallet_dkg_proposals::DKGEcdsaToEthereumAddress; use pallet_transaction_payment::{CurrencyAdapter, Multiplier}; use polkadot_runtime_common::SlowAdjustingFeeUpdate; use sp_api::impl_runtime_apis; @@ -489,6 +489,8 @@ parameter_types! { pub const DecayPercentage: Percent = Percent::from_percent(50); pub const UnsignedPriority: u64 = 1 << 20; pub const UnsignedInterval: BlockNumber = 3; + #[derive(Default, Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd, MaxEncodedLen)] + pub const VoteLength: u32 = 64; } impl pallet_dkg_metadata::Config for Runtime { @@ -513,6 +515,8 @@ impl pallet_dkg_metadata::Config for Runtime { type MaxSignatureLength = MaxSignatureLength; type MaxReporters = MaxReporters; type MaxAuthorities = MaxAuthorities; + type ProposerSetView = DKGProposals; + type VoteLength = VoteLength; type WeightInfo = pallet_dkg_metadata::weights::WebbWeight; } @@ -542,14 +546,12 @@ parameter_types! { #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] pub const MaxResources : u32 = 1000; #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxAuthorityProposers : u32 = 1000; - #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxExternalProposerAccounts : u32 = 1000; + pub const MaxProposers : u32 = 1000; } impl pallet_dkg_proposals::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot; - type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereum; + type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereumAddress; type DKGId = DKGId; type ChainIdentifier = ChainIdentifier; type RuntimeEvent = RuntimeEvent; @@ -560,8 +562,8 @@ impl pallet_dkg_proposals::Config for Runtime { type Period = Period; type MaxVotes = MaxVotes; type MaxResources = MaxResources; - type MaxAuthorityProposers = MaxAuthorityProposers; - type MaxExternalProposerAccounts = MaxExternalProposerAccounts; + type MaxProposers = MaxProposers; + type ExternalProposerAccountSize = MaxKeyLength; type WeightInfo = pallet_dkg_proposals::WebbWeight; } @@ -980,6 +982,10 @@ impl_runtime_apis! { fn should_execute_new_keygen() -> bool { DKG::should_execute_new_keygen() } + + fn should_submit_proposer_set_vote() -> bool { + DKG::should_submit_proposer_vote() + } } impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 503e6a501..634846f40 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -36,7 +36,7 @@ use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, }; -use pallet_dkg_proposals::DKGEcdsaToEthereum; +use pallet_dkg_proposals::DKGEcdsaToEthereumAddress; use pallet_election_provider_multi_phase::SolutionAccuracyOf; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, @@ -601,9 +601,11 @@ impl pallet_sudo::Config for Runtime { } parameter_types! { - pub const DecayPercentage: Percent = Percent::from_percent(50); - pub const UnsignedPriority: u64 = 1 << 20; - pub const UnsignedInterval: BlockNumber = 3; + pub const DecayPercentage: Percent = Percent::from_percent(50); + pub const UnsignedPriority: u64 = 1 << 20; + pub const UnsignedInterval: BlockNumber = 3; + #[derive(Default, Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd, MaxEncodedLen)] + pub const VoteLength: u32 = 64; } impl pallet_dkg_metadata::Config for Runtime { @@ -628,6 +630,8 @@ impl pallet_dkg_metadata::Config for Runtime { type MaxSignatureLength = MaxSignatureLength; type MaxReporters = MaxReporters; type MaxAuthorities = MaxAuthorities; + type VoteLength = VoteLength; + type ProposerSetView = DKGProposals; type WeightInfo = pallet_dkg_metadata::weights::WebbWeight; } @@ -657,14 +661,12 @@ parameter_types! { #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] pub const MaxResources : u32 = 1000; #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxAuthorityProposers : u32 = 1000; - #[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, scale_info::TypeInfo, Ord, PartialOrd)] - pub const MaxExternalProposerAccounts : u32 = 1000; + pub const MaxProposers : u32 = 1000; } impl pallet_dkg_proposals::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot; - type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereum; + type DKGAuthorityToMerkleLeaf = DKGEcdsaToEthereumAddress; type DKGId = DKGId; type ChainIdentifier = ChainIdentifier; type RuntimeEvent = RuntimeEvent; @@ -675,8 +677,8 @@ impl pallet_dkg_proposals::Config for Runtime { type Period = Period; type MaxVotes = MaxVotes; type MaxResources = MaxResources; - type MaxAuthorityProposers = MaxAuthorityProposers; - type MaxExternalProposerAccounts = MaxExternalProposerAccounts; + type MaxProposers = MaxProposers; + type ExternalProposerAccountSize = MaxKeyLength; type WeightInfo = pallet_dkg_proposals::WebbWeight; } @@ -1034,6 +1036,10 @@ impl_runtime_apis! { fn should_execute_new_keygen() -> bool { DKG::should_execute_new_keygen() } + + fn should_submit_proposer_set_vote() -> bool { + DKG::should_submit_proposer_vote() + } } impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { From d00750da0e702b2940e8d9848a60e4d8d3205280 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Wed, 21 Jun 2023 14:50:23 -0400 Subject: [PATCH 2/2] Fix more rust errors --- Cargo.lock | 12 + Cargo.toml | 1 + .../async_protocols/blockchain_interface.rs | 9 +- dkg-gadget/src/async_protocols/incoming.rs | 8 +- .../async_protocols/keygen/state_machine.rs | 8 +- dkg-gadget/src/async_protocols/mod.rs | 14 +- .../src/async_protocols/sign/handler.rs | 8 +- .../src/async_protocols/sign/state_machine.rs | 4 +- dkg-gadget/src/async_protocols/test_utils.rs | 12 +- .../gossip_messages/misbehaviour_report.rs | 12 +- dkg-gadget/src/gossip_messages/mod.rs | 1 + .../gossip_messages/proposer_vote_gossip.rs | 209 ++++++++++++++++++ .../src/gossip_messages/public_key_gossip.rs | 10 +- dkg-gadget/src/proposal.rs | 6 +- dkg-gadget/src/storage/mod.rs | 1 + dkg-gadget/src/storage/proposer_votes.rs | 67 ++++++ dkg-gadget/src/storage/public_keys.rs | 2 - dkg-gadget/src/worker.rs | 89 ++++++-- dkg-primitives/src/lib.rs | 2 +- dkg-primitives/src/types.rs | 133 +++-------- dkg-runtime-primitives/Cargo.toml | 1 + dkg-runtime-primitives/src/ethereum_abi.rs | 33 +++ dkg-runtime-primitives/src/gossip_messages.rs | 91 ++++++++ dkg-runtime-primitives/src/lib.rs | 39 +++- .../src/offchain/storage_keys.rs | 6 + dkg-test-orchestrator/src/dummy_api.rs | 29 +-- pallets/dkg-metadata/src/lib.rs | 12 +- pallets/dkg-proposals/src/lib.rs | 2 +- standalone/runtime/src/lib.rs | 2 +- 29 files changed, 618 insertions(+), 205 deletions(-) create mode 100644 dkg-gadget/src/gossip_messages/proposer_vote_gossip.rs create mode 100644 dkg-gadget/src/storage/proposer_votes.rs create mode 100644 dkg-runtime-primitives/src/ethereum_abi.rs create mode 100644 dkg-runtime-primitives/src/gossip_messages.rs diff --git a/Cargo.lock b/Cargo.lock index 735cde78f..88465dfb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1887,6 +1887,7 @@ dependencies = [ name = "dkg-runtime-primitives" version = "0.0.1" dependencies = [ + "ethabi", "ethereum", "ethereum-types", "frame-support", @@ -2255,6 +2256,17 @@ dependencies = [ "libc", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "sha3 0.10.6", +] + [[package]] name = "ethbloom" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index acb1e5477..dbffca491 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ dkg-mock-blockchain = { path = "dkg-mock-blockchain", default-features = false } dkg-test-orchestrator = { path = "dkg-test-orchestrator", default-features = false } futures = "0.3.15" +ethabi = { version = "18.0.0", default-features = false } clap = { version = "4.0.32", features = ["derive"] } rand = "0.8.4" hex-literal = { package = "hex-literal", version = "0.3.3" } diff --git a/dkg-gadget/src/async_protocols/blockchain_interface.rs b/dkg-gadget/src/async_protocols/blockchain_interface.rs index 4809a9837..512a238e1 100644 --- a/dkg-gadget/src/async_protocols/blockchain_interface.rs +++ b/dkg-gadget/src/async_protocols/blockchain_interface.rs @@ -25,13 +25,12 @@ use crate::{ use codec::Encode; use curv::{elliptic::curves::Secp256k1, BigInt}; use dkg_primitives::{ - types::{ - DKGError, DKGMessage, DKGPublicKeyMessage, DKGSignedPayload, SessionId, SignedDKGMessage, - }, + types::{DKGError, DKGMessage, SessionId, SignedDKGMessage}, utils::convert_signature, }; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, + gossip_messages::{DKGSignedPayload, PublicKeyMessage}, AggregatedPublicKeys, AuthoritySet, MaxAuthorities, MaxProposalLength, UnsignedProposal, }; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ @@ -72,7 +71,7 @@ pub trait BlockchainInterface: Send + Sync { batch_key: BatchKey, message: BigInt, ) -> Result<(), DKGError>; - fn gossip_public_key(&self, key: DKGPublicKeyMessage) -> Result<(), DKGError>; + fn gossip_public_key(&self, key: PublicKeyMessage) -> Result<(), DKGError>; fn store_public_key( &self, key: LocalKey, @@ -261,7 +260,7 @@ where Ok(()) } - fn gossip_public_key(&self, key: DKGPublicKeyMessage) -> Result<(), DKGError> { + fn gossip_public_key(&self, key: PublicKeyMessage) -> Result<(), DKGError> { gossip_public_key::( &self.keystore, self.gossip_engine.clone(), diff --git a/dkg-gadget/src/async_protocols/incoming.rs b/dkg-gadget/src/async_protocols/incoming.rs index 26c910f61..1c4a6ecff 100644 --- a/dkg-gadget/src/async_protocols/incoming.rs +++ b/dkg-gadget/src/async_protocols/incoming.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use dkg_primitives::types::{DKGError, DKGMessage, DKGMsgPayload, SessionId, SignedDKGMessage}; +use dkg_primitives::types::{DKGError, DKGMessage, NetworkMsgPayload, SessionId, SignedDKGMessage}; use dkg_runtime_primitives::{crypto::Public, MaxAuthorities}; use futures::Stream; use round_based::Msg; @@ -95,9 +95,9 @@ impl TransformIncoming for Arc> { Self: Sized, { match (stream_type, &self.msg.payload) { - (ProtocolType::Keygen { .. }, DKGMsgPayload::Keygen(..)) | - (ProtocolType::Offline { .. }, DKGMsgPayload::Offline(..)) | - (ProtocolType::Voting { .. }, DKGMsgPayload::Vote(..)) => { + (ProtocolType::Keygen { .. }, NetworkMsgPayload::Keygen(..)) | + (ProtocolType::Offline { .. }, NetworkMsgPayload::Offline(..)) | + (ProtocolType::Voting { .. }, NetworkMsgPayload::Vote(..)) => { // only clone if the downstream receiver expects this type let sender = self .msg diff --git a/dkg-gadget/src/async_protocols/keygen/state_machine.rs b/dkg-gadget/src/async_protocols/keygen/state_machine.rs index 30edb5258..2a4993bd0 100644 --- a/dkg-gadget/src/async_protocols/keygen/state_machine.rs +++ b/dkg-gadget/src/async_protocols/keygen/state_machine.rs @@ -20,8 +20,8 @@ use crate::{ debug_logger::DebugLogger, }; use async_trait::async_trait; -use dkg_primitives::types::{DKGError, DKGMessage, DKGMsgPayload, DKGPublicKeyMessage}; -use dkg_runtime_primitives::{crypto::Public, MaxAuthorities}; +use dkg_primitives::types::{DKGError, DKGMessage, NetworkMsgPayload}; +use dkg_runtime_primitives::{crypto::Public, gossip_messages::PublicKeyMessage, MaxAuthorities}; use futures::channel::mpsc::UnboundedSender; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::{ Keygen, ProtocolMessage, @@ -42,7 +42,7 @@ impl StateMachineHandler for Keygen { let DKGMessage { payload, session_id, .. } = msg.body; // Send the payload to the appropriate AsyncProtocols match payload { - DKGMsgPayload::Keygen(msg) => { + NetworkMsgPayload::Keygen(msg) => { logger.info_keygen(format!( "Handling Keygen inbound message from id={}, session={}", msg.sender_id, session_id @@ -87,7 +87,7 @@ impl StateMachineHandler for Keygen { // public_key_gossip.rs:gossip_public_key [2] store public key locally (public_keys.rs: // store_aggregated_public_keys) let session_id = params.session_id; - let pub_key_msg = DKGPublicKeyMessage { + let pub_key_msg = PublicKeyMessage { session_id, pub_key: local_key.public_key().to_bytes(true).to_vec(), signature: vec![], diff --git a/dkg-gadget/src/async_protocols/mod.rs b/dkg-gadget/src/async_protocols/mod.rs index 9680ef5a2..ac9c728e2 100644 --- a/dkg-gadget/src/async_protocols/mod.rs +++ b/dkg-gadget/src/async_protocols/mod.rs @@ -26,13 +26,13 @@ pub mod test_utils; use curv::elliptic::curves::Secp256k1; use dkg_primitives::{ crypto::Public, - types::{ - DKGError, DKGKeygenMessage, DKGMessage, DKGMsgPayload, DKGMsgStatus, DKGOfflineMessage, - SessionId, - }, + types::{DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SessionId}, AuthoritySet, }; -use dkg_runtime_primitives::{MaxAuthorities, UnsignedProposal}; +use dkg_runtime_primitives::{ + gossip_messages::{DKGKeygenMessage, DKGOfflineMessage}, + MaxAuthorities, UnsignedProposal, +}; use futures::{ channel::mpsc::{UnboundedReceiver, UnboundedSender}, Future, StreamExt, @@ -575,12 +575,12 @@ where None => None, }; let payload = match &proto_ty { - ProtocolType::Keygen { .. } => DKGMsgPayload::Keygen(DKGKeygenMessage { + ProtocolType::Keygen { .. } => NetworkMsgPayload::Keygen(DKGKeygenMessage { sender_id: party_id, keygen_msg: serialized_body, }), ProtocolType::Offline { unsigned_proposal, .. } => - DKGMsgPayload::Offline(DKGOfflineMessage { + NetworkMsgPayload::Offline(DKGOfflineMessage { key: Vec::from( &unsigned_proposal.hash().expect("Cannot hash unsigned proposal!") as &[u8], diff --git a/dkg-gadget/src/async_protocols/sign/handler.rs b/dkg-gadget/src/async_protocols/sign/handler.rs index 02b71c2df..feb8d5ac3 100644 --- a/dkg-gadget/src/async_protocols/sign/handler.rs +++ b/dkg-gadget/src/async_protocols/sign/handler.rs @@ -13,7 +13,7 @@ // limitations under the License. use curv::{arithmetic::Converter, elliptic::curves::Secp256k1, BigInt}; -use dkg_runtime_primitives::UnsignedProposal; +use dkg_runtime_primitives::{gossip_messages::DKGVoteMessage, UnsignedProposal}; use futures::{stream::FuturesUnordered, StreamExt, TryStreamExt}; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::{ keygen::LocalKey, @@ -29,7 +29,7 @@ use crate::async_protocols::{ BatchKey, GenericAsyncHandler, KeygenPartyId, OfflinePartyId, ProtocolType, Threshold, }; use dkg_primitives::types::{ - DKGError, DKGMessage, DKGMsgPayload, DKGMsgStatus, DKGVoteMessage, SignedDKGMessage, + DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SignedDKGMessage, }; use dkg_runtime_primitives::{crypto::Public, MaxAuthorities}; use futures::FutureExt; @@ -217,7 +217,7 @@ where DKGError::GenericError { reason: "Partial signature is invalid".to_string() } })?; - let payload = DKGMsgPayload::Vote(DKGVoteMessage { + let payload = NetworkMsgPayload::Vote(DKGVoteMessage { party_ind: *offline_i.as_ref(), // use the hash of proposal as "round key" ONLY for purposes of ensuring // uniqueness We only want voting to happen amongst voters under the SAME @@ -249,7 +249,7 @@ where )); while let Some(msg) = incoming_wrapper.next().await { - if let DKGMsgPayload::Vote(dkg_vote_msg) = msg.body.payload { + if let NetworkMsgPayload::Vote(dkg_vote_msg) = msg.body.payload { // only process messages which are from the respective proposal if dkg_vote_msg.round_key.as_slice() == hash_of_proposal { params.logger.info_signing("Found matching round key!".to_string()); diff --git a/dkg-gadget/src/async_protocols/sign/state_machine.rs b/dkg-gadget/src/async_protocols/sign/state_machine.rs index 9057d4be1..f2cfd7b4c 100644 --- a/dkg-gadget/src/async_protocols/sign/state_machine.rs +++ b/dkg-gadget/src/async_protocols/sign/state_machine.rs @@ -20,7 +20,7 @@ use crate::{ debug_logger::DebugLogger, }; use async_trait::async_trait; -use dkg_primitives::types::{DKGError, DKGMessage, DKGMsgPayload, SignedDKGMessage}; +use dkg_primitives::types::{DKGError, DKGMessage, NetworkMsgPayload, SignedDKGMessage}; use dkg_runtime_primitives::{crypto::Public, MaxAuthorities, UnsignedProposal}; use futures::channel::mpsc::UnboundedSender; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::sign::{ @@ -52,7 +52,7 @@ impl StateMachineHandler for OfflineStage // Send the payload to the appropriate AsyncProtocols match payload { - DKGMsgPayload::Offline(msg) => { + NetworkMsgPayload::Offline(msg) => { let message: Msg = match serde_json::from_slice(msg.offline_msg.as_slice()) { Ok(msg) => msg, diff --git a/dkg-gadget/src/async_protocols/test_utils.rs b/dkg-gadget/src/async_protocols/test_utils.rs index 3df297846..b135c7d68 100644 --- a/dkg-gadget/src/async_protocols/test_utils.rs +++ b/dkg-gadget/src/async_protocols/test_utils.rs @@ -6,12 +6,14 @@ use crate::{ use codec::Encode; use curv::{elliptic::curves::Secp256k1, BigInt}; use dkg_primitives::{ - types::{ - DKGError, DKGMessage, DKGPublicKeyMessage, DKGSignedPayload, SessionId, SignedDKGMessage, - }, + types::{DKGError, DKGMessage, SessionId, SignedDKGMessage}, utils::convert_signature, }; -use dkg_runtime_primitives::{crypto::Public, MaxProposalLength, UnsignedProposal}; +use dkg_runtime_primitives::{ + crypto::Public, + gossip_messages::{DKGSignedPayload, PublicKeyMessage}, + MaxProposalLength, UnsignedProposal, +}; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::{ party_i::SignatureRecid, state_machine::keygen::LocalKey, }; @@ -87,7 +89,7 @@ impl BlockchainInterface for TestDummyIface { Ok(()) } - fn gossip_public_key(&self, _key: DKGPublicKeyMessage) -> Result<(), DKGError> { + fn gossip_public_key(&self, _key: PublicKeyMessage) -> Result<(), DKGError> { // we do not gossip the public key in the test interface Ok(()) } diff --git a/dkg-gadget/src/gossip_messages/misbehaviour_report.rs b/dkg-gadget/src/gossip_messages/misbehaviour_report.rs index b948defa2..92b6ec420 100644 --- a/dkg-gadget/src/gossip_messages/misbehaviour_report.rs +++ b/dkg-gadget/src/gossip_messages/misbehaviour_report.rs @@ -20,11 +20,11 @@ use crate::{ }; use codec::Encode; use dkg_primitives::types::{ - DKGError, DKGMessage, DKGMisbehaviourMessage, DKGMsgPayload, DKGMsgStatus, SignedDKGMessage, + DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SignedDKGMessage, }; use dkg_runtime_primitives::{ - crypto::AuthorityId, AggregatedMisbehaviourReports, DKGApi, MaxAuthorities, MaxProposalLength, - MaxReporters, MaxSignatureLength, MisbehaviourType, + crypto::AuthorityId, gossip_messages::MisbehaviourMessage, AggregatedMisbehaviourReports, + DKGApi, MaxAuthorities, MaxProposalLength, MaxReporters, MaxSignatureLength, MisbehaviourType, }; use sc_client_api::Backend; use sp_runtime::traits::{Block, Get, NumberFor}; @@ -49,7 +49,7 @@ where return Err(DKGError::NoAuthorityAccounts) } - if let DKGMsgPayload::MisbehaviourBroadcast(msg) = dkg_msg.payload { + if let NetworkMsgPayload::MisbehaviourBroadcast(msg) = dkg_msg.payload { dkg_worker.logger.debug("Received misbehaviour report".to_string()); let is_main_round = { @@ -111,7 +111,7 @@ where pub(crate) fn gossip_misbehaviour_report( dkg_worker: &DKGWorker, - report: DKGMisbehaviourMessage, + report: MisbehaviourMessage, ) -> Result<(), DKGError> where B: Block, @@ -135,7 +135,7 @@ where if let Ok(signature) = dkg_worker.key_store.sign(&public.clone(), &payload) { let encoded_signature = signature.encode(); - let payload = DKGMsgPayload::MisbehaviourBroadcast(DKGMisbehaviourMessage { + let payload = NetworkMsgPayload::MisbehaviourBroadcast(MisbehaviourMessage { signature: encoded_signature.clone(), ..report.clone() }); diff --git a/dkg-gadget/src/gossip_messages/mod.rs b/dkg-gadget/src/gossip_messages/mod.rs index 92cd5063e..7e7b2dcf8 100644 --- a/dkg-gadget/src/gossip_messages/mod.rs +++ b/dkg-gadget/src/gossip_messages/mod.rs @@ -14,4 +14,5 @@ // pub mod dkg_message; pub mod misbehaviour_report; +pub mod proposer_vote_gossip; pub mod public_key_gossip; diff --git a/dkg-gadget/src/gossip_messages/proposer_vote_gossip.rs b/dkg-gadget/src/gossip_messages/proposer_vote_gossip.rs new file mode 100644 index 000000000..b114b8b52 --- /dev/null +++ b/dkg-gadget/src/gossip_messages/proposer_vote_gossip.rs @@ -0,0 +1,209 @@ +use crate::{gossip_engine::GossipEngineIface, worker::AggregatedProposerVotesStore}; +// Copyright 2022 Webb Technologies Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Handles non-dkg messages +use crate::{ + storage::proposer_votes::store_aggregated_proposer_votes, + worker::{DKGWorker, KeystoreExt}, + Client, +}; +use codec::Encode; +use dkg_primitives::types::{ + DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SignedDKGMessage, +}; +use dkg_runtime_primitives::{ + crypto::{AuthorityId, Public}, + ethereum_abi::IntoAbiToken, + gossip_messages::{ProposerVoteMessage, PublicKeyMessage}, + AggregatedProposerVotes, DKGApi, MaxAuthorities, MaxProposalLength, MaxSignatureLength, + MaxVoteLength, MaxVotes, +}; +use sc_client_api::Backend; +use sp_runtime::{ + traits::{Block, Get, Header, NumberFor}, + BoundedVec, +}; + +pub(crate) fn handle_proposer_vote( + dkg_worker: &DKGWorker, + dkg_msg: DKGMessage, +) -> Result<(), DKGError> +where + B: Block, + BE: Backend + 'static, + GE: GossipEngineIface + 'static, + C: Client + 'static, + C::Api: DKGApi, MaxProposalLength, MaxAuthorities>, +{ + // Get authority accounts + let header = &dkg_worker.latest_header.read().clone().ok_or(DKGError::NoHeader)?; + let current_block_number = *header.number(); + let authorities = dkg_worker.validator_set(header).map(|a| (a.0.authorities, a.1.authorities)); + if authorities.is_none() { + return Err(DKGError::NoAuthorityAccounts) + } + + if let NetworkMsgPayload::ProposerVote(msg) = dkg_msg.payload { + dkg_worker + .logger + .debug(format!("SESSION {} | Received proposer vote broadcast", msg.session_id)); + + let is_main_round = { + if let Some(round) = dkg_worker.rounds.read().as_ref() { + msg.session_id == round.session_id + } else { + false + } + }; + + // Create encoded message + let mut encoded_vote_msg = + msg.encode_abi().try_into().map_err(|_| DKGError::InputOutOfBounds)?; + + let session_id = msg.session_id; + let mut lock = dkg_worker.aggregated_proposer_votes.write(); + let votes = lock.entry((msg.session_id, msg.new_governor)).or_insert_with(|| { + AggregatedProposerVotes { + session_id: msg.session_id, + encoded_vote: encoded_vote_msg, + voters: Default::default(), + signatures: Default::default(), + } + }); + // Fetch the current threshold for the DKG. We will use the + // current threshold to determine if we have enough signatures + // to submit the next DKG public key. + let threshold = dkg_worker.get_next_signature_threshold(header) as usize; + dkg_worker.logger.debug(format!( + "SESSION {} | Threshold {} | Aggregated proposer votes {}", + msg.session_id, + threshold, + votes.voters.len() + )); + + if votes.voters.len() > threshold { + store_aggregated_proposer_votes(&dkg_worker, votes)?; + } else { + dkg_worker.logger.debug(format!( + "SESSION {} | Need more signatures to submit next DKG public key, needs {} more", + msg.session_id, + (threshold + 1) - votes.voters.len() + )); + } + } + + Ok(()) +} + +pub(crate) fn gossip_proposer_vote( + dkg_worker: &DKGWorker, + msg: ProposerVoteMessage, +) -> Result<(), DKGError> +where + B: Block, + BE: Backend, + GE: GossipEngineIface, + C: Client, + MaxProposalLength: Get + Clone + Send + Sync + 'static + std::fmt::Debug, + MaxAuthorities: Get + Clone + Send + Sync + 'static + std::fmt::Debug, + C::Api: DKGApi< + B, + AuthorityId, + <::Header as Header>::Number, + MaxProposalLength, + MaxAuthorities, + >, +{ + let public = dkg_worker.key_store.get_authority_public_key(); + let encoded_vote: BoundedVec = + msg.encode_abi().try_into().map_err(|_| DKGError::InputOutOfBounds)?; + + if let Ok(signature) = dkg_worker.key_store.sign(&public, &encoded_vote) { + let encoded_signature = signature.encode(); + let payload = NetworkMsgPayload::ProposerVote(ProposerVoteMessage { + signature: encoded_signature.clone(), + ..msg.clone() + }); + + let status = + if msg.session_id == 0u64 { DKGMsgStatus::ACTIVE } else { DKGMsgStatus::QUEUED }; + let message = DKGMessage:: { + sender_id: public.clone(), + // we need to gossip the final public key to all parties, so no specific recipient in + // this case. + recipient_id: None, + status, + session_id: msg.session_id, + payload, + }; + let encoded_dkg_message = message.encode(); + + crate::utils::inspect_outbound("proposer_vote", encoded_dkg_message.len()); + + match dkg_worker.key_store.sign(&public, &encoded_dkg_message) { + Ok(sig) => { + let signed_dkg_message = + SignedDKGMessage { msg: message, signature: Some(sig.encode()) }; + if let Err(e) = dkg_worker.keygen_gossip_engine.gossip(signed_dkg_message) { + dkg_worker + .keygen_gossip_engine + .logger() + .error(format!("Failed to gossip DKG public key: {e:?}")); + } + }, + Err(e) => dkg_worker.logger.error(format!("🕸️ Error signing DKG message: {e:?}")), + } + + let mut lock = dkg_worker.aggregated_proposer_votes.write(); + let bounded_voters: BoundedVec = + vec![public.clone()].try_into().map_err(|_| DKGError::InputOutOfBounds)?; + let bounded_encoded_sig: BoundedVec = + encoded_signature.clone().try_into().map_err(|_| DKGError::InputOutOfBounds)?; + let bounded_sigs: BoundedVec, MaxAuthorities> = + vec![bounded_encoded_sig.clone()] + .try_into() + .map_err(|_| DKGError::InputOutOfBounds)?; + let votes = lock.entry((msg.session_id, encoded_vote.to_vec())).or_insert_with(|| { + AggregatedProposerVotes { + session_id: msg.session_id, + encoded_vote, + voters: bounded_voters, + signatures: bounded_sigs, + } + }); + + if votes.voters.contains(&public) { + return Ok(()) + } + + votes.voters.try_push(public).map_err(|_| DKGError::InputOutOfBounds)?; + votes + .signatures + .try_push(encoded_signature.try_into().map_err(|_| DKGError::InputOutOfBounds)?) + .map_err(|_| DKGError::InputOutOfBounds)?; + + dkg_worker + .keygen_gossip_engine + .logger() + .debug(format!("Gossiping local node proposer vote for {:?} ", msg)); + Ok(()) + } else { + dkg_worker + .keygen_gossip_engine + .logger() + .error("Could not sign proposer vote".to_string()); + Err(DKGError::CannotSign) + } +} diff --git a/dkg-gadget/src/gossip_messages/public_key_gossip.rs b/dkg-gadget/src/gossip_messages/public_key_gossip.rs index 8f902194a..fec8f7812 100644 --- a/dkg-gadget/src/gossip_messages/public_key_gossip.rs +++ b/dkg-gadget/src/gossip_messages/public_key_gossip.rs @@ -21,11 +21,11 @@ use crate::{ }; use codec::Encode; use dkg_primitives::types::{ - DKGError, DKGMessage, DKGMsgPayload, DKGMsgStatus, DKGPublicKeyMessage, SessionId, - SignedDKGMessage, + DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SessionId, SignedDKGMessage, }; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, + gossip_messages::PublicKeyMessage, AggregatedPublicKeys, DKGApi, MaxAuthorities, MaxProposalLength, }; use sc_client_api::Backend; @@ -51,7 +51,7 @@ where return Err(DKGError::NoAuthorityAccounts) } - if let DKGMsgPayload::PublicKeyBroadcast(msg) = dkg_msg.payload { + if let NetworkMsgPayload::PublicKeyBroadcast(msg) = dkg_msg.payload { dkg_worker .logger .debug(format!("SESSION {} | Received public key broadcast", msg.session_id)); @@ -118,7 +118,7 @@ pub(crate) fn gossip_public_key( key_store: &DKGKeystore, gossip_engine: Arc, aggregated_public_keys: &mut HashMap, - msg: DKGPublicKeyMessage, + msg: PublicKeyMessage, ) where B: Block, BE: Backend, @@ -138,7 +138,7 @@ pub(crate) fn gossip_public_key( if let Ok(signature) = key_store.sign(&public, &msg.pub_key) { let encoded_signature = signature.encode(); - let payload = DKGMsgPayload::PublicKeyBroadcast(DKGPublicKeyMessage { + let payload = NetworkMsgPayload::PublicKeyBroadcast(PublicKeyMessage { signature: encoded_signature.clone(), ..msg.clone() }); diff --git a/dkg-gadget/src/proposal.rs b/dkg-gadget/src/proposal.rs index e894f24f0..896b6547e 100644 --- a/dkg-gadget/src/proposal.rs +++ b/dkg-gadget/src/proposal.rs @@ -15,10 +15,10 @@ use std::sync::Arc; // use crate::{debug_logger::DebugLogger, Client}; use codec::Encode; -use dkg_primitives::types::{DKGError, DKGSignedPayload}; +use dkg_primitives::types::DKGError; use dkg_runtime_primitives::{ - crypto::AuthorityId, offchain::storage_keys::OFFCHAIN_PUBLIC_KEY_SIG, DKGApi, DKGPayloadKey, - RefreshProposalSigned, + crypto::AuthorityId, gossip_messages::DKGSignedPayload, + offchain::storage_keys::OFFCHAIN_PUBLIC_KEY_SIG, DKGApi, DKGPayloadKey, RefreshProposalSigned, }; use sc_client_api::Backend; use sp_api::offchain::STORAGE_PREFIX; diff --git a/dkg-gadget/src/storage/mod.rs b/dkg-gadget/src/storage/mod.rs index ddc559446..e76f3198f 100644 --- a/dkg-gadget/src/storage/mod.rs +++ b/dkg-gadget/src/storage/mod.rs @@ -16,4 +16,5 @@ // limitations under the License. pub mod misbehaviour_reports; pub mod proposals; +pub mod proposer_votes; pub mod public_keys; diff --git a/dkg-gadget/src/storage/proposer_votes.rs b/dkg-gadget/src/storage/proposer_votes.rs new file mode 100644 index 000000000..4f8686f59 --- /dev/null +++ b/dkg-gadget/src/storage/proposer_votes.rs @@ -0,0 +1,67 @@ +// This file is part of Webb. + +// Copyright (C) 2021 Webb Technologies Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{gossip_engine::GossipEngineIface, worker::DKGWorker, Client}; +use codec::Encode; +use dkg_primitives::types::DKGError; +use dkg_runtime_primitives::{ + crypto::AuthorityId, offchain::storage_keys::AGGREGATED_PROPOSER_VOTES, + AggregatedMisbehaviourReports, AggregatedProposerVotes, DKGApi, +}; +use sc_client_api::Backend; +use sp_application_crypto::sp_core::offchain::{OffchainStorage, STORAGE_PREFIX}; +use sp_runtime::traits::{Block, Get, NumberFor}; + +/// stores aggregated proposer votes offchain +pub(crate) fn store_aggregated_proposer_votes< + B, + BE, + C, + GE, + MaxProposalLength, + MaxSignatureLength, + MaxAuthorities, + MaxVoteLength, +>( + dkg_worker: &DKGWorker, + votes: &AggregatedProposerVotes, +) -> Result<(), DKGError> +where + B: Block, + GE: GossipEngineIface + 'static, + BE: Backend, + C: Client, + MaxProposalLength: Get + Clone + Send + Sync + 'static + std::fmt::Debug, + MaxSignatureLength: + Get + Clone + Send + Sync + 'static + scale_info::TypeInfo + std::fmt::Debug, + MaxVoteLength: + Get + Clone + Send + Sync + 'static + scale_info::TypeInfo + std::fmt::Debug, + MaxAuthorities: + Get + Clone + Send + Sync + 'static + scale_info::TypeInfo + std::fmt::Debug, + C::Api: DKGApi, MaxProposalLength, MaxAuthorities>, +{ + let maybe_offchain = dkg_worker.backend.offchain_storage(); + if maybe_offchain.is_none() { + return Err(DKGError::GenericError { reason: "No offchain storage available".to_string() }) + } + + let mut offchain = maybe_offchain.expect("Should never happen, checked above"); + offchain.set(STORAGE_PREFIX, AGGREGATED_PROPOSER_VOTES, &votes.clone().encode()); + dkg_worker + .logger + .trace(format!("Stored aggregated proposer votes {:?}", votes.encode())); + Ok(()) +} diff --git a/dkg-gadget/src/storage/public_keys.rs b/dkg-gadget/src/storage/public_keys.rs index 60d633598..09c2f8aa4 100644 --- a/dkg-gadget/src/storage/public_keys.rs +++ b/dkg-gadget/src/storage/public_keys.rs @@ -62,7 +62,6 @@ where reason: format!("Aggregated public key for session {session_id} does not exist in map"), })?; if is_genesis_round { - //dkg_worker.dkg_state.listening_for_active_pub_key = false; perform_storing_of_aggregated_public_keys::( offchain, keys, @@ -72,7 +71,6 @@ where logger, ); } else { - //dkg_worker.dkg_state.listening_for_pub_key = false; perform_storing_of_aggregated_public_keys::( offchain, keys, diff --git a/dkg-gadget/src/worker.rs b/dkg-gadget/src/worker.rs index f787f2205..f314d09d5 100644 --- a/dkg-gadget/src/worker.rs +++ b/dkg-gadget/src/worker.rs @@ -17,6 +17,7 @@ use crate::{ async_protocols::{blockchain_interface::DKGProtocolEngine, KeygenPartyId}, debug_logger::DebugLogger, + gossip_messages::proposer_vote_gossip::{self, gossip_proposer_vote, handle_proposer_vote}, utils::convert_u16_vec_to_usize_vec, }; use codec::{Codec, Encode}; @@ -47,18 +48,16 @@ use std::{ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use dkg_primitives::{ - types::{ - DKGError, DKGMessage, DKGMisbehaviourMessage, DKGMsgPayload, DKGMsgStatus, SessionId, - SignedDKGMessage, - }, + types::{DKGError, DKGMessage, DKGMsgStatus, NetworkMsgPayload, SessionId, SignedDKGMessage}, AuthoritySetId, DKGReport, MisbehaviourType, }; use dkg_runtime_primitives::{ crypto::{AuthorityId, Public}, + gossip_messages::{MisbehaviourMessage, ProposerVoteMessage}, utils::to_slice_33, - AggregatedMisbehaviourReports, AggregatedPublicKeys, AuthoritySet, DKGApi, MaxAuthorities, - MaxProposalLength, MaxReporters, MaxSignatureLength, UnsignedProposal, - GENESIS_AUTHORITY_SET_ID, KEYGEN_TIMEOUT, + AggregatedMisbehaviourReports, AggregatedProposerVotes, AggregatedPublicKeys, AuthoritySet, + DKGApi, MaxAuthorities, MaxProposalLength, MaxReporters, MaxSignatureLength, MaxVoteLength, + MaxVotes, ProposerVote, UnsignedProposal, GENESIS_AUTHORITY_SET_ID, KEYGEN_TIMEOUT, }; use crate::{ @@ -147,7 +146,10 @@ where pub aggregated_public_keys: Shared>, /// Tracking for the misbehaviour reports pub aggregated_misbehaviour_reports: Shared, - pub misbehaviour_tx: Option>, + /// Tracking for the broadcasting proposer votes in the event of emergency fallback + pub aggregated_proposer_votes: Shared, + /// Misbehaviour sending channel + pub misbehaviour_tx: Option>, /// A HashSet of the currently being signed proposals. /// Note: we only store the hash of the proposal here, not the full proposal. pub currently_signing_proposals: Shared>, @@ -199,6 +201,7 @@ where queued_validator_set: self.queued_validator_set.clone(), aggregated_public_keys: self.aggregated_public_keys.clone(), aggregated_misbehaviour_reports: self.aggregated_misbehaviour_reports.clone(), + aggregated_proposer_votes: self.aggregated_proposer_votes.clone(), misbehaviour_tx: self.misbehaviour_tx.clone(), currently_signing_proposals: self.currently_signing_proposals.clone(), local_keystore: self.local_keystore.clone(), @@ -217,6 +220,11 @@ pub type AggregatedMisbehaviourReportStore = HashMap< AggregatedMisbehaviourReports, >; +pub type AggregatedProposerVotesStore = HashMap< + (SessionId, Vec), + AggregatedProposerVotes, +>; + impl DKGWorker where B: Block + Codec, @@ -268,6 +276,7 @@ where latest_header, aggregated_public_keys: Arc::new(RwLock::new(HashMap::new())), aggregated_misbehaviour_reports: Arc::new(RwLock::new(HashMap::new())), + aggregated_proposer_votes: Arc::new(RwLock::new(HashMap::new())), currently_signing_proposals: Arc::new(RwLock::new(HashSet::new())), local_keystore: Arc::new(RwLock::new(local_keystore)), test_bundle, @@ -946,6 +955,25 @@ where self.logger.error(format!("🕸️ Error submitting unsigned proposals: {e:?}")); } } + + // Attempt to run the proposer set vote protocol in an emergency fallback case + // for any application leveraging the DKG. This process effectively falls back + // to a multi-sig vote if the DKG has a critical issue, requiring the chain + // to `force_change_authorities`. + if self.should_submit_proposer_vote(header) { + self.submit_proposer_vote(header); + } + } + + fn submit_proposer_vote(&self, header: &B::Header) { + let proposer_vote_msg = ProposerVoteMessage { + session_id: todo!(), + proposer_leaf_index: todo!(), + new_governor: todo!(), + proposer_merkle_path: todo!(), + signature: todo!(), + }; + gossip_proposer_vote(self, proposer_vote_msg); } fn maybe_enact_genesis_authorities(&self, header: &B::Header) { @@ -1302,7 +1330,7 @@ where } match &dkg_msg.msg.payload { - DKGMsgPayload::Keygen(_) => { + NetworkMsgPayload::Keygen(_) => { let msg = Arc::new(dkg_msg); if let Some(rounds) = self.rounds.read().as_ref() { if rounds.session_id == msg.msg.session_id { @@ -1328,7 +1356,7 @@ where Ok(()) }, - DKGMsgPayload::Offline(..) | DKGMsgPayload::Vote(..) => { + NetworkMsgPayload::Offline(..) | NetworkMsgPayload::Vote(..) => { let msg = Arc::new(dkg_msg); let async_index = msg.msg.payload.get_async_index(); self.logger.debug(format!("Received message for async index {async_index}")); @@ -1360,7 +1388,7 @@ where } Ok(()) }, - DKGMsgPayload::PublicKeyBroadcast(_) => { + NetworkMsgPayload::PublicKeyBroadcast(_) => { match self.verify_signature_against_authorities(dkg_msg) { Ok(dkg_msg) => { match handle_public_key_broadcast(self, dkg_msg) { @@ -1377,14 +1405,32 @@ where } Ok(()) }, - DKGMsgPayload::MisbehaviourBroadcast(_) => { + NetworkMsgPayload::MisbehaviourBroadcast(_) => { match self.verify_signature_against_authorities(dkg_msg) { Ok(dkg_msg) => { match handle_misbehaviour_report(self, dkg_msg) { Ok(()) => (), - Err(err) => self - .logger - .error(format!("🕸️ Error while handling DKG message {err:?}")), + Err(err) => self.logger.error(format!( + "🕸️ Error while handling misbehaviour message {err:?}" + )), + }; + }, + + Err(err) => self.logger.error(format!( + "Error while verifying signature against authorities: {err:?}" + )), + } + + Ok(()) + }, + NetworkMsgPayload::ProposerVote(_) => { + match self.verify_signature_against_authorities(dkg_msg) { + Ok(dkg_msg) => { + match handle_proposer_vote(self, dkg_msg) { + Ok(()) => (), + Err(err) => self.logger.error(format!( + "🕸️ Error while handling proposer vote message {err:?}" + )), }; }, @@ -1417,7 +1463,7 @@ where }; let misbehaviour_msg = - DKGMisbehaviourMessage { misbehaviour_type, session_id, offender, signature: vec![] }; + MisbehaviourMessage { misbehaviour_type, session_id, offender, signature: vec![] }; let gossip = gossip_misbehaviour_report(self, misbehaviour_msg); if gossip.is_err() { self.logger.info("🕸️ DKG gossip_misbehaviour_report failed!"); @@ -1731,6 +1777,15 @@ where self.client.runtime_api().should_execute_new_keygen(at).unwrap_or_default() } + fn should_submit_proposer_vote(&self, header: &B::Header) -> bool { + // query runtime api to check if we should execute new keygen. + let at = header.hash(); + self.client + .runtime_api() + .should_submit_proposer_set_vote(at) + .unwrap_or_default() + } + /// Wait for initial finalized block async fn initialization(&mut self) { use futures::future; @@ -1839,7 +1894,7 @@ where fn spawn_misbehaviour_report_task( &self, - mut misbehaviour_rx: UnboundedReceiver, + mut misbehaviour_rx: UnboundedReceiver, ) -> tokio::task::JoinHandle<()> { let self_ = self.clone(); tokio::spawn(async move { diff --git a/dkg-primitives/src/lib.rs b/dkg-primitives/src/lib.rs index ab2844c0c..55e02867b 100644 --- a/dkg-primitives/src/lib.rs +++ b/dkg-primitives/src/lib.rs @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// + pub mod dkg_key_cli; pub mod keys; pub mod types; diff --git a/dkg-primitives/src/types.rs b/dkg-primitives/src/types.rs index 9c0c387e8..c3890e131 100644 --- a/dkg-primitives/src/types.rs +++ b/dkg-primitives/src/types.rs @@ -14,7 +14,7 @@ // use codec::{Decode, Encode}; use curv::elliptic::curves::{Point, Scalar, Secp256k1}; -use dkg_runtime_primitives::{crypto::AuthorityId, MisbehaviourType}; +use dkg_runtime_primitives::{gossip_messages::*, SignerSetId}; use multi_party_ecdsa::protocols::multi_party_ecdsa::gg_2020::state_machine::keygen::LocalKey; use sp_runtime::traits::{Block, Hash, Header}; use std::fmt; @@ -24,11 +24,6 @@ pub use dkg_runtime_primitives::SessionId; pub type FE = Scalar; pub type GE = Point; -/// A typedef for signer set id -pub type SignerSetId = u64; - -pub use dkg_runtime_primitives::DKGPayloadKey; - /// DKGMsgStatus Enum identifies if a message is for an active or queued round #[derive(Debug, Copy, Clone, Decode, Encode)] #[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] @@ -52,7 +47,7 @@ pub struct DKGMessage { /// If None, the message is broadcasted to all nodes. pub recipient_id: Option, /// DKG message contents - pub payload: DKGMsgPayload, + pub payload: NetworkMsgPayload, /// Indentifier for the message pub session_id: SessionId, /// enum for active or queued @@ -76,11 +71,12 @@ impl SignedDKGMessage { // in case of Keygen or Offline, we only need to hash the inner raw message bytes that // are going to be sent to the state machine. let bytes_to_hash = match self.msg.payload { - DKGMsgPayload::Keygen(ref m) => m.keygen_msg.clone(), - DKGMsgPayload::Offline(ref m) => m.offline_msg.clone(), - DKGMsgPayload::Vote(ref m) => m.encode(), - DKGMsgPayload::PublicKeyBroadcast(ref m) => m.encode(), - DKGMsgPayload::MisbehaviourBroadcast(ref m) => m.encode(), + NetworkMsgPayload::Keygen(ref m) => m.keygen_msg.clone(), + NetworkMsgPayload::Offline(ref m) => m.offline_msg.clone(), + NetworkMsgPayload::Vote(ref m) => m.encode(), + NetworkMsgPayload::PublicKeyBroadcast(ref m) => m.encode(), + NetworkMsgPayload::MisbehaviourBroadcast(ref m) => m.encode(), + NetworkMsgPayload::ProposerVote(ref m) => m.encode(), }; <::Hashing as Hash>::hash_of(&bytes_to_hash) } @@ -89,11 +85,12 @@ impl SignedDKGMessage { impl fmt::Display for DKGMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let label = match self.payload { - DKGMsgPayload::Keygen(_) => "Keygen", - DKGMsgPayload::Offline(_) => "Offline", - DKGMsgPayload::Vote(_) => "Vote", - DKGMsgPayload::PublicKeyBroadcast(_) => "PublicKeyBroadcast", - DKGMsgPayload::MisbehaviourBroadcast(_) => "MisbehaviourBroadcast", + NetworkMsgPayload::Keygen(_) => "Keygen", + NetworkMsgPayload::Offline(_) => "Offline", + NetworkMsgPayload::Vote(_) => "Vote", + NetworkMsgPayload::PublicKeyBroadcast(_) => "PublicKeyBroadcast", + NetworkMsgPayload::MisbehaviourBroadcast(_) => "MisbehaviourBroadcast", + NetworkMsgPayload::ProposerVote(_) => "ProposerVote", }; write!(f, "DKGMessage of type {label}") } @@ -101,115 +98,45 @@ impl fmt::Display for DKGMessage { #[derive(Debug, Clone, Decode, Encode)] #[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub enum DKGMsgPayload { +pub enum NetworkMsgPayload { Keygen(DKGKeygenMessage), Offline(DKGOfflineMessage), Vote(DKGVoteMessage), - PublicKeyBroadcast(DKGPublicKeyMessage), - MisbehaviourBroadcast(DKGMisbehaviourMessage), + PublicKeyBroadcast(PublicKeyMessage), + MisbehaviourBroadcast(MisbehaviourMessage), + ProposerVote(ProposerVoteMessage), } -impl DKGMsgPayload { - /// NOTE: this is hacky +impl NetworkMsgPayload { /// TODO: Change enums for keygen, offline, vote pub fn async_proto_only_get_sender_id(&self) -> Option { match self { - DKGMsgPayload::Keygen(kg) => Some(kg.sender_id), - DKGMsgPayload::Offline(offline) => Some(offline.signer_set_id as u16), - DKGMsgPayload::Vote(vote) => Some(vote.party_ind), + NetworkMsgPayload::Keygen(kg) => Some(kg.sender_id), + NetworkMsgPayload::Offline(offline) => Some(offline.signer_set_id as u16), + NetworkMsgPayload::Vote(vote) => Some(vote.party_ind), _ => None, } } pub fn get_type(&self) -> &'static str { match self { - DKGMsgPayload::Keygen(_) => "keygen", - DKGMsgPayload::Offline(_) => "offline", - DKGMsgPayload::Vote(_) => "vote", - DKGMsgPayload::PublicKeyBroadcast(_) => "pub_key_broadcast", - DKGMsgPayload::MisbehaviourBroadcast(_) => "misbehaviour", + NetworkMsgPayload::Keygen(_) => "keygen", + NetworkMsgPayload::Offline(_) => "offline", + NetworkMsgPayload::Vote(_) => "vote", + NetworkMsgPayload::PublicKeyBroadcast(_) => "pub_key_broadcast", + NetworkMsgPayload::MisbehaviourBroadcast(_) => "misbehaviour", + NetworkMsgPayload::ProposerVote(_) => "proposer_vote", } } pub fn get_async_index(&self) -> u8 { match self { - DKGMsgPayload::Offline(m) => m.async_index, - DKGMsgPayload::Vote(m) => m.async_index, + NetworkMsgPayload::Offline(m) => m.async_index, + NetworkMsgPayload::Vote(m) => m.async_index, _ => 0, } } } - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGKeygenMessage { - /// Sender id / party index - pub sender_id: u16, - /// Serialized keygen msg - pub keygen_msg: Vec, -} - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGOfflineMessage { - // Identifier - pub key: Vec, - /// Signer set epoch id - pub signer_set_id: SignerSetId, - /// Serialized offline stage msg - pub offline_msg: Vec, - /// Index in async protocols - pub async_index: u8, -} - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGVoteMessage { - /// Party index - pub party_ind: u16, - /// Key for the vote signature created for - pub round_key: Vec, - /// Serialized partial signature - pub partial_signature: Vec, - /// Index in async protocols - pub async_index: u8, -} - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGSignedPayload { - /// Payload key - pub key: Vec, - /// The payload signatures are collected for. - pub payload: Vec, - /// Runtime compatible signature for the payload - pub signature: Vec, -} - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGPublicKeyMessage { - /// Round ID of DKG protocol - pub session_id: SessionId, - /// Public key for the DKG - pub pub_key: Vec, - /// Authority's signature for this public key - pub signature: Vec, -} - -#[derive(Debug, Clone, Decode, Encode)] -#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] -pub struct DKGMisbehaviourMessage { - /// Offending type - pub misbehaviour_type: MisbehaviourType, - /// Misbehaving round - pub session_id: SessionId, - /// Offending authority's id - pub offender: AuthorityId, - /// Authority's signature for this report - pub signature: Vec, -} - pub trait DKGRoundsSM { fn proceed(&mut self, _at: Clock) -> Result { Ok(false) diff --git a/dkg-runtime-primitives/Cargo.toml b/dkg-runtime-primitives/Cargo.toml index 9941de998..c2395529c 100644 --- a/dkg-runtime-primitives/Cargo.toml +++ b/dkg-runtime-primitives/Cargo.toml @@ -10,6 +10,7 @@ repository = { workspace = true } edition = { workspace = true } [dependencies] +ethabi = { workspace = true } impl-trait-for-tuples = { version = "0.2.2", default-features = false } codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive", diff --git a/dkg-runtime-primitives/src/ethereum_abi.rs b/dkg-runtime-primitives/src/ethereum_abi.rs new file mode 100644 index 000000000..3b9984d25 --- /dev/null +++ b/dkg-runtime-primitives/src/ethereum_abi.rs @@ -0,0 +1,33 @@ +use codec::Encode; +use ethabi::{encode, Token}; +use sp_std::{vec, vec::Vec}; + +use crate::gossip_messages::ProposerVoteMessage; + +#[allow(clippy::wrong_self_convention)] +pub trait IntoAbiToken { + fn into_abi(&self) -> Token; + fn encode_abi(&self) -> Vec { + let token = self.into_abi(); + + encode(&[token]) + } +} + +impl IntoAbiToken for [u8; 32] { + fn into_abi(&self) -> Token { + Token::Bytes(self.to_vec()) + } +} + +impl IntoAbiToken for ProposerVoteMessage { + fn into_abi(&self) -> Token { + let tokens = vec![ + Token::Bytes(self.proposer_leaf_index.encode()), + Token::Bytes(self.new_governor.encode()), + ]; + let merkle_path_tokens = + self.proposer_merkle_path.iter().map(|x| x.into_abi()).collect::>(); + Token::Tuple([tokens, merkle_path_tokens].concat()) + } +} diff --git a/dkg-runtime-primitives/src/gossip_messages.rs b/dkg-runtime-primitives/src/gossip_messages.rs new file mode 100644 index 000000000..ba4ddb999 --- /dev/null +++ b/dkg-runtime-primitives/src/gossip_messages.rs @@ -0,0 +1,91 @@ +use crate::{crypto::AuthorityId, MisbehaviourType, SessionId, SignerSetId}; +use codec::{Decode, Encode}; +use sp_std::vec::Vec; + +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct DKGKeygenMessage { + /// Sender id / party index + pub sender_id: u16, + /// Serialized keygen msg + pub keygen_msg: Vec, +} + +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct DKGOfflineMessage { + // Identifier + pub key: Vec, + /// Signer set epoch id + pub signer_set_id: SignerSetId, + /// Serialized offline stage msg + pub offline_msg: Vec, + /// Index in async protocols + pub async_index: u8, +} + +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct DKGVoteMessage { + /// Party index + pub party_ind: u16, + /// Key for the vote signature created for + pub round_key: Vec, + /// Serialized partial signature + pub partial_signature: Vec, + /// Index in async protocols + pub async_index: u8, +} + +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct DKGSignedPayload { + /// Payload key + pub key: Vec, + /// The payload signatures are collected for. + pub payload: Vec, + /// Runtime compatible signature for the payload + pub signature: Vec, +} + +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct PublicKeyMessage { + /// Round ID of DKG protocol + pub session_id: SessionId, + /// Public key for the DKG + pub pub_key: Vec, + /// Authority's signature for this public key + pub signature: Vec, +} + +/// A misbehaviour message for reporting misbehaviour of an authority. +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct MisbehaviourMessage { + /// Offending type + pub misbehaviour_type: MisbehaviourType, + /// Misbehaving round + pub session_id: SessionId, + /// Offending authority's id + pub offender: AuthorityId, + /// Authority's signature for this report + pub signature: Vec, +} + +/// A vote message for voting on a new governor for cross-chain applications leveraging the DKG. +/// https://github.com/webb-tools/protocol-solidity/blob/main/packages/contracts/contracts/utils/Governable.sol#L46-L53C3 +#[derive(Debug, Clone, Decode, Encode)] +#[cfg_attr(feature = "scale-info", derive(scale_info::TypeInfo))] +pub struct ProposerVoteMessage { + /// Round ID of DKG protocol + pub session_id: SessionId, + /// The leaf index of the proposer + pub proposer_leaf_index: u32, + /// The proposed governor + pub new_governor: Vec, + /// The merkle path sibling nodes for the proposer in the proposer set merkle tree + pub proposer_merkle_path: Vec<[u8; 32]>, + /// Authority's signature for this vote + pub signature: Vec, +} diff --git a/dkg-runtime-primitives/src/lib.rs b/dkg-runtime-primitives/src/lib.rs index c786f57e5..efc2701f5 100644 --- a/dkg-runtime-primitives/src/lib.rs +++ b/dkg-runtime-primitives/src/lib.rs @@ -16,11 +16,14 @@ // NOTE: needed to silence warnings about generated code in `decl_runtime_apis` #![allow(clippy::too_many_arguments, clippy::unnecessary_mut_passed)] +pub mod ethereum_abi; +pub mod gossip_messages; pub mod handlers; pub mod offchain; pub mod proposal; pub mod traits; pub mod utils; + pub use crate::proposal::DKGPayloadKey; use codec::{Codec, Decode, Encode, MaxEncodedLen}; use crypto::AuthorityId; @@ -71,6 +74,10 @@ pub fn keccak_256(data: &[u8]) -> [u8; 32] { /// A typedef for keygen set / session id pub type SessionId = u64; + +/// Signer set ID +pub type SignerSetId = u64; + /// The type used to represent an MMR root hash. pub type MmrRootHash = H256; @@ -89,20 +96,20 @@ pub const OFFLINE_TIMEOUT: u32 = 2; /// The sign timeout limit in blocks before we consider misbehaviours pub const SIGN_TIMEOUT: u32 = 2; -// Engine ID for DKG +/// Engine ID for DKG pub const DKG_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"WDKG"; -// Key type for DKG keys +/// Key type for DKG keys pub const KEY_TYPE: sp_application_crypto::KeyTypeId = sp_application_crypto::KeyTypeId(*b"wdkg"); -// Max length for proposals +/// Max length for proposals pub type MaxProposalLength = CustomU32Getter<10_000>; -// Max authorities -pub type MaxAuthorities = CustomU32Getter<100>; +/// Max authorities +pub type MaxAuthorities = CustomU32Getter<1024>; -// Max reporters -pub type MaxReporters = CustomU32Getter<100>; +/// Max reporters +pub type MaxReporters = CustomU32Getter<128>; /// Max size for signatures pub type MaxSignatureLength = CustomU32Getter<512>; @@ -116,6 +123,16 @@ pub type MaxVotes = CustomU32Getter<100>; /// Max resources to store onchain pub type MaxResources = CustomU32Getter<32>; +/// Max size for vote proposal +/// A vote consists of a new address, a leaf index, and a merkle proof +/// [20 bytes, 4 bytes, 32 * HEIGHT bytes] +/// If we want to support up to 1000 validators than the max vote length +/// is 20 + 4 + 32 * 10 = 364 bytes or in bits 2912 +pub type MaxVoteLength = CustomU32Getter<2912>; + +/// Proposer vote type +pub type ProposerVote = BoundedVec; + // Untrack interval for unsigned proposals completed stages for signing pub const UNTRACK_INTERVAL: u32 = 10; @@ -158,7 +175,7 @@ pub struct AggregatedMisbehaviourReports< } #[derive(Eq, PartialEq, Clone, Encode, Decode, Debug, TypeInfo, codec::MaxEncodedLen)] -pub struct AggregatedProposerSetVotes< +pub struct AggregatedProposerVotes< DKGId: AsRef<[u8]>, MaxSignatureLength: Get + Debug + Clone + TypeInfo, MaxVoters: Get + Debug + Clone + TypeInfo, @@ -166,10 +183,8 @@ pub struct AggregatedProposerSetVotes< > { /// The round id the proposer vote is valid for. pub session_id: u64, - /// The proposer - pub proposer: DKGId, - /// The encoded vote - pub vote: BoundedVec, + /// The encoded vote according to the Solidity ABI + pub encoded_vote: ProposerVote, /// A list of voters pub voters: BoundedVec, /// A list of signed encoded votes diff --git a/dkg-runtime-primitives/src/offchain/storage_keys.rs b/dkg-runtime-primitives/src/offchain/storage_keys.rs index c5dbd7a8a..e3d2d062d 100644 --- a/dkg-runtime-primitives/src/offchain/storage_keys.rs +++ b/dkg-runtime-primitives/src/offchain/storage_keys.rs @@ -46,6 +46,12 @@ pub const AGGREGATED_MISBEHAVIOUR_REPORTS: &[u8] = b"dkg-metadata::misbehaviour" // Lock Key for offchain storage of aggregated derived public keys pub const AGGREGATED_MISBEHAVIOUR_REPORTS_LOCK: &[u8] = b"dkg-metadata::misbehaviour::lock"; +// Key for offchain storage of aggregated proposer votes +pub const AGGREGATED_PROPOSER_VOTES: &[u8] = b"dkg-metadata::proposer_votes"; + +// Lock Key for offchain storage of aggregated proposer votes +pub const AGGREGATED_PROPOSER_VOTES_LOCK: &[u8] = b"dkg-metadata::proposer_votes::lock"; + // Lock Key for submitting signed proposal on chain pub const SUBMIT_SIGNED_PROPOSAL_ON_CHAIN_LOCK: &[u8] = b"dkg-proposal-handler::submit_signed_proposal_on_chain::lock"; diff --git a/dkg-test-orchestrator/src/dummy_api.rs b/dkg-test-orchestrator/src/dummy_api.rs index 1bbc2c6a1..a95b39839 100644 --- a/dkg-test-orchestrator/src/dummy_api.rs +++ b/dkg-test-orchestrator/src/dummy_api.rs @@ -1,6 +1,8 @@ use dkg_gadget::debug_logger::DebugLogger; use dkg_mock_blockchain::TestBlock; -use dkg_runtime_primitives::{crypto::AuthorityId, UnsignedProposal}; +use dkg_runtime_primitives::{ + crypto::AuthorityId, MaxAuthorities, MaxProposalLength, UnsignedProposal, +}; use hash_db::HashDB; use parking_lot::RwLock; use sp_api::*; @@ -27,8 +29,7 @@ pub struct DummyApiInner { #[allow(dead_code)] pub signing_n: u16, // maps: block number => list of authorities for that block - pub authority_sets: - HashMap>>, + pub authority_sets: HashMap>, pub dkg_keys: HashMap>, } @@ -513,8 +514,8 @@ impl TestBlock, AuthorityId, sp_api::NumberFor, - dkg_runtime_primitives::CustomU32Getter<10000>, - dkg_runtime_primitives::CustomU32Getter<100>, + MaxProposalLength, + MaxAuthorities, > for DummyApi { fn __runtime_api_internal_call_api_at( @@ -531,12 +532,7 @@ impl fn authority_set( &self, block: H256, - ) -> ApiResult< - dkg_runtime_primitives::AuthoritySet< - AuthorityId, - dkg_runtime_primitives::CustomU32Getter<100>, - >, - > { + ) -> ApiResult> { let number = self.block_id_to_u64(&block); self.logger.info(format!("Getting authority set for block {number}")); let authorities = self.inner.read().authority_sets.get(&number).unwrap().clone(); @@ -548,12 +544,7 @@ impl fn queued_authority_set( &self, id: H256, - ) -> ApiResult< - dkg_runtime_primitives::AuthoritySet< - AuthorityId, - dkg_runtime_primitives::CustomU32Getter<100>, - >, - > { + ) -> ApiResult> { let header = sp_runtime::generic::Header::::new_from_number(self.block_id_to_u64(&id) + 1); self.authority_set(header.hash()) @@ -679,4 +670,8 @@ impl fn should_execute_new_keygen(&self, _: H256) -> ApiResult { Ok(true) } + + fn should_submit_proposer_set_vote(&self, _: H256) -> ApiResult { + Ok(true) + } } diff --git a/pallets/dkg-metadata/src/lib.rs b/pallets/dkg-metadata/src/lib.rs index ffd187796..3fc147ff4 100644 --- a/pallets/dkg-metadata/src/lib.rs +++ b/pallets/dkg-metadata/src/lib.rs @@ -104,9 +104,9 @@ use dkg_runtime_primitives::{ }, traits::{GetDKGPublicKey, GetProposerSet, OnAuthoritySetChangeHandler}, utils::{ecdsa, to_slice_33, verify_signer_from_set_ecdsa}, - AggregatedMisbehaviourReports, AggregatedProposerSetVotes, AggregatedPublicKeys, - AuthorityIndex, AuthoritySet, ConsensusLog, MisbehaviourType, ProposalHandlerTrait, - RefreshProposal, RefreshProposalSigned, DKG_ENGINE_ID, + AggregatedMisbehaviourReports, AggregatedProposerVotes, AggregatedPublicKeys, AuthorityIndex, + AuthoritySet, ConsensusLog, MisbehaviourType, ProposalHandlerTrait, RefreshProposal, + RefreshProposalSigned, DKG_ENGINE_ID, }; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -1271,7 +1271,7 @@ pub mod pallet { #[pallet::call_index(7)] pub fn submit_proposer_set_votes( origin: OriginFor, - votes: AggregatedProposerSetVotes< + votes: AggregatedProposerVotes< T::DKGId, T::MaxSignatureLength, T::MaxReporters, @@ -1650,7 +1650,7 @@ impl Pallet { } pub fn process_proposer_votes( - votes: AggregatedProposerSetVotes< + votes: AggregatedProposerVotes< T::DKGId, T::MaxSignatureLength, T::MaxReporters, @@ -1660,7 +1660,7 @@ impl Pallet { let mut valid_voters = Vec::new(); for (inx, signature) in votes.signatures.iter().enumerate() { let mut signed_payload = Vec::new(); - signed_payload.extend_from_slice(votes.vote.as_ref()); + signed_payload.extend_from_slice(votes.encoded_vote.as_ref()); let previous_proposer_set: Vec = T::ProposerSetView::get_previous_external_proposer_accounts() .iter() diff --git a/pallets/dkg-proposals/src/lib.rs b/pallets/dkg-proposals/src/lib.rs index a094d2a3a..740094c14 100644 --- a/pallets/dkg-proposals/src/lib.rs +++ b/pallets/dkg-proposals/src/lib.rs @@ -851,7 +851,7 @@ impl Pallet { /// It is expected that the size of the returned vector is a power of 2. pub fn pre_process_for_merkleize() -> Vec<[u8; 32]> { let height = Self::get_proposer_set_tree_height(); - // Check for each key that the proposer is valid (should return true) + // Hash the external accounts into 32 byte chunks to form the base layer of the merkle tree let mut base_layer: Vec<[u8; 32]> = ExternalProposerAccounts::::get() .into_iter() .map(|accounts| keccak_256(&accounts.1)) diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 634846f40..42c2513ea 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -20,7 +20,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use dkg_runtime_primitives::{ MaxAuthorities, MaxKeyLength, MaxProposalLength, MaxReporters, MaxSignatureLength, TypedChainId, UnsignedProposal,