Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
eda918d
Batch verify builder deposit signatures
pawanjay176 May 15, 2026
ed15ac6
Refactor `onboard_builders_from_pending_deposits` based on new spec
pawanjay176 May 15, 2026
369eecd
Add a builder onboarding cache
pawanjay176 May 15, 2026
112b093
Working wiring with pre-gloas cache
pawanjay176 May 16, 2026
0aa7d7a
More threading
pawanjay176 May 16, 2026
5e46d38
Clean up threading with a GloasVerificationContext
pawanjay176 May 17, 2026
18bcc24
Add a hashmap for keeping track of added builders
pawanjay176 May 17, 2026
826a521
Update onboarding cache size
pawanjay176 May 17, 2026
49410fa
Add better docs
pawanjay176 May 17, 2026
e2e8406
fmt
pawanjay176 May 17, 2026
9738905
lint
pawanjay176 May 17, 2026
fd43fdc
Cleanup
pawanjay176 May 18, 2026
44dcf5d
Fix lint
pawanjay176 May 19, 2026
bc1a156
Call initialize_ptc in all cases and rename variant
pawanjay176 May 19, 2026
50cd378
Use spawn_blocking_with_rayon for batch signature verification tasks
pawanjay176 May 19, 2026
16f615e
Review comments
pawanjay176 May 19, 2026
33bccd0
Add unit tests
pawanjay176 May 19, 2026
f89c099
Remove SkipBuilderOnboarding variant and thread the onboard builder c…
pawanjay176 May 19, 2026
de89987
Add more tests
pawanjay176 May 19, 2026
70b8594
Optimise by not iterating through the state.builders list for every i…
pawanjay176 May 21, 2026
ecbb7e7
Only onboard state deposits before gloas
pawanjay176 May 22, 2026
656e70a
Fix consensus bugs and add test
pawanjay176 May 22, 2026
31b6f3d
Merge branch 'unstable' into builder-deposits-optimisation
pawanjay176 May 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 40 additions & 3 deletions beacon_node/beacon_chain/src/beacon_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ use slasher::Slasher;
use slot_clock::SlotClock;
use ssz::Encode;
use state_processing::{
BlockSignatureStrategy, ConsensusContext, SigVerifiedOp, VerifyBlockRoot, VerifyOperation,
BlockSignatureStrategy, ConsensusContext, GloasVerificationContext, SigVerifiedOp,
VerifyBlockRoot, VerifyOperation,
builder_deposits_cache::OnboardBuildersCache,
common::get_attesting_indices_from_state,
epoch_cache::initialize_epoch_cache,
per_block_processing,
Expand Down Expand Up @@ -510,6 +512,9 @@ pub struct BeaconChain<T: BeaconChainTypes> {
pub pending_payload_cache: Arc<PendingPayloadCache<T>>,
/// The KZG trusted setup used by this chain.
pub kzg: Arc<Kzg>,
/// Pre-validates builder deposit signatures.
/// Only required when gloas is enabled.
pub builder_onboarding_cache: Option<Arc<OnboardBuildersCache>>,
/// RNG instance used by the chain. Currently used for shuffling column sidecars in block publishing.
pub rng: Arc<Mutex<Box<dyn RngCore + Send>>>,
}
Expand Down Expand Up @@ -1513,7 +1518,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
while state.slot() < slot {
// Note: supplying some `state_root` when it is known would be a cheap and easy
// optimization.
match per_slot_processing(&mut state, skip_state_root, &self.spec) {
match per_slot_processing(
&mut state,
skip_state_root,
GloasVerificationContext::from_cache(
self.builder_onboarding_cache.as_deref(),
),
&self.spec,
) {
Ok(_) => (),
Err(e) => {
warn!(
Expand Down Expand Up @@ -2116,6 +2128,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&mut state,
Some(advanced_state_root),
request_epoch.start_slot(T::EthSpec::slots_per_epoch()),
self.builder_onboarding_cache.as_deref(),
&self.spec,
)
.map_err(Error::StateAdvanceError)?;
Expand Down Expand Up @@ -4536,6 +4549,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
current_slot,
);

if let Some(builder_onboarding_cache) = &self.builder_onboarding_cache {
let cache = builder_onboarding_cache.clone();
let spec = self.spec.clone();
let executor = self.task_executor.clone();
// Using rayon pool here since `add_new_pending_deposits` uses rayon threads to
// perform the signature verification in batches.
// // We have until the fork transition for the cache to be used, so we use the low priority pool.
Comment thread
pawanjay176 marked this conversation as resolved.
Outdated
executor.spawn_blocking_with_rayon(
move || cache.add_new_pending_deposits::<T::EthSpec>(&state, &spec),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this pretty much a no-op after the fork?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it is. Hadn't considered it. can potentially only do this for pre-gloas states and then delete it post gloas

RayonPoolType::LowPriority,
"pre_verify_deposits",
);
}

Ok(block_root)
}

Expand Down Expand Up @@ -5170,6 +5197,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
&mut advanced_state,
Some(unadvanced_state_root),
proposal_slot,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)?;

Expand All @@ -5188,6 +5216,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
apply_parent_execution_payload(
&mut advanced_state,
&envelope.message.execution_requests,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)
.map_err(Error::PrepareProposerFailed)?;
Expand Down Expand Up @@ -6191,6 +6220,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
signature_strategy,
VerifyBlockRoot::True,
&mut ctxt,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)?;
drop(process_timer);
Expand Down Expand Up @@ -6977,6 +7007,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
proposal_epoch,
accessor,
state_provider,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)
}
Expand Down Expand Up @@ -7134,7 +7165,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
if state.current_epoch() + 1 < shuffling_epoch {
// Advance the state into the required slot, using the "partial" method since the
// state roots are not relevant for the shuffling.
partial_state_advance(&mut state, Some(state_root), target_slot, &self.spec)?;
partial_state_advance(
&mut state,
Some(state_root),
target_slot,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)?;
}
metrics::stop_timer(state_skip_timer);

