|
16 | 16 |
|
17 | 17 | use polkadot_primitives::{Hash as PHash, PersistedValidationData}; |
18 | 18 |
|
| 19 | +use cumulus_primitives_core::ParaId; |
| 20 | + |
19 | 21 | use sc_client_api::Backend; |
20 | 22 | use sc_consensus::{shared_data::SharedData, BlockImport, ImportResult}; |
21 | 23 | use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; |
@@ -174,3 +176,160 @@ where |
174 | 176 | pub trait ParachainBlockImportMarker {} |
175 | 177 |
|
176 | 178 | impl<B: BlockT, BI, BE> ParachainBlockImportMarker for ParachainBlockImport<B, BI, BE> {} |
| 179 | + |
| 180 | +/// Parameters when searching for suitable parents to build on top of. |
| 181 | +pub struct ParentSearchParams { |
| 182 | + /// The relay-parent that is intended to be used. |
| 183 | + pub relay_parent: PHash, |
| 184 | + /// The ID of the parachain. |
| 185 | + pub para_id: ParaId, |
| 186 | + /// A limitation on the age of relay parents for parachain blocks that are being |
| 187 | + /// considered. This is relative to the `relay_parent` number. |
| 188 | + pub ancestry_lookback: usize, |
| 189 | + /// How "deep" parents can be relative to the included parachain block at the relay-parent. |
| 190 | + /// The included block has depth 0. |
| 191 | + pub max_depth: usize, |
| 192 | + /// Whether to only ignore "alternative" branches, i.e. branches of the chain |
| 193 | + /// which do not contain the block pending availability. |
| 194 | + pub ignore_alternative_branches: bool, |
| 195 | +} |
| 196 | + |
| 197 | +/// A potential parent block returned from [`find_potential_parents`] |
| 198 | +pub struct PotentialParent<B: BlockT> { |
| 199 | + /// The hash of the block. |
| 200 | + pub hash: B::Hash, |
| 201 | + /// The header of the block. |
| 202 | + pub header: B::Header, |
| 203 | + /// The depth of the block. |
| 204 | + pub depth: usize, |
| 205 | + /// Whether the block descends from the block pending availability. |
| 206 | + /// |
| 207 | + /// This is false for the last inclued block as well as the block pending availability itself. |
| 208 | + pub descends_from_pending: bool, |
| 209 | +} |
| 210 | + |
| 211 | +/// Perform a recursive search through blocks to find potential |
| 212 | +/// parent blocks for a new block. |
| 213 | +/// |
| 214 | +/// This accepts a relay-chain block to be used as an anchor and a maximum search depth, |
| 215 | +/// along with some arguments for filtering parachain blocks and performs a recursive search |
| 216 | +/// for parachain blocks. The search begins at the last included parachain block and returns |
| 217 | +/// a set of [`PotentialParent`]s which could be potential parents of a new block with this |
| 218 | +/// relay-parent according to the search parameters. |
| 219 | +/// |
| 220 | +/// A parachain block is a potential parent if it is either the last included parachain block, the pending |
| 221 | +/// parachain block (when `max_depth` >= 1), or all of the following hold: |
| 222 | +/// * its parent is a potential parent |
| 223 | +/// * its relay-parent is within `ancestry_lookback` of the targeted relay-parent. |
| 224 | +/// * the block number is within `max_depth` blocks of the included block |
| 225 | +pub async fn find_potential_parents<B: BlockT>( |
| 226 | + params: ParentSearchParams, |
| 227 | + client: &C, |
| 228 | + relay_client: &impl RelayChainInterface, |
| 229 | +) -> Result<Vec<B::Hash>, RelayChainError> { |
| 230 | + // 1. Build up the ancestry record of the relay chain to compare against. |
| 231 | + let rp_ancestry = { |
| 232 | + let mut ancestry = Vec::with_capacity(params.ancestry_lookback + 1); |
| 233 | + let mut current_rp = params.relay_parent; |
| 234 | + while ancestry.len() <= params.ancestry_lookback { |
| 235 | + let header = match relay_client.header(current_rp).await? { |
| 236 | + None => break, |
| 237 | + Some(h) => h, |
| 238 | + }; |
| 239 | + |
| 240 | + ancestry.push((current_rp, header.state_root().clone())); |
| 241 | + current_rp = header.parent_hash().clone(); |
| 242 | + |
| 243 | + // don't iterate back into the genesis block. |
| 244 | + if header.number == 1u32.into() { break } |
| 245 | + } |
| 246 | + |
| 247 | + rp_ancestry |
| 248 | + }; |
| 249 | + |
| 250 | + let is_hash_in_ancestry = |hash| rp_ancestry.iter().any(|x| x.0 == hash); |
| 251 | + let is_root_in_ancestry = |root| rp.ancestry.iter().any(|x| x.1 == root); |
| 252 | + |
| 253 | + // 2. Get the included and pending availability blocks. |
| 254 | + let included_header = relay_client.persisted_validation_data( |
| 255 | + params.relay_parent, |
| 256 | + params.para_id, |
| 257 | + OccupiedCoreAssumption::TimedOut, |
| 258 | + )?; |
| 259 | + |
| 260 | + let included_header = match included_header { |
| 261 | + Some(pvd) => pvd.parent_head, |
| 262 | + None => return Ok(Vec::new()), // this implies the para doesn't exist. |
| 263 | + }; |
| 264 | + |
| 265 | + let pending_header = relay_client.persisted_validation_data( |
| 266 | + params.relay_parent, |
| 267 | + params.para_id, |
| 268 | + OccupiedCoreAssumption::Included, |
| 269 | + )?.and_then(|x| if x.parent_head != included_header { Some(x.parent_head) } else { None }); |
| 270 | + |
| 271 | + let included_header = match B::Header::decode(&mut &included_header.0[..]).ok() { |
| 272 | + None => return Ok(Vec::new()), |
| 273 | + Some(x) => x, |
| 274 | + }; |
| 275 | + // Silently swallow if pending block can't decode. |
| 276 | + let pending_header = pending_header.map(|p| B::Header::decode(&mut &p.0[..]).ok()).flatten(); |
| 277 | + let included_hash = included_header.hash(); |
| 278 | + let pending_hash = pending_header.as_ref().map(|hdr| hdr.hash()); |
| 279 | + |
| 280 | + let mut frontier = vec![PotentialParent { |
| 281 | + hash: included_hash, |
| 282 | + header: included_header, |
| 283 | + depth: 0, |
| 284 | + descends_from_pending: false, |
| 285 | + }]; |
| 286 | + |
| 287 | + // Recursive search through descendants of the included block which have acceptable |
| 288 | + // relay parents. |
| 289 | + let mut potential_parents = Vec::new(); |
| 290 | + while let Some(entry) = frontier.pop() { |
| 291 | + let is_pending = entry.depth == 1 |
| 292 | + && pending_hash.as_ref().map_or(false, |h| &entry.hash == h); |
| 293 | + let is_included = entry.depth == 0; |
| 294 | + |
| 295 | + // note: even if the pending block or included block have a relay parent |
| 296 | + // outside of the expected part of the relay chain, they are always allowed |
| 297 | + // because they have already been posted on chain. |
| 298 | + let is_potential = is_pending || is_included || { |
| 299 | + let digest = entry.header.digest(); |
| 300 | + cumulus_primitives_core::extract_relay_parent(digest) |
| 301 | + .map_or(false, is_hash_in_ancestry) || |
| 302 | + cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root(digest) |
| 303 | + .map_or(false, is_root_in_ancestry) |
| 304 | + }; |
| 305 | + |
| 306 | + if is_potential { |
| 307 | + potential_parents.push(entry); |
| 308 | + } |
| 309 | + |
| 310 | + if !is_potential || entry.depth + 1 > max_depth { continue } |
| 311 | + |
| 312 | + // push children onto search frontier. |
| 313 | + for child in client.children(entry.hash).ok().flatten().into_iter().flat_map(|c| c) { |
| 314 | + if params.ignore_alternative_branches |
| 315 | + && is_included |
| 316 | + && pending_hash.map_or(false, |h| &child != h) |
| 317 | + { continue } |
| 318 | + |
| 319 | + let header = match client.header(child) { |
| 320 | + Ok(Some(h)) => h, |
| 321 | + Ok(None) => continue, |
| 322 | + Err(_) => continue, |
| 323 | + }; |
| 324 | + |
| 325 | + frontier.push(PotentialParent { |
| 326 | + hash: child, |
| 327 | + header, |
| 328 | + depth: entry.depth + 1, |
| 329 | + descends_from_pending: is_pending || entry.descends_from_pending, |
| 330 | + }); |
| 331 | + } |
| 332 | + } |
| 333 | + |
| 334 | + Ok(potential_parents) |
| 335 | +} |
0 commit comments