Skip to main content

grandpa_verifier/
lib.rs

1// Copyright (c) 2025 Polytope Labs.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! GRANDPA consensus client verification function
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![allow(clippy::all)]
20#![deny(missing_docs)]
21
22mod tests;
23
24extern crate alloc;
25use polkadot_sdk::*;
26
27use alloc::collections::BTreeMap;
28use anyhow::anyhow;
29use codec::DecodeAll;
30use finality_grandpa::Chain;
31use grandpa_verifier_primitives::{
32	justification::{find_scheduled_change, AncestryChain, GrandpaJustification},
33	parachain_header_storage_key, ConsensusState, FinalityProof, ParachainHeadersWithFinalityProof,
34};
35use sp_core::H256;
36use sp_runtime::traits::{BlakeTwo256, Header};
37use sp_std::prelude::*;
38use sp_trie::StorageProof;
39use substrate_state_machine::read_proof_check;
40
41/// This function verifies the GRANDPA finality proof for both standalone chain and parachain
42/// headers.
43pub fn verify_grandpa_finality_proof<H>(
44	mut consensus_state: ConsensusState,
45	finality_proof: FinalityProof<H>,
46) -> Result<(ConsensusState, H, Vec<H256>, AncestryChain<H>), anyhow::Error>
47where
48	H: Header<Hash = H256, Number = u32>,
49	H::Number: finality_grandpa::BlockNumberOps + Into<u32>,
50{
51	// First validate unknown headers.
52	let headers = AncestryChain::<H>::new(&finality_proof.unknown_headers);
53
54	let target = finality_proof
55		.unknown_headers
56		.iter()
57		.max_by_key(|h| *h.number())
58		.ok_or_else(|| anyhow!("Unknown headers can't be empty!"))?;
59
60	// this is illegal
61	if target.hash() != finality_proof.block {
62		Err(anyhow!("Latest finalized block should be highest block in unknown_headers"))?;
63	}
64
65	let justification =
66		GrandpaJustification::<H>::decode_all(&mut &finality_proof.justification[..])
67			.map_err(|e| anyhow!("Failed to decode justification {:?}", e))?;
68
69	if justification.commit.target_hash != finality_proof.block {
70		Err(anyhow!("Justification target hash and finality proof block hash mismatch"))?;
71	}
72
73	let from = consensus_state.latest_hash;
74
75	let base = finality_proof
76		.unknown_headers
77		.iter()
78		.min_by_key(|h| *h.number())
79		.ok_or_else(|| anyhow!("Unknown headers can't be empty!"))?;
80
81	if base.number() < &consensus_state.latest_height {
82		headers.ancestry(base.hash(), consensus_state.latest_hash).map_err(|_| {
83			anyhow!(
84				"[verify_grandpa_finality_proof] Invalid ancestry (base -> latest relay block)!"
85			)
86		})?;
87	}
88
89	let finalized = headers
90		.ancestry(from, target.hash())
91		.map_err(|_| anyhow!("[verify_grandpa_finality_proof] Invalid ancestry!"))?;
92
93	// 2. verify justification.
94	justification.verify(consensus_state.current_set_id, &consensus_state.current_authorities)?;
95
96	// Sets new consensus state, optionally rotating authorities
97	consensus_state.latest_hash = target.hash();
98	consensus_state.latest_height = (*target.number()).into();
99	if let Some(scheduled_change) = find_scheduled_change::<H>(&target) {
100		consensus_state.current_set_id += 1;
101		consensus_state.current_authorities = scheduled_change.next_authorities;
102	}
103
104	Ok((consensus_state, target.clone(), finalized, headers))
105}
106/// This function verifies the GRANDPA finality proof for relay chain headers.
107///
108/// Next, we prove the finality of parachain headers, by verifying patricia-merkle trie state proofs
109/// of these headers, stored at the recently finalized relay chain heights.
110/// Returns the new Consensus state alongside a map of para id to a vector that contains a tuple of
111/// finalized parachain header and timestamp
112pub fn verify_parachain_headers_with_grandpa_finality_proof<H>(
113	consensus_state: ConsensusState,
114	proof: ParachainHeadersWithFinalityProof<H>,
115) -> Result<(ConsensusState, BTreeMap<u32, Vec<H>>), anyhow::Error>
116where
117	H: Header<Hash = H256, Number = u32>,
118	H::Number: finality_grandpa::BlockNumberOps + Into<u32>,
119{
120	let ParachainHeadersWithFinalityProof { finality_proof, parachain_headers } = proof;
121
122	let (consensus_state, _, mut finalized_hashes, headers) =
123		verify_grandpa_finality_proof(consensus_state, finality_proof)?;
124	finalized_hashes.sort();
125	// verifies state proofs of parachain headers in finalized relay chain headers.
126	let mut verified_parachain_headers: BTreeMap<u32, Vec<H>> = BTreeMap::new();
127	for (hash, proof) in parachain_headers {
128		if finalized_hashes.binary_search(&hash).is_err() {
129			// seems relay hash isn't in the finalized chain.
130			continue;
131		}
132		let relay_chain_header =
133			headers.header(&hash).expect("Headers have been checked by AncestryChain; qed");
134		let state_proof = proof.state_proof;
135		let mut keys = BTreeMap::new();
136		for para_id in proof.para_ids {
137			let key = parachain_header_storage_key(para_id);
138
139			keys.insert(key.0, para_id);
140		}
141
142		let proof = StorageProof::new(state_proof);
143
144		// verify patricia-merkle state proofs
145		let mut result = read_proof_check::<BlakeTwo256, _>(
146			relay_chain_header.state_root(),
147			proof,
148			keys.keys().map(|key| key.as_slice()),
149		)
150		.map_err(|err| anyhow!("error verifying parachain header state proof: {err:?}"))?;
151		for (key, para_id) in keys {
152			let header = result
153				.remove(&key)
154				.flatten()
155				.ok_or_else(|| anyhow!("Invalid proof, parachain header not found"))?;
156			let parachain_header =
157				H::decode(&mut &header[..]).map_err(|e| anyhow!("error decoding header: {e:?}"))?;
158			verified_parachain_headers.entry(para_id).or_default().push(parachain_header);
159		}
160	}
161
162	Ok((consensus_state, verified_parachain_headers))
163}