Expand Down
15 changes: 13 additions & 2 deletions beacon_node/beacon_chain/src/beacon_proposer_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use safe_arith::SafeArith;
use smallvec::SmallVec;
use state_processing::builder_deposits_cache::OnboardBuildersCache;
use state_processing::state_advance::partial_state_advance;
use std::num::NonZeroUsize;
use std::sync::Arc;
Expand Down Expand Up @@ -178,6 +179,7 @@ pub fn with_proposer_cache<Spec, V, Err>(
proposal_epoch: Epoch,
accessor: impl Fn(&EpochBlockProposers) -> Result<V, BeaconChainError>,
state_provider: impl FnOnce() -> Result<(Hash256, BeaconState<Spec>), Err>,
builder_onboarding_cache: Option<&OnboardBuildersCache>,
spec: &ChainSpec,
) -> Result<V, Err>
where
Expand All @@ -204,6 +206,7 @@ where
&mut state,
state_root,
proposal_epoch,
builder_onboarding_cache,
spec,
)?;

Expand Down Expand Up @@ -275,6 +278,7 @@ pub fn compute_proposer_duties_from_head<T: BeaconChainTypes>(
&mut state,
head_state_root,
request_epoch,
chain.builder_onboarding_cache.as_deref(),
&chain.spec,
)?;

