Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

pallet-beefy-mmr: add API for BEEFY Authority Sets #11406

Merged
merged 7 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion frame/beefy-mmr/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ hex = { version = "0.4", default-features = false, optional = true }
log = { version = "0.4", default-features = false, optional = true }
tiny-keccak = { version = "2.0.2", features = ["keccak"], optional = true }

beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy" }
sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" }

[dev-dependencies]
env_logger = "0.9"
hex = "0.4"
Expand All @@ -22,4 +25,7 @@ hex-literal = "0.3"
debug = ["hex", "hex/std", "log"]
default = ["debug", "keccak", "std"]
keccak = ["tiny-keccak"]
std = []
std = [
"beefy-primitives/std",
"sp-api/std"
]
17 changes: 17 additions & 0 deletions frame/beefy-mmr/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use beefy_primitives::mmr::{BeefyAuthoritySet, BeefyNextAuthoritySet};

/// Supported hashing output size.
///
/// The size is restricted to 32 bytes to allow for a more optimised implementation.
Expand Down Expand Up @@ -375,6 +377,21 @@ where
}
}

sp_api::decl_runtime_apis! {
/// API useful for BEEFY light clients.
pub trait BeefyMmrApi<H>
where
H: From<Hash> + Into<Hash>,
BeefyAuthoritySet<H>: sp_api::Decode,
{
/// Return the currently active BEEFY authority set proof.
fn authority_set_proof() -> BeefyAuthoritySet<H>;

/// Return the next/queued BEEFY authority set proof.
fn next_authority_set_proof() -> BeefyNextAuthoritySet<H>;
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
67 changes: 48 additions & 19 deletions frame/beefy-mmr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
use sp_runtime::traits::{Convert, Hash, Member};
use sp_std::prelude::*;

use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion};
use beefy_primitives::{
mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion},
ValidatorSet as BeefyValidatorSet,
};
use pallet_mmr::{LeafDataProvider, ParentNumberAndHash};

use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get};
Expand Down Expand Up @@ -124,6 +127,12 @@ pub mod pallet {
type BeefyDataProvider: BeefyDataProvider<Self::LeafExtra>;
}

/// Details of current BEEFY authority set.
#[pallet::storage]
#[pallet::getter(fn beefy_authorities)]
pub type BeefyAuthorities<T: Config> =
StorageValue<_, BeefyAuthoritySet<MerkleRootOf<T>>, ValueQuery>;

/// Details of next BEEFY authority set.
///
/// This storage entry is used as cache for calls to `update_beefy_next_authority_set`.
Expand All @@ -149,7 +158,7 @@ where
version: T::LeafVersion::get(),
parent_number_and_hash: ParentNumberAndHash::<T>::leaf_data(),
leaf_extra: T::BeefyDataProvider::extra_data(),
beefy_next_authority_set: Pallet::<T>::update_beefy_next_authority_set(),
beefy_next_authority_set: Pallet::<T>::beefy_next_authorities(),
}
}
}
Expand All @@ -163,35 +172,55 @@ where
}
}

impl<T> beefy_primitives::OnNewValidatorSet<<T as pallet_beefy::Config>::BeefyId> for Pallet<T>
where
T: pallet::Config,
MerkleRootOf<T>: From<beefy_merkle_tree::Hash> + Into<beefy_merkle_tree::Hash>,
{
/// Compute and cache BEEFY authority sets based on updated BEEFY validator sets.
fn on_new_validator_set(
current_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
next_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
) {
let current = Pallet::<T>::compute_authority_set(current_set);
let next = Pallet::<T>::compute_authority_set(next_set);
// cache the result
BeefyAuthorities::<T>::put(&current);
BeefyNextAuthorities::<T>::put(&next);
}
}

impl<T: Config> Pallet<T>
where
MerkleRootOf<T>: From<beefy_merkle_tree::Hash> + Into<beefy_merkle_tree::Hash>,
{
/// Returns details of the next BEEFY authority set.
/// Return the currently active BEEFY authority set proof.
pub fn authority_set_proof() -> BeefyAuthoritySet<MerkleRootOf<T>> {
Pallet::<T>::beefy_authorities()
}

/// Return the next/queued BEEFY authority set proof.
pub fn next_authority_set_proof() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
Pallet::<T>::beefy_next_authorities()
}

