use super::{ecdsa_crypto::AuthorityId, ConsensusLog, MmrRootHash, BEEFY_ENGINE_ID};
use crate::runtime::{
generic::OpaqueDigestItemId,
traits::{Block, Header},
};
use alloc::vec::Vec;
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
pub trait BeefyDataProvider<ExtraData> {
fn extra_data() -> ExtraData;
}
impl BeefyDataProvider<Vec<u8>> for () {
fn extra_data() -> Vec<u8> {
Vec::new()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub struct MmrLeaf<BlockNumber, Hash, MerkleRoot, ExtraData> {
pub version: MmrLeafVersion,
pub parent_number_and_hash: (BlockNumber, Hash),
pub beefy_next_authority_set: BeefyNextAuthoritySet<MerkleRoot>,
pub leaf_extra: ExtraData,
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
pub struct MmrLeafVersion(u8);
impl MmrLeafVersion {
pub fn new(major: u8, minor: u8) -> Self {
if major > 0b111 || minor > 0b11111 {
panic!("Version components are too big.");
}
let version = (major << 5) + minor;
Self(version)
}
pub fn split(&self) -> (u8, u8) {
let major = self.0 >> 5;
let minor = self.0 & 0b11111;
(major, minor)
}
}
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BeefyAuthoritySet<AuthoritySetCommitment> {
pub id: super::ValidatorSetId,
pub len: u32,
pub keyset_commitment: AuthoritySetCommitment,
}
pub type BeefyNextAuthoritySet<MerkleRoot> = BeefyAuthoritySet<MerkleRoot>;
pub fn find_mmr_root_digest<B: Block>(header: &B::Header) -> Option<MmrRootHash> {
let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID);
let filter = |log: ConsensusLog<AuthorityId>| match log {
ConsensusLog::MmrRoot(root) => Some(root),
_ => None,
};
header.digest().convert_first(|l| l.try_to(id).and_then(filter))
}
#[cfg(feature = "std")]
pub use mmr_root_provider::MmrRootProvider;
#[cfg(feature = "std")]
mod mmr_root_provider {
use super::*;
use crate::api::ProvideRuntimeApi;
use crate::consensus::beefy::{known_payloads, payload::PayloadProvider, Payload};
use crate::mmr::MmrApi;
use crate::runtime::traits::NumberFor;
use alloc::sync::Arc;
use core::marker::PhantomData;
pub struct MmrRootProvider<B, R> {
runtime: Arc<R>,
_phantom: PhantomData<B>,
}
impl<B, R> Clone for MmrRootProvider<B, R> {
fn clone(&self) -> Self {
Self { runtime: self.runtime.clone(), _phantom: PhantomData }
}
}
impl<B, R> MmrRootProvider<B, R>
where
B: Block,
R: ProvideRuntimeApi<B>,
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
{
pub fn new(runtime: Arc<R>) -> Self {
Self { runtime, _phantom: PhantomData }
}
fn mmr_root_from_digest_or_runtime(&self, header: &B::Header) -> Option<MmrRootHash> {
find_mmr_root_digest::<B>(header).or_else(|| {
self.runtime.runtime_api().mmr_root(header.hash()).ok().and_then(|r| r.ok())
})
}
}
impl<B: Block, R> PayloadProvider<B> for MmrRootProvider<B, R>
where
B: Block,
R: ProvideRuntimeApi<B>,
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
{
fn payload(&self, header: &B::Header) -> Option<Payload> {
self.mmr_root_from_digest_or_runtime(header).map(|mmr_root| {
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode())
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::H256;
use crate::runtime::{traits::BlakeTwo256, Digest, DigestItem, OpaqueExtrinsic};
#[test]
fn should_construct_version_correctly() {
let tests = vec![(0, 0, 0b00000000), (7, 2, 0b11100010), (7, 31, 0b11111111)];
for (major, minor, version) in tests {
let v = MmrLeafVersion::new(major, minor);
assert_eq!(v.encode(), vec![version], "Encoding does not match.");
assert_eq!(v.split(), (major, minor));
}
}
#[test]
#[should_panic]
fn should_panic_if_major_too_large() {
MmrLeafVersion::new(8, 0);
}
#[test]
#[should_panic]
fn should_panic_if_minor_too_large() {
MmrLeafVersion::new(0, 32);
}
#[test]
fn extract_mmr_root_digest() {
type Header = crate::runtime::generic::Header<u64, BlakeTwo256>;
type Block = crate::runtime::generic::Block<Header, OpaqueExtrinsic>;
let mut header = Header::new(
1u64,
Default::default(),
Default::default(),
Default::default(),
Digest::default(),
);
assert!(find_mmr_root_digest::<Block>(&header).is_none());
let mmr_root_hash = H256::random();
header.digest_mut().push(DigestItem::Consensus(
BEEFY_ENGINE_ID,
ConsensusLog::<AuthorityId>::MmrRoot(mmr_root_hash).encode(),
));
let extracted = find_mmr_root_digest::<Block>(&header);
assert_eq!(extracted, Some(mmr_root_hash));
}
}