Expand Down Expand Up @@ -318,6 +322,7 @@ pub fn ensure_state_can_determine_proposers_for_epoch<E: EthSpec>(
state: &mut BeaconState<E>,
state_root: Hash256,
target_epoch: Epoch,
builder_onboarding_cache: Option<&OnboardBuildersCache>,
spec: &ChainSpec,
) -> Result<(), BeaconChainError> {
// The decision slot is the end of an epoch, so we add 1 to reach the first slot of the epoch
Expand All @@ -338,7 +343,13 @@ pub fn ensure_state_can_determine_proposers_for_epoch<E: EthSpec>(
} else {
// State's current epoch is less than the minimum epoch.
// Advance the state up to the minimum epoch.
partial_state_advance(state, Some(state_root), minimum_slot, spec)
.map_err(BeaconChainError::from)
partial_state_advance(
state,
Some(state_root),
minimum_slot,
builder_onboarding_cache,
spec,
)
.map_err(BeaconChainError::from)
}
}
2 changes: 2 additions & 0 deletions beacon_node/beacon_chain/src/block_production/gloas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
signature_strategy,
VerifyBlockRoot::True,
&mut ctxt,
self.builder_onboarding_cache.as_deref(),
&self.spec,
)?;
drop(process_timer);
Expand Down Expand Up @@ -965,6 +966,7 @@ fn get_execution_payload_gloas<T: BeaconChainTypes>(
apply_parent_execution_payload(
&mut withdrawals_state,
&envelope.message.execution_requests,
chain.builder_onboarding_cache.as_deref(),
spec,
)?;
Withdrawals::<T::EthSpec>::from(get_expected_withdrawals(&withdrawals_state, spec)?)
Expand Down
27 changes: 22 additions & 5 deletions beacon_node/beacon_chain/src/block_verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ use safe_arith::ArithError;
use slot_clock::SlotClock;
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use state_processing::builder_deposits_cache::OnboardBuildersCache;
use state_processing::per_block_processing::errors::IntoWithIndex;
use state_processing::{
AllCaches, BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError,
VerifyBlockRoot,
AllCaches, BlockProcessingError, BlockSignatureStrategy, ConsensusContext,
GloasVerificationContext, SlotProcessingError, VerifyBlockRoot,
block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError},
per_block_processing, per_slot_processing,
state_advance::partial_state_advance,
Expand Down Expand Up @@ -609,6 +610,7 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
&mut parent.pre_state,
parent.beacon_state_root,
highest_slot,
chain.builder_onboarding_cache.as_deref(),
&chain.spec,
)?;

Expand Down Expand Up @@ -1109,6 +1111,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
&mut parent.pre_state,
parent.beacon_state_root,
block.slot(),
chain.builder_onboarding_cache.as_deref(),
&chain.spec,
)?;

Expand Down Expand Up @@ -1180,6 +1183,7 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
&mut parent.pre_state,
parent.beacon_state_root,
block.slot(),
chain.builder_onboarding_cache.as_deref(),
&chain.spec,
)?;

Expand Down Expand Up @@ -1542,7 +1546,12 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
state_root
};

if let Some(summary) = per_slot_processing(&mut state, Some(state_root), &chain.spec)? {
if let Some(summary) = per_slot_processing(
&mut state,
Some(state_root),
GloasVerificationContext::from_cache(chain.builder_onboarding_cache.as_deref()),
&chain.spec,
)? {
// Expose Prometheus metrics.
if let Err(e) = summary.observe_metrics() {
error!(
Expand Down Expand Up @@ -1611,6 +1620,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
BlockSignatureStrategy::NoVerification,
VerifyBlockRoot::True,
&mut consensus_context,
chain.builder_onboarding_cache.as_deref(),
&chain.spec,
) {
match err {
Expand Down Expand Up @@ -2088,6 +2098,7 @@ pub fn cheap_state_advance_to_obtain_committees<'a, E: EthSpec, Err: BlockBlobEr
state: &'a mut BeaconState<E>,
state_root_opt: Option<Hash256>,
block_slot: Slot,
builder_onboarding_cache: Option<&OnboardBuildersCache>,
spec: &ChainSpec,
) -> Result<Cow<'a, BeaconState<E>>, Err> {
let block_epoch = block_slot.epoch(E::slots_per_epoch());
Expand All @@ -2107,8 +2118,14 @@ pub fn cheap_state_advance_to_obtain_committees<'a, E: EthSpec, Err: BlockBlobEr

// Advance the state into the same epoch as the block. Use the "partial" method since state
// roots are not important for proposer/attester shuffling.
partial_state_advance(&mut state, state_root_opt, target_slot, spec)
.map_err(BeaconChainError::from)?;
partial_state_advance(
&mut state,
state_root_opt,
target_slot,
builder_onboarding_cache,
spec,
)
.map_err(BeaconChainError::from)?;

state.build_committee_cache(RelativeEpoch::Previous, spec)?;
state.build_committee_cache(RelativeEpoch::Current, spec)?;
Expand Down
27 changes: 24 additions & 3 deletions beacon_node/beacon_chain/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ use rayon::prelude::*;
use slasher::Slasher;
use slot_clock::{SlotClock, TestingSlotClock};
use state_processing::AllCaches;
use state_processing::builder_deposits_cache::OnboardBuildersCache;
use state_processing::genesis::genesis_block;
use state_processing::per_slot_processing;
use state_processing::{GloasVerificationContext, per_slot_processing};
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::Duration;
Expand Down Expand Up @@ -445,8 +446,13 @@ where
"Advancing checkpoint state to boundary"
);
while weak_subj_state.slot() % slots_per_epoch != 0 {
per_slot_processing(&mut weak_subj_state, None, &self.spec)
.map_err(|e| format!("Error advancing state: {e:?}"))?;
per_slot_processing(
&mut weak_subj_state,
None,
GloasVerificationContext::FullVerification,
&self.spec,
)
.map_err(|e| format!("Error advancing state: {e:?}"))?;
}
}

Expand Down Expand Up @@ -1084,6 +1090,7 @@ where
rng: Arc::new(Mutex::new(rng)),
gossip_verified_payload_bid_cache: <_>::default(),
gossip_verified_proposer_preferences_cache: <_>::default(),
builder_onboarding_cache: OnboardBuildersCache::new(&self.spec).map(Arc::new),
};

let head = beacon_chain.head_snapshot();
Expand Down Expand Up @@ -1155,6 +1162,20 @@ where
.process_prune_blobs(data_availability_boundary);
}