/// Returns details of a BEEFY authority set.
///
/// Details contain authority set id, authority set length and a merkle root,
/// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses
/// of the next BEEFY authority set.
///
/// This function will use a storage-cached entry in case the set didn't change, or compute and
/// cache new one in case it did.
fn update_beefy_next_authority_set() -> BeefyNextAuthoritySet<MerkleRootOf<T>> {
let id = pallet_beefy::Pallet::<T>::validator_set_id() + 1;
let current_next = Self::beefy_next_authorities();
// avoid computing the merkle tree if validator set id didn't change.
if id == current_next.id {
return current_next
}

let beefy_addresses = pallet_beefy::Pallet::<T>::next_authorities()
fn compute_authority_set(
validator_set: &BeefyValidatorSet<<T as pallet_beefy::Config>::BeefyId>,
) -> BeefyAuthoritySet<MerkleRootOf<T>> {
let id = validator_set.id();
let beefy_addresses = validator_set
.validators()
.into_iter()
.cloned()
.map(T::BeefyAuthorityToMerkleLeaf::convert)
.collect::<Vec<_>>();
let len = beefy_addresses.len() as u32;
let root = beefy_merkle_tree::merkle_root::<Self, _, _>(beefy_addresses).into();
let next_set = BeefyNextAuthoritySet { id, len, root };
// cache the result
BeefyNextAuthorities::<T>::put(&next_set);
next_set
BeefyAuthoritySet { id, len, root }
}
}
1 change: 1 addition & 0 deletions frame/beefy-mmr/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ impl pallet_mmr::Config for Test {
impl pallet_beefy::Config for Test {
type BeefyId = BeefyId;
type MaxAuthorities = ConstU32<100>;
type OnNewValidatorSet = BeefyMmr;
}

parameter_types! {
Expand Down
50 changes: 50 additions & 0 deletions frame/beefy-mmr/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,53 @@ fn should_contain_valid_leaf_data() {
}
);
}

#[test]
fn should_update_authorities() {
new_test_ext(vec![1, 2, 3, 4]).execute_with(|| {
let auth_set = BeefyMmr::authority_set_proof();
let next_auth_set = BeefyMmr::next_authority_set_proof();

// check current authority set
assert_eq!(0, auth_set.id);
assert_eq!(2, auth_set.len);
let want: H256 =
hex!("176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96").into();
assert_eq!(want, auth_set.root);

// next authority set should have same validators but different id
assert_eq!(1, next_auth_set.id);
assert_eq!(auth_set.len, next_auth_set.len);
assert_eq!(auth_set.root, next_auth_set.root);

let announced_set = next_auth_set;
init_block(1);
let auth_set = BeefyMmr::authority_set_proof();
let next_auth_set = BeefyMmr::next_authority_set_proof();

// check new auth are expected ones
assert_eq!(announced_set, auth_set);
assert_eq!(1, auth_set.id);
// check next auth set
assert_eq!(2, next_auth_set.id);
let want: H256 =
hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5").into();
assert_eq!(2, next_auth_set.len);
assert_eq!(want, next_auth_set.root);

let announced_set = next_auth_set;
init_block(2);
let auth_set = BeefyMmr::authority_set_proof();
let next_auth_set = BeefyMmr::next_authority_set_proof();

// check new auth are expected ones
assert_eq!(announced_set, auth_set);
assert_eq!(2, auth_set.id);
// check next auth set
assert_eq!(3, next_auth_set.id);
let want: H256 =
hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5").into();
assert_eq!(2, next_auth_set.len);
assert_eq!(want, next_auth_set.root);
});
}
51 changes: 41 additions & 10 deletions frame/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ use sp_runtime::{
};
use sp_std::prelude::*;

use beefy_primitives::{AuthorityIndex, ConsensusLog, ValidatorSet, BEEFY_ENGINE_ID};
use beefy_primitives::{
AuthorityIndex, ConsensusLog, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID,
};

#[cfg(test)]
mod mock;
Expand All @@ -58,6 +60,13 @@ pub mod pallet {

/// The maximum number of authorities that can be added.
type MaxAuthorities: Get<u32>;

/// A hook to act on the new BEEFY validator set.
///
/// For some applications it might be beneficial to make the BEEFY validator set available
/// externally apart from having it in the storage. For instance you might cache a light
/// weight MMR root over validators and make it available for Light Clients.
type OnNewValidatorSet: OnNewValidatorSet<<Self as Config>::BeefyId>;
}

