From 0d0232e8fc08c5f8bdf6a3d772f839fd405440f5 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Mon, 24 Nov 2025 16:25:46 +1100 Subject: [PATCH] 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`: headercalc 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`: https://github.com/sigp/lighthouse/blob/261322c3e3ee467c9454fa160a00866439cbc62f/beacon_node/beacon_chain/src/block_verification.rs#L1075-L1076 Which is where the `header` is calculated prior to calling `from_gossip_verified_block`: https://github.com/sigp/lighthouse/blob/261322c3e3ee467c9454fa160a00866439cbc62f/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 :tada: Co-Authored-By: Michael Sproul --- .../beacon_chain/src/block_verification.rs | 20 +++++++++++-------- consensus/types/src/signed_beacon_block.rs | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index 237826281c..374f1e2b36 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -1164,9 +1164,9 @@ impl SignatureVerifiedBlock { block_root: Hash256, chain: &BeaconChain, ) -> Result> { - 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 SignatureVerifiedBlock { from: GossipVerifiedBlock, chain: &BeaconChain, ) -> Result> { - 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 IntoExecutionPendingBlock for SignatureVerifiedBloc chain: &Arc>, notify_execution_layer: NotifyExecutionLayer, ) -> Result, BlockSlashInfo> { - 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 IntoExecutionPendingBlock 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 { diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 35d2faac48..7b04cc5771 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -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> SignedBeaconBlock } /// 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(),