,
) -> FlightResult {
let block = ancestry
.peek()
.expect("candidate ancestry must start with its block");
let (digest, parent_digest) = (block.digest(), block.parent());
let consensus_context = block.context();
let round = consensus_context.round();
let sync_targets = A::sync_targets(block.inner());
let batches = self.fork_batches(&parent_digest).await?;
let Some(merkleized) = app
.process(
(context, consensus_context),
ancestry,
batches,
Finality::Candidate,
)
.await
else {
warn!(block = ?digest, "application rejected block");
return Err(PrepareBatchesError::Invalid);
};
if !A::Databases::matches_sync_targets(&merkleized, &sync_targets) {
warn!(block = ?digest, "processed state must match block commitments");
return Err(PrepareBatchesError::Invalid);
}
self.cache_pending(
digest,
PendingEntry {
round,
parent: parent_digest,
merkleized,
},
)
.then_some(())
.ok_or(PrepareBatchesError::Invalid)
}
/// Collects the missing blocks between `target` and cached or applied state without forking
/// database batches.
///
/// Returns [`PrepareBatchesError::Invalid`] if the walk reaches the anchor height without
/// meeting the branch anchor, or if `provider` returns a block that is not the parent at the
/// preceding height. Returns [`PrepareBatchesError::Incomplete`] if `provider` stops before
/// delivering a parent.
async fn prepare_replay(
&self,
context: &E,
provider: &P,
target: Digested,
) -> Result, PrepareBatchesError>
where
P: BlockProvider + Clone,
{
let timer = self.metrics.rebuild_pending_duration.timer(context);
let target_digest = target.digest();
let mut replay_path = Vec::new();
let mut cursor = target;
loop {
let (known, anchor) = {
let state = self.state.lock();
(
cursor.digest() == state.branch.digest
|| state.pending.contains_key(&cursor.digest()),
state.branch,
)
};
if known {
break;
}
let cursor_height = cursor.height();
if cursor_height <= anchor.height {
warn!(
?target_digest,
cursor = ?cursor.digest(),
current_height = cursor_height.get(),
branch_height = anchor.height.get(),
branch_anchor = ?anchor.digest,
"ancestry reached stale state at or below anchor height"
);
return Err(PrepareBatchesError::Invalid);
}
let Some(parent) = provider.subscribe_parent(&cursor).await else {
debug!(
?target_digest,
cursor = ?cursor.digest(),
"ancestor subscription ended before delivery"
);
return Err(PrepareBatchesError::Incomplete);
};
if parent.digest() != cursor.parent()
|| cursor_height.previous() != Some(parent.height())
{
warn!(
?target_digest,
cursor = ?cursor.digest(),
parent = ?parent.digest(),
cursor_height = cursor_height.get(),
parent_height = parent.height().get(),
expected_parent = ?cursor.parent(),
"received non-contiguous ancestry"
);
return Err(PrepareBatchesError::Invalid);
}
replay_path.push(cursor);
cursor = parent;
}
let depth = replay_path.len();
Ok(ReplayPath {
blocks: replay_path,
parent: cursor,
measurement: Some((timer, depth)),
})
}
/// Returns the ancestry for the next block on `path`, starting with the block and its parent,
/// or [`None`] once the path is exhausted.
///
/// Each block forks from the state of the one before it, so the caller processes a block
/// before requesting the next.
fn next_replay(
&self,
context: &E,
provider: &P,
path: &mut ReplayPath,
) -> Option>
where
P: BlockProvider + Clone,
{
let Some(block) = path.blocks.pop() else {
if let Some((timer, depth)) = path.measurement.take() {
let _ = self.metrics.rebuild_pending_depth.try_set(depth);
timer.observe(context);
}
return None;
};
let parent = std::mem::replace(&mut path.parent, block.clone());
Some(BoxedAncestry::new(self.ancestry(
context,
provider.clone(),
[block, parent],
)))
}
#[cfg(test)]
async fn rebuild_pending(
&self,
app: &A,
context: &E,
provider: &P,
target: Digested,
) -> Result<(), PrepareBatchesError>
where
P: BlockProvider + Clone,
{
let mut path = self.prepare_replay(context, provider, target).await?;
while let Some(ancestry) = self.next_replay(context, provider, &mut path) {
if let Some(flight) = self.join_flight(app, context.child("replay"), ancestry) {
flight.await?;
}
}
Ok(())
}
}
/// Returns the output of `future`, or `None` if `cancelled` resolves first.
async fn await_or_cancel(
cancelled: impl Future