#[pallet::pallet]
Expand Down Expand Up @@ -118,20 +127,29 @@ impl<T: Config> Pallet<T> {
) {
<Authorities<T>>::put(&new);

let next_id = Self::validator_set_id() + 1u64;
<ValidatorSetId<T>>::put(next_id);
if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, next_id) {
let new_id = Self::validator_set_id() + 1u64;
<ValidatorSetId<T>>::put(new_id);

<NextAuthorities<T>>::put(&queued);

if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
let log = DigestItem::Consensus(
BEEFY_ENGINE_ID,
ConsensusLog::AuthoritiesChange(validator_set).encode(),
ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
);
<frame_system::Pallet<T>>::deposit_log(log);
}

<NextAuthorities<T>>::put(&queued);
let next_id = new_id + 1;
if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
<T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
&validator_set,
&next_validator_set,
);
}
}
}

fn initialize_authorities(authorities: &[T::BeefyId]) -> Result<(), ()> {
fn initialize_authorities(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
if authorities.is_empty() {
return Ok(())
}
Expand All @@ -141,12 +159,25 @@ impl<T: Config> Pallet<T> {
}

let bounded_authorities =
BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities)?;
BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())?;

let id = 0;
<Authorities<T>>::put(bounded_authorities);
<ValidatorSetId<T>>::put(0);
<ValidatorSetId<T>>::put(id);
// Like `pallet_session`, initialize the next validator set as well.
<NextAuthorities<T>>::put(bounded_authorities);

if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
let next_id = id + 1;
if let Some(next_validator_set) =
ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
{
<T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
&validator_set,
&next_validator_set,
);
}
}
Ok(())
}
}
Expand Down
1 change: 1 addition & 0 deletions frame/beefy/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ impl frame_system::Config for Test {
impl pallet_beefy::Config for Test {
type BeefyId = BeefyId;
type MaxAuthorities = ConstU32<100>;
type OnNewValidatorSet = ();
}

parameter_types! {
Expand Down
14 changes: 14 additions & 0 deletions primitives/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ pub struct VoteMessage<Number, Id, Signature> {
pub signature: Signature,
}

/// New BEEFY validator set notification hook.
pub trait OnNewValidatorSet<AuthorityId> {
/// Function called by the pallet when BEEFY validator set changes.
fn on_new_validator_set(
validator_set: &ValidatorSet<AuthorityId>,
next_validator_set: &ValidatorSet<AuthorityId>,
);
}

/// No-op implementation of [OnNewValidatorSet].
impl<AuthorityId> OnNewValidatorSet<AuthorityId> for () {
fn on_new_validator_set(_: &ValidatorSet<AuthorityId>, _: &ValidatorSet<AuthorityId>) {}
}

sp_api::decl_runtime_apis! {
/// API necessary for BEEFY voters.
pub trait BeefyApi
Expand Down
13 changes: 8 additions & 5 deletions primitives/beefy/src/mmr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,29 +95,32 @@ impl MmrLeafVersion {
}
}

/// Details of the next BEEFY authority set.
/// Details of a BEEFY authority set.
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub struct BeefyNextAuthoritySet<MerkleRoot> {
/// Id of the next set.
pub struct BeefyAuthoritySet<MerkleRoot> {
/// Id of the set.
///
/// Id is required to correlate BEEFY signed commitments with the validator set.
/// Light Client can easily verify that the commitment witness it is getting is
/// produced by the latest validator set.
pub id: crate::ValidatorSetId,
/// Number of validators in the set.
///
/// Some BEEFY Light Clients may use an interactive protocol to verify only subset
/// Some BEEFY Light Clients may use an interactive protocol to verify only a subset
/// of signatures. We put set length here, so that these clients can verify the minimal
/// number of required signatures.
pub len: u32,
/// Merkle Root Hash build from BEEFY AuthorityIds.
/// Merkle Root Hash built from BEEFY AuthorityIds.
///
/// This is used by Light Clients to confirm that the commitments are signed by the correct
/// validator set. Light Clients using interactive protocol, might verify only subset of
/// signatures, hence don't require the full list here (will receive inclusion proofs).
pub root: MerkleRoot,
}

/// Details of the next BEEFY authority set.
pub type BeefyNextAuthoritySet<MerkleRoot> = BeefyAuthoritySet<MerkleRoot>;

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading