Optimise out block header calculation (#8446)

This is a `tracing`-driven optimisation. While investigating why Lighthouse is slow to send `newPayload`, I found a suspicious 13ms of computation on the hot path in `gossip_block_into_execution_pending_block_slashable`:

<img width="1998" height="1022" alt="headercalc" src="https://github.com/user-attachments/assets/e4f88c1a-da23-47b4-b533-cf5479a1c55c" />

Looking at the current implementation we can see that the _only_ thing that happens prior to calling into `from_gossip_verified_block` is the calculation of a `header`. We first call `SignatureVerifiedBlock::from_gossip_verified_block_check_slashable`:

261322c3e3/beacon_node/beacon_chain/src/block_verification.rs (L1075-L1076)

Which is where the `header` is calculated prior to calling `from_gossip_verified_block`:

261322c3e3/beacon_node/beacon_chain/src/block_verification.rs (L1224-L1226)

Notice that the `header` is _only_ used in the case of an error, yet we spend time computing it every time!


  This PR moves the calculation of the header (which involves hashing the whole beacon block, including the execution payload), into the error case. We take a cheap clone of the `Arc`'d beacon block on the hot path, and use this for calculating the header _only_ in the case an error actually occurs. This shaves 10-20ms off our pre-newPayload delays, and 10-20ms off every block processing 🎉


Co-Authored-By: Michael Sproul <michael@sigmaprime.io>
This commit is contained in:
Michael Sproul
2025-11-24 16:25:46 +11:00
committed by GitHub
parent 2ba8a8e6ae
commit 0d0232e8fc
2 changed files with 14 additions and 8 deletions

View File

@@ -1164,9 +1164,9 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
block_root: Hash256,
chain: &BeaconChain<T>,
) -> Result<Self, BlockSlashInfo<BlockError>> {
let header = block.signed_block_header();
let arc_block = block.block_cloned();
Self::new(block, block_root, chain)
.map_err(|e| BlockSlashInfo::from_early_error_block(header, e))
.map_err(|e| BlockSlashInfo::from_early_error_block(arc_block.signed_block_header(), e))
}
/// Finishes signature verification on the provided `GossipVerifedBlock`. Does not re-verify
@@ -1221,9 +1221,13 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
from: GossipVerifiedBlock<T>,
chain: &BeaconChain<T>,
) -> Result<Self, BlockSlashInfo<BlockError>> {
let header = from.block.signed_block_header();
Self::from_gossip_verified_block(from, chain)
.map_err(|e| BlockSlashInfo::from_early_error_block(header, e))
let block = from.block.clone();
Self::from_gossip_verified_block(from, chain).map_err(|e| {
// Lazily create the header from the block in case of error. Computing the header
// involves some hashing and takes ~13ms which we DO NOT want to do on the hot path of
// block processing (prior to sending newPayload pre-Gloas).
BlockSlashInfo::from_early_error_block(block.signed_block_header(), e)
})
}
pub fn block_root(&self) -> Hash256 {
@@ -1248,12 +1252,12 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBloc
chain: &Arc<BeaconChain<T>>,
notify_execution_layer: NotifyExecutionLayer,
) -> Result<ExecutionPendingBlock<T>, BlockSlashInfo<BlockError>> {
let header = self.block.signed_block_header();
let arc_block = self.block.block_cloned();
let (parent, block) = if let Some(parent) = self.parent {
(parent, self.block)
} else {
load_parent(self.block, chain)
.map_err(|e| BlockSlashInfo::SignatureValid(header.clone(), e))?
.map_err(|e| BlockSlashInfo::SignatureValid(arc_block.signed_block_header(), e))?
};
ExecutionPendingBlock::from_signature_verified_components(
@@ -1264,7 +1268,7 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBloc
chain,
notify_execution_layer,
)
.map_err(|e| BlockSlashInfo::SignatureValid(header, e))
.map_err(|e| BlockSlashInfo::SignatureValid(arc_block.signed_block_header(), e))
}
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {

View File

@@ -8,6 +8,7 @@ use ssz_derive::{Decode, Encode};
use std::fmt;
use superstruct::superstruct;
use test_random_derive::TestRandom;
use tracing::instrument;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
@@ -253,6 +254,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> SignedBeaconBlock<E, Payload>
}
/// Produce a signed beacon block header corresponding to this block.
#[instrument(level = "debug", skip_all)]
pub fn signed_block_header(&self) -> SignedBeaconBlockHeader {
SignedBeaconBlockHeader {
message: self.message().block_header(),