Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposer selection upgrade and proposer set voting protocol in fallback case #633

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Modify proposers to only come from validator set
  • Loading branch information
drewstone committed Jun 9, 2023
commit e28294a12a0386eb67739efe3146bf9ae30b648f
23 changes: 22 additions & 1 deletion dkg-runtime-primitives/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<BoundedVec<u8, MaxSignatureLength>, MaxReporters>,
}

#[derive(Eq, PartialEq, Clone, Encode, Decode, Debug, TypeInfo, codec::MaxEncodedLen)]
pub struct AggregatedProposerSetVotes<
DKGId: AsRef<[u8]>,
MaxSignatureLength: Get<u32> + Debug + Clone + TypeInfo,
MaxVoters: Get<u32> + Debug + Clone + TypeInfo,
VoteLength: Get<u32> + 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<u8, VoteLength>,
/// A list of voters
pub voters: BoundedVec<DKGId, MaxVoters>,
/// A list of signed encoded votes
pub signatures: BoundedVec<BoundedVec<u8, MaxSignatureLength>, MaxVoters>,
}

impl<BlockNumber, MaxLength: Get<u32>> Default for OffchainSignedProposals<BlockNumber, MaxLength> {
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;
}
}
27 changes: 19 additions & 8 deletions dkg-runtime-primitives/src/traits.rs
Original file line number Diff line number Diff line change
@@ -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<AccountId, AuthoritySetId, AuthorityId> {
fn on_authority_set_changed(
authority_accounts: Vec<AccountId>,
authority_ids: Vec<AuthorityId>,
);
fn on_authority_set_changed(authority_accounts: &[AccountId], authority_ids: &[AuthorityId]);
}

impl<AccountId, AuthoritySetId, AuthorityId>
OnAuthoritySetChangeHandler<AccountId, AuthoritySetId, AuthorityId> for ()
{
fn on_authority_set_changed(
_authority_accounts: Vec<AccountId>,
_authority_ids: Vec<AuthorityId>,
) {
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<u8>;
fn previous_dkg_key() -> Vec<u8>;
}

/// A trait for fetching the current proposer set.
pub trait GetProposerSet<AccountId, Bound> {
fn get_previous_proposer_set() -> Vec<AccountId>;
fn get_previous_external_proposer_accounts() -> Vec<(AccountId, BoundedVec<u8, Bound>)>;
}

impl<A, B> GetProposerSet<A, B> for () {
fn get_previous_proposer_set() -> Vec<A> {
Vec::new()
}
fn get_previous_external_proposer_accounts() -> Vec<(A, BoundedVec<u8, B>)> {
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.
120 changes: 107 additions & 13 deletions pallets/dkg-metadata/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Self::AccountId, Self::MaxKeyLength>;

/// A type that gives allows the pallet access to the session progress
type NextSessionRotation: EstimateNextSessionRotation<Self::BlockNumber>;

@@ -294,6 +299,19 @@ pub mod pallet {
+ PartialOrd
+ Ord;

/// Length of encoded proposer vote
#[pallet::constant]
type VoteLength: Get<u32>
+ Default
+ TypeInfo
+ MaxEncodedLen
+ Debug
+ Clone
+ Eq
+ PartialEq
+ PartialOrd
+ Ord;

/// The origin which may forcibly reset parameters or otherwise alter
/// privileged attributes.
type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
@@ -429,6 +447,11 @@ pub mod pallet {
#[pallet::getter(fn should_execute_new_keygen)]
pub type ShouldExecuteNewKeygen<T: Config> = 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<T: Config> = 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<T::DKGId>,
offender: T::DKGId,
},
/// Proposer votes submitted
ProposerSetVotesSubmitted { voters: Vec<T::DKGId>, signatures: Vec<Vec<u8>>, vote: Vec<u8> },
/// 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::<T>::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<T>,
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(<T as Config>::WeightInfo::unjail())]
#[pallet::call_index(7)]
#[pallet::call_index(8)]
pub fn unjail(origin: OriginFor<T>) -> 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(<T as Config>::WeightInfo::force_unjail_keygen())]
#[pallet::call_index(8)]
#[pallet::call_index(9)]
pub fn force_unjail_keygen(
origin: OriginFor<T>,
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(<T as Config>::WeightInfo::force_unjail_signing())]
#[pallet::call_index(9)]
#[pallet::call_index(10)]
pub fn force_unjail_signing(
origin: OriginFor<T>,
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<T>) -> DispatchResultWithPostInfo {
T::ForceOrigin::ensure_origin(origin)?;
let next_authorities = NextAuthorities::<T>::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::<T>::put(true);
ShouldSubmitProposerVote::<T>::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<T>) -> 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<T: Config> Pallet<T> {
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<ecdsa::Public> = 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<T: Config> Pallet<T> {
valid_reporters
}

pub fn process_proposer_votes(
votes: AggregatedProposerSetVotes<
T::DKGId,
T::MaxSignatureLength,
T::MaxReporters,
T::VoteLength,
>,
) -> Vec<T::DKGId> {
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<ecdsa::Public> =
T::ProposerSetView::get_previous_external_proposer_accounts()
.iter()
.map(|x: &(T::AccountId, BoundedVec<u8, T::MaxKeyLength>)| {
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<T::DKGId, T::MaxAuthorities>,
next_authority_ids: BoundedVec<T::DKGId, T::MaxAuthorities>,
@@ -1628,7 +1722,7 @@ impl<T: Config> Pallet<T> {
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::<T>::put(false);
// Update the next thresholds for the next session
@@ -1801,7 +1895,7 @@ impl<T: Config> Pallet<T> {
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
8 changes: 8 additions & 0 deletions pallets/dkg-metadata/src/mock.rs
Original file line number Diff line number Diff line change
@@ -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 = ();
}

Loading