cumulus_client_consensus_common/
finality.rs1use sc_client_api::HeaderBackend;
21use sp_runtime::traits::{Block as BlockT, Header as _};
22
23const LOG_TARGET: &str = "consensus::common::finality";
24
25pub 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 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 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}