// Seed the builder onboarding cache in the background from the current head state.
if let Some(onboarding_cache) = &beacon_chain.builder_onboarding_cache {
let cache = onboarding_cache.clone();
let spec = self.spec.clone();
let executor = beacon_chain.task_executor.clone();
// Using rayon pool here since `seed_from_state` uses rayon threads to
// perform the signature verification in batches.
executor.spawn_blocking_with_rayon(
move || cache.seed_from_state(&head.beacon_state, &spec),
task_executor::RayonPoolType::HighPriority,
"initialize_builder_onboarding_cache",
);
}

Ok(beacon_chain)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use eth2::types::{EventKind, ForkVersionedResponse};
use parking_lot::RwLock;
use safe_arith::SafeArith;
use slot_clock::SlotClock;
use state_processing::builder_deposits_cache::OnboardBuildersCache;
use state_processing::per_block_processing::signature_sets::indexed_payload_attestation_signature_set;
use state_processing::state_advance::partial_state_advance;
use std::borrow::Cow;
Expand All @@ -18,6 +19,7 @@ use types::{ChainSpec, EthSpec, IndexedPayloadAttestation, PTC, PayloadAttestati
pub struct GossipVerificationContext<'a, T: BeaconChainTypes> {
pub slot_clock: &'a T::SlotClock,
pub spec: &'a ChainSpec,
pub builder_onboarding_cache: Option<&'a OnboardBuildersCache>,
pub observed_payload_attesters: &'a RwLock<ObservedPayloadAttesters<T::EthSpec>>,
pub canonical_head: &'a CanonicalHead<T>,
pub validator_pubkey_cache: &'a RwLock<ValidatorPubkeyCache<T>>,
Expand Down Expand Up @@ -113,8 +115,14 @@ impl<T: BeaconChainTypes> VerifiedPayloadAttestationMessage<T> {
.map_err(BeaconChainError::from)?
< message_epoch
{
partial_state_advance(&mut state, Some(state_root), target_slot, ctx.spec)
.map_err(BeaconChainError::from)?;
partial_state_advance(
&mut state,
Some(state_root),
target_slot,
ctx.builder_onboarding_cache,
ctx.spec,
)
.map_err(BeaconChainError::from)?;
}

Some(state)
Expand Down Expand Up @@ -202,6 +210,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
GossipVerificationContext {
slot_clock: &self.slot_clock,
spec: &self.spec,
builder_onboarding_cache: self.builder_onboarding_cache.as_deref(),
observed_payload_attesters: &self.observed_payload_attesters,
canonical_head: &self.canonical_head,
validator_pubkey_cache: &self.validator_pubkey_cache,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ impl TestContext {
GossipVerificationContext {
slot_clock: &self.slot_clock,
spec: &self.spec,
builder_onboarding_cache: None,
observed_payload_attesters: &self.observed_payload_attesters,
canonical_head: &self.canonical_head,
validator_pubkey_cache: &self.validator_pubkey_cache,
Expand Down Expand Up @@ -373,6 +374,7 @@ async fn stale_head_with_partial_advance() {
&mut reference_state,
Some(head.snapshot.beacon_state_root()),
target_slot,
harness.chain.builder_onboarding_cache.as_deref(),
&harness.spec,
)
.expect("should advance reference state");
Expand Down
Loading
Loading