Skip to content

Commit 064694b

Browse files
authored
refactor(engine): simplify on_new_payload (#18613)
1 parent e6608be commit 064694b

File tree

2 files changed

+417
-63
lines changed

2 files changed

+417
-63
lines changed

crates/engine/tree/src/tree/mod.rs

Lines changed: 155 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -536,87 +536,32 @@ where
536536
// null}` if the expected and the actual arrays don't match.
537537
//
538538
// This validation **MUST** be instantly run in all cases even during active sync process.
539-
let parent_hash = payload.parent_hash();
540539

541540
let num_hash = payload.num_hash();
542541
let engine_event = ConsensusEngineEvent::BlockReceived(num_hash);
543542
self.emit_event(EngineApiEvent::BeaconConsensus(engine_event));
544543

545544
let block_hash = num_hash.hash;
546-
let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
547-
if lowest_buffered_ancestor == block_hash {
548-
lowest_buffered_ancestor = parent_hash;
549-
}
550545

551-
// now check if the block has an invalid ancestor
552-
if let Some(invalid) = self.state.invalid_headers.get(&lowest_buffered_ancestor) {
553-
// Here we might have 2 cases
554-
// 1. the block is well formed and indeed links to an invalid header, meaning we should
555-
// remember it as invalid
556-
// 2. the block is not well formed (i.e block hash is incorrect), and we should just
557-
// return an error and forget it
558-
let block = match self.payload_validator.ensure_well_formed_payload(payload) {
559-
Ok(block) => block,
560-
Err(error) => {
561-
let status = self.on_new_payload_error(error, parent_hash)?;
562-
return Ok(TreeOutcome::new(status))
563-
}
564-
};
565-
566-
let status = self.on_invalid_new_payload(block.into_sealed_block(), invalid)?;
567-
return Ok(TreeOutcome::new(status))
546+
// Check for invalid ancestors
547+
if let Some(invalid) = self.find_invalid_ancestor(&payload) {
548+
let status = self.handle_invalid_ancestor_payload(payload, invalid)?;
549+
return Ok(TreeOutcome::new(status));
568550
}
551+
569552
// record pre-execution phase duration
570553
self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64());
571554

572555
let status = if self.backfill_sync_state.is_idle() {
573-
let mut latest_valid_hash = None;
574-
match self.insert_payload(payload) {
575-
Ok(status) => {
576-
let status = match status {
577-
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
578-
latest_valid_hash = Some(block_hash);
579-
self.try_connect_buffered_blocks(num_hash)?;
580-
PayloadStatusEnum::Valid
581-
}
582-
InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
583-
latest_valid_hash = Some(block_hash);
584-
PayloadStatusEnum::Valid
585-
}
586-
InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) |
587-
InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
588-
// not known to be invalid, but we don't know anything else
589-
PayloadStatusEnum::Syncing
590-
}
591-
};
592-
593-
PayloadStatus::new(status, latest_valid_hash)
594-
}
595-
Err(error) => match error {
596-
InsertPayloadError::Block(error) => self.on_insert_block_error(error)?,
597-
InsertPayloadError::Payload(error) => {
598-
self.on_new_payload_error(error, parent_hash)?
599-
}
600-
},
601-
}
556+
self.try_insert_payload(payload)?
602557
} else {
603-
match self.payload_validator.ensure_well_formed_payload(payload) {
604-
// if the block is well-formed, buffer it for later
605-
Ok(block) => {
606-
if let Err(error) = self.buffer_block(block) {
607-
self.on_insert_block_error(error)?
608-
} else {
609-
PayloadStatus::from_status(PayloadStatusEnum::Syncing)
610-
}
611-
}
612-
Err(error) => self.on_new_payload_error(error, parent_hash)?,
613-
}
558+
self.try_buffer_payload(payload)?
614559
};
615560

