referrerpolicy=no-referrer-when-downgrade

cumulus_client_consensus_common/
finality.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Cumulus is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Cumulus is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Cumulus. If not, see <https://www.gnu.org/licenses/>.
17
18//! Shared helpers for finality-driven aux-storage cleanup.
19
20use sc_client_api::HeaderBackend;
21use sp_runtime::traits::{Block as BlockT, Header as _};
22
23const LOG_TARGET: &str = "consensus::common::finality";
24
25/// Resolve the previously-finalized block hash: the parent of the first block in `tree_route`,
26/// or `fallback_parent` (the just-finalized header's parent) when the route is empty or that
27/// header can't be loaded.
28pub fn old_finalized_hash<C, Block>(
29	client: &C,
30	tree_route: &[Block::Hash],
31	fallback_parent: Block::Hash,
32) -> Block::Hash
33where
34	C: HeaderBackend<Block>,
35	Block: BlockT,
36{
37	let Some(first) = tree_route.first() else {
38		return fallback_parent;
39	};
40
41	match client.header(*first) {
42		Ok(Some(header)) => *header.parent_hash(),
43		Ok(None) => {
44			tracing::warn!(
45				target: LOG_TARGET,
46				?first,
47				"tree_route head header missing; falling back to notification parent hash",
48			);
49			fallback_parent
50		},
51		Err(error) => {
52			tracing::warn!(
53				target: LOG_TARGET,
54				?first,
55				?error,
56				"tree_route head header lookup failed; falling back to notification parent hash",
57			);
58			fallback_parent
59		},
60	}
61}
62
63#[cfg(test)]
64mod tests {
65	use super::*;
66	use sp_blockchain::Result as ClientResult;
67
68	type Block = substrate_test_runtime::Block;
69	type Hash = <Block as BlockT>::Hash;
70
71	// Minimal `HeaderBackend` mock for the `old_finalized_hash_*` tests; only `header()` is used.
72	struct MockHeaderBackend {
73		lookup: Option<(Hash, substrate_test_runtime::Header)>,
74	}
75
76	impl HeaderBackend<Block> for MockHeaderBackend {
77		fn header(
78			&self,
79			hash: <Block as BlockT>::Hash,
80		) -> ClientResult<Option<<Block as BlockT>::Header>> {
81			Ok(self.lookup.as_ref().and_then(|(h, hdr)| (*h == hash).then(|| hdr.clone())))
82		}
83
84		fn info(&self) -> sc_client_api::blockchain::Info<Block> {
85			unimplemented!()
86		}
87
88		fn status(
89			&self,
90			_hash: <Block as BlockT>::Hash,
91		) -> ClientResult<sc_client_api::blockchain::BlockStatus> {
92			unimplemented!()
93		}
94
95		fn number(
96			&self,
97			_hash: <Block as BlockT>::Hash,
98		) -> ClientResult<Option<sp_runtime::traits::NumberFor<Block>>> {
99			unimplemented!()
100		}
101
102		fn hash(
103			&self,
104			_number: sp_runtime::traits::NumberFor<Block>,
105		) -> ClientResult<Option<<Block as BlockT>::Hash>> {
106			unimplemented!()
107		}
108	}
109
110	#[test]
111	fn old_finalized_hash_with_empty_tree_route() {
112		let client = MockHeaderBackend { lookup: None };
113		let fallback = Hash::repeat_byte(0x01);
114
115		let old_hash = old_finalized_hash::<_, Block>(&client, &[], fallback);
116		assert_eq!(
117			old_hash, fallback,
118			"empty tree_route should fall through to the supplied parent"
119		);
120	}
121
122	#[test]
123	fn old_finalized_hash_with_tree_route() {
124		use substrate_test_runtime::Header as TestHeader;
125
126		let expected_old = Hash::repeat_byte(0x01);
127		let tree_block = Hash::repeat_byte(0x02);
128
129		let header = TestHeader {
130			parent_hash: expected_old,
131			number: 2,
132			state_root: Default::default(),
133			extrinsics_root: Default::default(),
134			digest: Default::default(),
135		};
136
137		let client = MockHeaderBackend { lookup: Some((tree_block, header)) };
138		let fallback = Hash::repeat_byte(0xFF);
139
140		let old_hash = old_finalized_hash::<_, Block>(&client, &[tree_block], fallback);
141		assert_eq!(old_hash, expected_old, "should resolve to parent of first tree_route block");
142	}
143
144	#[test]
145	fn old_finalized_hash_falls_back_when_header_missing() {
146		// Non-empty tree_route, but the header lookup returns None — the function should fall back
147		// to `fallback_parent` rather than panicking or returning a wrong hash.
148		let client = MockHeaderBackend { lookup: None };
149		let tree_block = Hash::repeat_byte(0x02);
150		let fallback = Hash::repeat_byte(0xAA);
151
152		let old_hash = old_finalized_hash::<_, Block>(&client, &[tree_block], fallback);
153		assert_eq!(old_hash, fallback, "missing header should fall back to the supplied parent");
154	}
155}