Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit 0057a11

Browse files
committed
rough draft of potential parent search
1 parent a82d5af commit 0057a11

File tree

2 files changed

+166
-0
lines changed
  • client

2 files changed

+166
-0
lines changed

client/consensus/common/src/lib.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use polkadot_primitives::{Hash as PHash, PersistedValidationData};
1818

19+
use cumulus_primitives_core::ParaId;
20+
1921
use sc_client_api::Backend;
2022
use sc_consensus::{shared_data::SharedData, BlockImport, ImportResult};
2123
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
@@ -174,3 +176,160 @@ where
174176
pub trait ParachainBlockImportMarker {}
175177

176178
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+
}

client/relay-chain-interface/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ pub trait RelayChainInterface: Send + Sync {
113113
/// Get the hash of the finalized block.
114114
async fn finalized_block_hash(&self) -> RelayChainResult<PHash>;
115115

116+
/// Get a header by hash, if it exists.
117+
async fn header(&self, block_id: PHash) -> RelayChainResult<Option<PHeader>>;
118+
116119
/// Returns the whole contents of the downward message queue for the parachain we are collating
117120
/// for.
118121
///
@@ -260,6 +263,10 @@ where
260263
(**self).finalized_block_hash().await
261264
}
262265

266+
async fn header(&self, block_id: PHash) -> RelayChainResult<Option<PHeader>> {
267+
(**self).header().await
268+
}
269+
263270
async fn is_major_syncing(&self) -> RelayChainResult<bool> {
264271
(**self).is_major_syncing().await
265272
}

0 commit comments

Comments
 (0)