616561
let mut outcome = TreeOutcome::new(status);
617562
// if the block is valid and it is the current sync target head, make it canonical
618563
if outcome.outcome.is_valid() && self.is_sync_target_head(block_hash) {
619-
// but only if it isn't already the canonical head
564+
// Only create the canonical event if this block isn't already the canonical head
620565
if self.state.tree_state.canonical_block_hash() != block_hash {
621566
outcome = outcome.with_event(TreeEvent::TreeAction(TreeAction::MakeCanonical {
622567
sync_target_head: block_hash,
@@ -630,6 +575,78 @@ where
630575
Ok(outcome)
631576
}
632577

578+
/// Processes a payload during normal sync operation.
579+
///
580+
/// Returns:
581+
/// - `Valid`: Payload successfully validated and inserted
582+
/// - `Syncing`: Parent missing, payload buffered for later
583+
/// - Error status: Payload is invalid
584+
fn try_insert_payload(
585+
&mut self,
586+
payload: T::ExecutionData,
587+
) -> Result<PayloadStatus, InsertBlockFatalError> {
588+
let block_hash = payload.block_hash();
589+
let num_hash = payload.num_hash();
590+
let parent_hash = payload.parent_hash();
591+
let mut latest_valid_hash = None;
592+
593+
match self.insert_payload(payload) {
594+
Ok(status) => {
595+
let status = match status {
596+
InsertPayloadOk::Inserted(BlockStatus::Valid) => {
597+
latest_valid_hash = Some(block_hash);
598+
self.try_connect_buffered_blocks(num_hash)?;
599+
PayloadStatusEnum::Valid
600+
}
601+
InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => {
602+
latest_valid_hash = Some(block_hash);
603+
PayloadStatusEnum::Valid
604+
}
605+
InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) |
606+
InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => {
607+
// not known to be invalid, but we don't know anything else
608+
PayloadStatusEnum::Syncing
609+
}
610+
};
611+
612+
Ok(PayloadStatus::new(status, latest_valid_hash))
613+
}
614+
Err(error) => match error {
615+
InsertPayloadError::Block(error) => Ok(self.on_insert_block_error(error)?),
616+
InsertPayloadError::Payload(error) => {
617+
Ok(self.on_new_payload_error(error, parent_hash)?)
618+
}
619+
},
620+
}
621+
}
622+
623+
/// Stores a payload for later processing during backfill sync.
624+
///
625+
/// During backfill, the node lacks the state needed to validate payloads,
626+
/// so they are buffered (stored in memory) until their parent blocks are synced.
627+
///
628+
/// Returns:
629+
/// - `Syncing`: Payload successfully buffered
630+
/// - Error status: Payload is malformed or invalid
631+
fn try_buffer_payload(
632+
&mut self,
633+
payload: T::ExecutionData,
634+
) -> Result<PayloadStatus, InsertBlockFatalError> {
635+
let parent_hash = payload.parent_hash();
636+
637+
match self.payload_validator.ensure_well_formed_payload(payload) {
638+
// if the block is well-formed, buffer it for later
639+
Ok(block) => {
640+
if let Err(error) = self.buffer_block(block) {
641+
Ok(self.on_insert_block_error(error)?)
642+
} else {
643+
Ok(PayloadStatus::from_status(PayloadStatusEnum::Syncing))
644+
}
645+
}
646+
Err(error) => Ok(self.on_new_payload_error(error, parent_hash)?),
647+
}
648+
}
649+
633650
/// Returns the new chain for the given head.
634651
///
635652
/// This also handles reorgs.
@@ -1856,6 +1873,57 @@ where
18561873
Ok(status)
18571874
}
18581875

1876+
/// Finds any invalid ancestor for the given payload.
1877+
///
1878+
/// This function walks up the chain of buffered ancestors from the payload's block
1879+
/// hash and checks if any ancestor is marked as invalid in the tree state.
1880+
///
1881+
/// The check works by:
1882+
/// 1. Finding the lowest buffered ancestor for the given block hash
1883+
/// 2. If the ancestor is the same as the block hash itself, using the parent hash instead
1884+
/// 3. Checking if this ancestor is in the `invalid_headers` map
1885+
///
1886+
/// Returns the invalid ancestor block info if found, or None if no invalid ancestor exists.
1887+
fn find_invalid_ancestor(&mut self, payload: &T::ExecutionData) -> Option<BlockWithParent> {
1888+
let parent_hash = payload.parent_hash();
1889+
let block_hash = payload.block_hash();
1890+
let mut lowest_buffered_ancestor = self.lowest_buffered_ancestor_or(block_hash);
1891+
if lowest_buffered_ancestor == block_hash {
1892+
lowest_buffered_ancestor = parent_hash;
1893+
}
1894+
1895+
// Check if the block has an invalid ancestor
1896+
self.state.invalid_headers.get(&lowest_buffered_ancestor)
1897+
}
1898+
1899+
/// Handles a payload that has an invalid ancestor.
1900+
///
1901+
/// This function validates the payload and processes it according to whether it's
1902+
/// well-formed or malformed:
1903+
/// 1. **Well-formed payload**: The payload is marked as invalid since it descends from a
1904+
/// known-bad block, which violates consensus rules
1905+
/// 2. **Malformed payload**: Returns an appropriate error status since the payload cannot be
1906+
/// validated due to its own structural issues
1907+
fn handle_invalid_ancestor_payload(
1908+
&mut self,
1909+
payload: T::ExecutionData,
1910+
invalid: BlockWithParent,
1911+
) -> Result<PayloadStatus, InsertBlockFatalError> {
1912+
let parent_hash = payload.parent_hash();
1913+
1914+
// Here we might have 2 cases
1915+
// 1. the block is well formed and indeed links to an invalid header, meaning we should
1916+
// remember it as invalid
1917+
// 2. the block is not well formed (i.e block hash is incorrect), and we should just return
1918+
// an error and forget it
1919+
let block = match self.payload_validator.ensure_well_formed_payload(payload) {
1920+
Ok(block) => block,
1921+
Err(error) => return Ok(self.on_new_payload_error(error, parent_hash)?),
1922+
};
1923+
1924+
Ok(self.on_invalid_new_payload(block.into_sealed_block(), invalid)?)
1925+
}
1926+
18591927
/// Checks if the given `head` points to an invalid header, which requires a specific response
18601928
/// to a forkchoice update.
18611929
fn check_invalid_ancestor(&mut self, head: B256) -> ProviderResult<Option<PayloadStatus>> {
@@ -2264,6 +2332,14 @@ where
22642332
Ok(None)
22652333
}
22662334

2335+
/// Inserts a payload into the tree and executes it.
2336+
///
2337+
/// This function validates the payload's basic structure, then executes it using the
2338+
/// payload validator. The execution includes running all transactions in the payload
2339+
/// and validating the resulting state transitions.
2340+
///
2341+
/// Returns `InsertPayloadOk` if the payload was successfully inserted and executed,
2342+
/// or `InsertPayloadError` if validation or execution failed.
22672343
fn insert_payload(
22682344
&mut self,
22692345
payload: T::ExecutionData,
@@ -2288,6 +2364,22 @@ where
22882364
)
22892365
}
22902366

2367+
/// Inserts a block or payload into the blockchain tree with full execution.
2368+
///
2369+
/// This is a generic function that handles both blocks and payloads by accepting
2370+
/// a block identifier, input data, and execution/validation functions. It performs
2371+
/// comprehensive checks and execution:
2372+
///
2373+
/// - Validates that the block doesn't already exist in the tree
2374+
/// - Ensures parent state is available, buffering if necessary
2375+
/// - Executes the block/payload using the provided execute function
2376+
/// - Handles both canonical and fork chain insertions
2377+
/// - Updates pending block state when appropriate
2378+
/// - Emits consensus engine events and records metrics
2379+
///
2380+
/// Returns `InsertPayloadOk::Inserted(BlockStatus::Valid)` on successful execution,
2381+
/// `InsertPayloadOk::AlreadySeen` if the block already exists, or
2382+
/// `InsertPayloadOk::Inserted(BlockStatus::Disconnected)` if parent state is missing.
22912383
fn insert_block_or_payload<Input, Err>(
22922384
&mut self,
22932385
block_id: BlockWithParent,

0 commit comments

Comments
 (0)