cumulus_client_consensus_common/
parent_search.rs1use codec::Decode;
19use polkadot_primitives::Hash as RelayHash;
20
21use cumulus_primitives_core::{
22 relay_chain::{BlockId as RBlockId, OccupiedCoreAssumption},
23 ParaId,
24};
25use cumulus_relay_chain_interface::{RelayChainError, RelayChainInterface};
26
27use sc_client_api::{Backend, HeaderBackend};
28
29use sp_blockchain::Backend as BlockchainBackend;
30
31use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
32
33const LOG_TARGET: &str = "consensus::common::parent_search";
34
35#[derive(Debug)]
37pub struct ParentSearchParams {
38 pub relay_parent: RelayHash,
40 pub para_id: ParaId,
42 pub ancestry_lookback: usize,
45}
46
47#[derive(PartialEq, Clone)]
49pub struct ParentSearchResult<B: BlockT> {
50 pub included_header: B::Header,
52 pub best_parent_header: B::Header,
54}
55
56impl<B: BlockT> std::fmt::Debug for ParentSearchResult<B> {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("ParentSearchResult")
59 .field("included_number", &self.included_header.number())
60 .field("best_parent_hash", &self.best_parent_header.hash())
61 .field("best_parent_number", &self.best_parent_header.number())
62 .finish()
63 }
64}
65
66pub async fn find_parent_for_building<B: BlockT>(
76 params: ParentSearchParams,
77 backend: &impl Backend<B>,
78 relay_client: &impl RelayChainInterface,
79) -> Result<Option<ParentSearchResult<B>>, RelayChainError> {
80 tracing::trace!(target: LOG_TARGET, "Parent search parameters: {params:?}");
81
82 let Some((included_header, included_hash)) =
84 fetch_included_from_relay_chain(relay_client, backend, params.para_id, params.relay_parent)
85 .await?
86 else {
87 return Ok(None);
88 };
89
90 let maybe_pending = {
92 let pending_header = relay_client
96 .persisted_validation_data(
97 params.relay_parent,
98 params.para_id,
99 OccupiedCoreAssumption::Included,
100 )
101 .await?
102 .and_then(|p| B::Header::decode(&mut &p.parent_head.0[..]).ok())
103 .filter(|x| x.hash() != included_hash);
104
105 if let Some(header) = pending_header {
107 let pending_hash = header.hash();
108 let Ok(Some(header)) = backend.blockchain().header(pending_hash) else {
109 tracing::warn!(
110 target: LOG_TARGET,
111 %pending_hash,
112 "Failed to get header for pending block.",
113 );
114 return Ok(None);
115 };
116 Some((header, pending_hash))
117 } else {
118 None
119 }
120 };
121
122 let (start_hash, start_header) = match &maybe_pending {
124 Some((pending_header, pending_hash)) => {
125 let route =
127 sp_blockchain::tree_route(backend.blockchain(), included_hash, *pending_hash)?;
128 if !route.retracted().is_empty() {
129 tracing::warn!(
130 target: LOG_TARGET,
131 "Included block not an ancestor of pending block. This should not happen."
132 );
133 return Ok(None);
134 }
135 (*pending_hash, pending_header.clone())
136 },
137 None => (included_hash, included_header.clone()),
138 };
139
140 let rp_ancestry =
142 build_relay_parent_ancestry(params.ancestry_lookback, params.relay_parent, relay_client)
143 .await?;
144
145 let best_parent_header =
147 find_deepest_valid_parent(start_hash, start_header, backend, &rp_ancestry);
148
149 Ok(Some(ParentSearchResult { included_header, best_parent_header }))
150}
151
152async fn fetch_included_from_relay_chain<B: BlockT>(
154 relay_client: &impl RelayChainInterface,
155 backend: &impl Backend<B>,
156 para_id: ParaId,
157 relay_parent: RelayHash,
158) -> Result<Option<(B::Header, B::Hash)>, RelayChainError> {
159 let included_header = relay_client
163 .persisted_validation_data(relay_parent, para_id, OccupiedCoreAssumption::TimedOut)
164 .await?;
165 let included_header = match included_header {
166 Some(pvd) => pvd.parent_head,
167 None => return Ok(None), };
169
170 let included_header = match B::Header::decode(&mut &included_header.0[..]).ok() {
171 None => return Ok(None),
172 Some(x) => x,
173 };
174
175 let included_hash = included_header.hash();
176 match backend.blockchain().header(included_hash) {
178 Ok(None) | Err(_) => {
179 tracing::warn!(
180 target: LOG_TARGET,
181 %included_hash,
182 "Failed to get header for included block.",
183 );
184 return Ok(None);
185 },
186 _ => {},
187 };
188
189 Ok(Some((included_header, included_hash)))
190}
191
192async fn build_relay_parent_ancestry(
202 ancestry_lookback: usize,
203 relay_parent: RelayHash,
204 relay_client: &impl RelayChainInterface,
205) -> Result<Vec<(RelayHash, RelayHash)>, RelayChainError> {
206 let mut ancestry = Vec::with_capacity(ancestry_lookback + 1);
207 let mut current_rp = relay_parent;
208 let mut required_session = None;
209 while ancestry.len() <= ancestry_lookback {
210 let Some(header) = relay_client.header(RBlockId::hash(current_rp)).await? else { break };
211
212 let session = relay_client.session_index_for_child(current_rp).await?;
213 if required_session.get_or_insert(session) != &session {
214 break;
216 }
217
218 ancestry.push((current_rp, *header.state_root()));
219 current_rp = *header.parent_hash();
220
221 if header.number == 1 {
223 break;
224 }
225 }
226 Ok(ancestry)
227}
228
229fn find_deepest_valid_parent<Block: BlockT>(
235 start_hash: Block::Hash,
236 start_header: Block::Header,
237 backend: &impl Backend<Block>,
238 rp_ancestry: &[(RelayHash, RelayHash)],
239) -> Block::Header {
240 let mut best = start_header;
241
242 let mut frontier: Vec<Block::Hash> =
243 backend.blockchain().children(start_hash).ok().into_iter().flatten().collect();
244
245 tracing::trace!(
246 target: LOG_TARGET,
247 ?start_hash,
248 num_children = frontier.len(),
249 "Searching for deepest valid parent."
250 );
251
252 while let Some(hash) = frontier.pop() {
253 let Ok(Some(header)) = backend.blockchain().header(hash) else { continue };
254
255 if !is_relay_parent_in_ancestry::<Block>(&header, rp_ancestry) {
256 continue;
257 }
258
259 if header.number() > best.number() {
261 best = header.clone();
262 }
263
264 frontier.extend(backend.blockchain().children(hash).ok().into_iter().flatten());
265 }
266
267 best
268}
269
270fn is_relay_parent_in_ancestry<Block: BlockT>(
272 header: &Block::Header,
273 rp_ancestry: &[(RelayHash, RelayHash)],
274) -> bool {
275 let digest = header.digest();
276 let relay_parent = cumulus_primitives_core::extract_relay_parent(digest);
277 let storage_root =
278 cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root(digest)
279 .map(|(r, _)| r);
280
281 rp_ancestry.iter().any(|(rp_hash, rp_storage_root)| {
282 relay_parent.map_or(false, |rp| *rp_hash == rp) ||
283 storage_root.map_or(false, |sr| *rp_storage_root == sr)
284 })
285}