#[cfg(feature = "serde")]
use serde::Serialize;
#[cfg(feature = "std")]
use crate::keystore::KeystorePtr;
use crate::runtime::{
traits::{Header as HeaderT, NumberFor},
ConsensusEngineId, Debug, OpaqueValue,
};
use alloc::vec::Vec;
use codec::{Codec, Decode, DecodeWithMemTracking, Encode};
use scale_info::TypeInfo;
pub const CLIENT_LOG_TARGET: &str = "grandpa";
pub const RUNTIME_LOG_TARGET: &str = "runtime::grandpa";
pub const KEY_TYPE: crate::core::crypto::KeyTypeId = crate::application_crypto::key_types::GRANDPA;
mod app {
use crate::application_crypto::{ed25519, key_types::GRANDPA};
crate::app_crypto!(ed25519, GRANDPA);
}
crate::with_pair! {
pub type AuthorityPair = app::Pair;
}
pub type AuthorityId = app::Public;
pub type AuthoritySignature = app::Signature;
pub const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK";
pub type AuthorityWeight = u64;
pub type AuthorityIndex = u64;
pub type SetId = u64;
pub type RoundNumber = u64;
pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>;
pub type Message<Header> =
finality_grandpa::Message<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
pub type SignedMessage<Header> = finality_grandpa::SignedMessage<
<Header as HeaderT>::Hash,
<Header as HeaderT>::Number,
AuthoritySignature,
AuthorityId,
>;
pub type PrimaryPropose<Header> =
finality_grandpa::PrimaryPropose<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
pub type Prevote<Header> =
finality_grandpa::Prevote<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
pub type Precommit<Header> =
finality_grandpa::Precommit<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
pub type CatchUp<Header> = finality_grandpa::CatchUp<
<Header as HeaderT>::Hash,
<Header as HeaderT>::Number,
AuthoritySignature,
AuthorityId,
>;
pub type Commit<Header> = finality_grandpa::Commit<
<Header as HeaderT>::Hash,
<Header as HeaderT>::Number,
AuthoritySignature,
AuthorityId,
>;
pub type CompactCommit<Header> = finality_grandpa::CompactCommit<
<Header as HeaderT>::Hash,
<Header as HeaderT>::Number,
AuthoritySignature,
AuthorityId,
>;
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct GrandpaJustification<Header: HeaderT> {
pub round: u64,
pub commit: Commit<Header>,
pub votes_ancestries: Vec<Header>,
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct ScheduledChange<N> {
pub next_authorities: AuthorityList,
pub delay: N,
}
#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub enum ConsensusLog<N: Codec> {
#[codec(index = 1)]
ScheduledChange(ScheduledChange<N>),
#[codec(index = 2)]
ForcedChange(N, ScheduledChange<N>),
#[codec(index = 3)]
OnDisabled(AuthorityIndex),
#[codec(index = 4)]
Pause(N),
#[codec(index = 5)]
Resume(N),
}
impl<N: Codec> ConsensusLog<N> {
pub fn try_into_change(self) -> Option<ScheduledChange<N>> {
match self {
ConsensusLog::ScheduledChange(change) => Some(change),
_ => None,
}
}
pub fn try_into_forced_change(self) -> Option<(N, ScheduledChange<N>)> {
match self {
ConsensusLog::ForcedChange(median, change) => Some((median, change)),
_ => None,
}
}
pub fn try_into_pause(self) -> Option<N> {
match self {
ConsensusLog::Pause(delay) => Some(delay),
_ => None,
}
}
pub fn try_into_resume(self) -> Option<N> {
match self {
ConsensusLog::Resume(delay) => Some(delay),
_ => None,
}
}
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, Eq, TypeInfo)]
pub struct EquivocationProof<H, N> {
set_id: SetId,
equivocation: Equivocation<H, N>,
}
impl<H, N> EquivocationProof<H, N> {
pub fn new(set_id: SetId, equivocation: Equivocation<H, N>) -> Self {
EquivocationProof { set_id, equivocation }
}
pub fn set_id(&self) -> SetId {
self.set_id
}
pub fn round(&self) -> RoundNumber {
match self.equivocation {
Equivocation::Prevote(ref equivocation) => equivocation.round_number,
Equivocation::Precommit(ref equivocation) => equivocation.round_number,
}
}
pub fn offender(&self) -> &AuthorityId {
self.equivocation.offender()
}
}
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, Eq, TypeInfo)]
pub enum Equivocation<H, N> {
Prevote(
finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Prevote<H, N>,
AuthoritySignature,
>,
),
Precommit(
finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Precommit<H, N>,
AuthoritySignature,
>,
),
}
impl<H, N>
From<
finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Prevote<H, N>,
AuthoritySignature,
>,
> for Equivocation<H, N>
{
fn from(
equivocation: finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Prevote<H, N>,
AuthoritySignature,
>,
) -> Self {
Equivocation::Prevote(equivocation)
}
}
impl<H, N>
From<
finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Precommit<H, N>,
AuthoritySignature,
>,
> for Equivocation<H, N>
{
fn from(
equivocation: finality_grandpa::Equivocation<
AuthorityId,
finality_grandpa::Precommit<H, N>,
AuthoritySignature,
>,
) -> Self {
Equivocation::Precommit(equivocation)
}
}
impl<H, N> Equivocation<H, N> {
pub fn offender(&self) -> &AuthorityId {
match self {
Equivocation::Prevote(ref equivocation) => &equivocation.identity,
Equivocation::Precommit(ref equivocation) => &equivocation.identity,
}
}
pub fn round_number(&self) -> RoundNumber {
match self {
Equivocation::Prevote(ref equivocation) => equivocation.round_number,
Equivocation::Precommit(ref equivocation) => equivocation.round_number,
}
}
}
pub fn check_equivocation_proof<H, N>(report: EquivocationProof<H, N>) -> bool
where
H: Clone + Encode + PartialEq,
N: Clone + Encode + PartialEq,
{
macro_rules! check {
( $equivocation:expr, $message:expr ) => {
if $equivocation.first.0.target_hash == $equivocation.second.0.target_hash
&& $equivocation.first.0.target_number == $equivocation.second.0.target_number
{
return false;
}
let valid_first = check_message_signature(
&$message($equivocation.first.0),
&$equivocation.identity,
&$equivocation.first.1,
$equivocation.round_number,
report.set_id,
)
.is_valid();
let valid_second = check_message_signature(
&$message($equivocation.second.0),
&$equivocation.identity,
&$equivocation.second.1,
$equivocation.round_number,
report.set_id,
)
.is_valid();
return valid_first && valid_second
};
}
match report.equivocation {
Equivocation::Prevote(equivocation) => {
check!(equivocation, finality_grandpa::Message::Prevote);
},
Equivocation::Precommit(equivocation) => {
check!(equivocation, finality_grandpa::Message::Precommit);
},
}
}
pub fn localized_payload<E: Encode>(round: RoundNumber, set_id: SetId, message: &E) -> Vec<u8> {
let mut buf = Vec::new();
localized_payload_with_buffer(round, set_id, message, &mut buf);
buf
}
pub fn localized_payload_with_buffer<E: Encode>(
round: RoundNumber,
set_id: SetId,
message: &E,
buf: &mut Vec<u8>,
) {
buf.clear();
(message, round, set_id).encode_to(buf)
}
#[derive(Clone, Encode, Decode, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub enum SignatureResult {
Valid,
Invalid,
OutdatedSet,
}
impl SignatureResult {
pub fn is_valid(&self) -> bool {
matches!(self, SignatureResult::Valid)
}
}
pub fn check_message_signature<H, N>(
message: &finality_grandpa::Message<H, N>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: RoundNumber,
set_id: SetId,
) -> SignatureResult
where
H: Encode,
N: Encode,
{
check_message_signature_with_buffer(message, id, signature, round, set_id, &mut Vec::new())
}
pub fn check_message_signature_with_buffer<H, N>(
message: &finality_grandpa::Message<H, N>,
id: &AuthorityId,
signature: &AuthoritySignature,
round: RoundNumber,
set_id: SetId,
buf: &mut Vec<u8>,
) -> SignatureResult
where
H: Encode,
N: Encode,
{
use crate::application_crypto::RuntimeAppPublic;
localized_payload_with_buffer(round, set_id, message, buf);
if id.verify(&buf, signature) {
return SignatureResult::Valid;
}
let log_target = if cfg!(feature = "std") { CLIENT_LOG_TARGET } else { RUNTIME_LOG_TARGET };
log::debug!(
target: log_target,
"Bad signature on message from id={id:?} round={round:?} set_id={set_id:?}",
);
if set_id == 0 {
return SignatureResult::Invalid;
}
let prev_set_id = set_id - 1;
localized_payload_with_buffer(round, prev_set_id, message, buf);
let valid = id.verify(&buf, signature);
log::debug!(
target: log_target,
"Previous set signature check for id={id:?} round={round:?} previous_set={prev_set_id:?} valid={valid:?}"
);
if valid {
SignatureResult::OutdatedSet
} else {
SignatureResult::Invalid
}
}
#[cfg(feature = "std")]
pub fn sign_message<H, N>(
keystore: KeystorePtr,
message: finality_grandpa::Message<H, N>,
public: AuthorityId,
round: RoundNumber,
set_id: SetId,
) -> Option<finality_grandpa::SignedMessage<H, N, AuthoritySignature, AuthorityId>>
where
H: Encode,
N: Encode,
{
use crate::application_crypto::AppCrypto;
let encoded = localized_payload(round, set_id, &message);
let signature = keystore
.ed25519_sign(AuthorityId::ID, public.as_ref(), &encoded[..])
.ok()
.flatten()?
.try_into()
.ok()?;
Some(finality_grandpa::SignedMessage { message, signature, id: public })
}
pub type OpaqueKeyOwnershipProof = OpaqueValue;
crate::api::decl_runtime_apis! {
#[api_version(3)]
pub trait GrandpaApi {
fn grandpa_authorities() -> AuthorityList;
fn submit_report_equivocation_unsigned_extrinsic(
equivocation_proof: EquivocationProof<Block::Hash, NumberFor<Block>>,
key_owner_proof: OpaqueKeyOwnershipProof,
) -> Option<()>;
fn generate_key_ownership_proof(
set_id: SetId,
authority_id: AuthorityId,
) -> Option<OpaqueKeyOwnershipProof>;
fn current_set_id() -> SetId;
}
}