Skip to content

feat: preverify builder deposit signatures - #9436

Closed
twoeths wants to merge 22 commits into
unstablefrom
te/improve_onboard_builders_2
Closed

feat: preverify builder deposit signatures#9436
twoeths wants to merge 22 commits into
unstablefrom
te/improve_onboard_builders_2

Conversation

@twoeths

@twoeths twoeths commented Jun 1, 2026

Copy link
Copy Markdown
Member

Motivation

  • at fork transition, there could be a lot of builder deposits which cause it to be super delayed
  • post-gloas, there could be 8192 builder deposit signatures that we don't want to slow down the import block process

Description

  • preverify pre-gloas builder deposits 2 epochs before gloas transition
  • preverify payload builder deposits when importing payload
    • when processing payload deposit requests, there is no slot, so I made PendingDepositNoSlot for that
    • PendingDepositNoSlot contains 4 fields so we save some hash costs with that, compared to PendingDeposit
  • create BatchOnboardBuilder to verify builder deposit signatures in batch
    • if cache misses, we queue and verify builder deposit signatures
    • if it hits, we either onboard builder immediately, or skip it
  • implement BuilderDepositSignatureCache for NodeJS
    • cache by slot for onboarding builders at fork transition
    • cache by payload block hash post-gloas

AI Assistance Disclosure

  • created with the help of Claude

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces optimizations for onboarding builders from pending deposits during the Gloas fork transition and post-Gloas block processing. It implements a lazy, batch-verification mechanism for builder deposit signatures using a new BuilderDepositSignatureCache and BatchOnboardBuilder utility, replacing the previous eager, one-by-one verification logic. The code reviewer provided valuable feedback, identifying a potential runtime crash on pre-Gloas forks that can be resolved with a isStatePostGloas guard. Additionally, the reviewer suggested simplifying the BatchOnboardBuilder design by reading directly from the state instead of maintaining a duplicate array of plain objects, and recommended changing the _lastVerifiedSlot sentinel value from 0 to -1 to prevent skipping pre-verification for slot 0.

Comment on lines +298 to +301
if (builderDeposits.length > 0) {
callInNextEventLoop(() => {
try {
const result = blockState.preVerifyPayloadBuilderDeposits(blockHashHex, builderDeposits);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

The blockState view might be from a pre-Gloas fork (e.g., Bellatrix, Capella, Deneb, Electra, Fulu) where preVerifyPayloadBuilderDeposits is not defined. Calling this method without a guard will cause a runtime TypeError and crash the beacon node during block import on those forks. Guard the pre-verification logic with isStatePostGloas(blockState).

Suggested change
if (builderDeposits.length > 0) {
callInNextEventLoop(() => {
try {
const result = blockState.preVerifyPayloadBuilderDeposits(blockHashHex, builderDeposits);
if (isStatePostGloas(blockState) && builderDeposits.length > 0) {
callInNextEventLoop(() => {
try {
const result = blockState.preVerifyPayloadBuilderDeposits(blockHashHex, builderDeposits);

Comment on lines +61 to +80
// this is use to scan for reused builder index
private preExistingBuilders: gloas.Builder[];
private nextReuseIndexCheck = 0;

constructor(private readonly state: CachedBeaconStateGloas) {
this.preExistingBuilders = this.state.builders.getAllReadonlyValues();
this.builderIndexByPubkey = new Map();
const currentEpoch = computeEpochAtSlot(this.state.slot);
// Sentinel = preExistingBuilders.length means "no eligible slot found yet".
// Since i increases monotonically, (i < firstReuseIdx) is true only until we
// assign, so this records the FIRST eligible slot's index.
let firstReuseIdx = this.preExistingBuilders.length;
for (const [i, builder] of this.preExistingBuilders.entries()) {
this.builderIndexByPubkey.set(toPubkeyHex(builder.pubkey), i);
if (i < firstReuseIdx && isBuilderExited(builder, currentEpoch)) {
firstReuseIdx = i;
}
}
this.nextReuseIndexCheck = firstReuseIdx;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Instead of maintaining a duplicate preExistingBuilders array of plain objects and manually synchronizing it during top-ups and additions, we can simplify the design by storing only the initial length of the builders list (initialBuildersLength) and reading directly from this.state.builders using getReadonly(i). This avoids duplicate state, potential out-of-sync bugs, and the need for the getAllReadonlyValues() helper.

  private readonly initialBuildersLength: number;
  private nextReuseIndexCheck = 0;

  constructor(private readonly state: CachedBeaconStateGloas) {
    this.builderIndexByPubkey = new Map();
    this.initialBuildersLength = this.state.builders.length;
    const currentEpoch = computeEpochAtSlot(this.state.slot);
    let firstReuseIdx = this.initialBuildersLength;
    for (let i = 0; i < this.initialBuildersLength; i++) {
      const builder = this.state.builders.getReadonly(i);
      this.builderIndexByPubkey.set(toPubkeyHex(builder.pubkey), i);
      if (i < firstReuseIdx && isBuilderExited(builder, currentEpoch)) {
        firstReuseIdx = i;
      }
    }
    this.nextReuseIndexCheck = firstReuseIdx;
  }

Comment on lines +105 to +108
if (
this.nextReuseIndexCheck < this.preExistingBuilders.length ||
this.queuedBuilderDeposits.size >= BUILDER_DEPOSIT_BATCH_SIZE
) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Update the queue flush condition to use initialBuildersLength instead of preExistingBuilders.length.

Suggested change
if (
this.nextReuseIndexCheck < this.preExistingBuilders.length ||
this.queuedBuilderDeposits.size >= BUILDER_DEPOSIT_BATCH_SIZE
) {
if (
this.nextReuseIndexCheck < this.initialBuildersLength ||
this.queuedBuilderDeposits.size >= BUILDER_DEPOSIT_BATCH_SIZE
) {

Comment on lines +120 to +126
topupBuilder(builderIndex: BuilderIndex, amount: UintNum64): void {
const builder = this.state.builders.get(builderIndex);
builder.balance += amount;
if (builderIndex < this.preExistingBuilders.length) {
this.preExistingBuilders[builderIndex].balance += amount;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

With the simplified design reading directly from this.state.builders, we no longer need to manually update the duplicate preExistingBuilders array during top-ups.

  /** Top up an already-onboarded builder's balance. No signature verification needed. */
  topupBuilder(builderIndex: BuilderIndex, amount: UintNum64): void {
    const builder = this.state.builders.get(builderIndex);
    builder.balance += amount;
  }

Comment on lines +170 to +183
for (let i = this.nextReuseIndexCheck; i < this.preExistingBuilders.length; i++) {
const builder = this.preExistingBuilders[i];
if (isBuilderExited(builder, currentEpoch)) {
this.state.builders.set(i, newBuilder);
this.preExistingBuilders[i] = newBuilder.toValue();
this.builderIndexByPubkey.delete(toPubkeyHex(builder.pubkey));
this.builderIndexByPubkey.set(toPubkeyHex(newBuilder.pubkey), i);
this.nextReuseIndexCheck = i + 1;
return;
}
}

// don't have to scan again the next time
this.nextReuseIndexCheck = this.preExistingBuilders.length;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Update addBuilderToRegistry to read directly from this.state.builders.getReadonly(i) and use initialBuildersLength instead of preExistingBuilders.

Suggested change
for (let i = this.nextReuseIndexCheck; i < this.preExistingBuilders.length; i++) {
const builder = this.preExistingBuilders[i];
if (isBuilderExited(builder, currentEpoch)) {
this.state.builders.set(i, newBuilder);
this.preExistingBuilders[i] = newBuilder.toValue();
this.builderIndexByPubkey.delete(toPubkeyHex(builder.pubkey));
this.builderIndexByPubkey.set(toPubkeyHex(newBuilder.pubkey), i);
this.nextReuseIndexCheck = i + 1;
return;
}
}
// don't have to scan again the next time
this.nextReuseIndexCheck = this.preExistingBuilders.length;
for (let i = this.nextReuseIndexCheck; i < this.initialBuildersLength; i++) {
const builder = this.state.builders.getReadonly(i);
if (isBuilderExited(builder, currentEpoch)) {
this.state.builders.set(i, newBuilder);
this.builderIndexByPubkey.delete(toPubkeyHex(builder.pubkey));
this.builderIndexByPubkey.set(toPubkeyHex(newBuilder.pubkey), i);
this.nextReuseIndexCheck = i + 1;
return;
}
}
// don't have to scan again the next time
this.nextReuseIndexCheck = this.initialBuildersLength;

Comment on lines +191 to +193
function isBuilderExited(builder: gloas.Builder, currentEpoch: Epoch): boolean {
return builder.withdrawableEpoch <= currentEpoch && builder.balance === 0;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Update isBuilderExited to accept a structurally typed object, making it compatible with both plain objects and SSZ views.

Suggested change
function isBuilderExited(builder: gloas.Builder, currentEpoch: Epoch): boolean {
return builder.withdrawableEpoch <= currentEpoch && builder.balance === 0;
}
function isBuilderExited(builder: {withdrawableEpoch: Epoch; balance: UintNum64}, currentEpoch: Epoch): boolean {
return builder.withdrawableEpoch <= currentEpoch && builder.balance === 0;
}

Comment on lines +47 to +57
private _lastVerifiedSlot: Slot = 0;

get lastVerifiedSlot(): Slot {
return this._lastVerifiedSlot;
}

set lastVerifiedSlot(slot: Slot) {
if (slot > this._lastVerifiedSlot) {
this._lastVerifiedSlot = slot;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Initializing _lastVerifiedSlot to 0 creates ambiguity because 0 is a valid slot (GENESIS_SLOT). Any builder deposits at slot 0 would be skipped during pre-verification because deposit.slot <= cursor (where cursor is 0) evaluates to true. Initialize _lastVerifiedSlot to -1 as a proper sentinel value to avoid skipping slot 0.

Suggested change
private _lastVerifiedSlot: Slot = 0;
get lastVerifiedSlot(): Slot {
return this._lastVerifiedSlot;
}
set lastVerifiedSlot(slot: Slot) {
if (slot > this._lastVerifiedSlot) {
this._lastVerifiedSlot = slot;
}
}
private _lastVerifiedSlot: Slot = -1;
get lastVerifiedSlot(): Slot {
return this._lastVerifiedSlot;
}
set lastVerifiedSlot(slot: Slot) {
if (slot > this._lastVerifiedSlot) {
this._lastVerifiedSlot = slot;
}
}

Comment on lines +99 to +102
clearPreGloasCache(): void {
this.preGloasResultsBySlot.clear();
this._lastVerifiedSlot = 0;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Reset _lastVerifiedSlot to -1 when clearing the pre-Gloas cache.

Suggested change
clearPreGloasCache(): void {
this.preGloasResultsBySlot.clear();
this._lastVerifiedSlot = 0;
}
clearPreGloasCache(): void {
this.preGloasResultsBySlot.clear();
this._lastVerifiedSlot = -1;
}

Comment on lines +594 to +597
clearPreGloasBuilderDepositCache(): void {
const cache = this.cachedState.epochCtx.builderDepositSignatureCache;
if (cache.lastVerifiedSlot !== 0) cache.clearPreGloasCache();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Update the cache clearing condition to check against -1 instead of 0 to match the new sentinel value.

Suggested change
clearPreGloasBuilderDepositCache(): void {
const cache = this.cachedState.epochCtx.builderDepositSignatureCache;
if (cache.lastVerifiedSlot !== 0) cache.clearPreGloasCache();
}
clearPreGloasBuilderDepositCache(): void {
const cache = this.cachedState.epochCtx.builderDepositSignatureCache;
if (cache.lastVerifiedSlot !== -1) cache.clearPreGloasCache();
}

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Performance Report

馃殌馃殌 Significant benchmark improvement detected

Benchmark suite Current: 99cbc5d Previous: 8793c24 Ratio
enrSubnets - fastDeserialize 64 bits 749.00 ns/op 2.3560 us/op 0.32
Full columns - reconstruct half of the blobs out of 10 111.45 us/op 365.88 us/op 0.30
Full benchmark results
Benchmark suite Current: 99cbc5d Previous: 8793c24 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 992.47 us/op 1.1823 ms/op 0.84
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 41.471 us/op 41.028 us/op 1.01
BLS verify - blst 650.21 us/op 744.97 us/op 0.87
BLS verifyMultipleSignatures 3 - blst 1.3601 ms/op 1.3785 ms/op 0.99
BLS verifyMultipleSignatures 8 - blst 2.1636 ms/op 2.2347 ms/op 0.97
BLS verifyMultipleSignatures 32 - blst 6.7892 ms/op 7.5562 ms/op 0.90
BLS verifyMultipleSignatures 64 - blst 13.121 ms/op 14.687 ms/op 0.89
BLS verifyMultipleSignatures 128 - blst 25.450 ms/op 26.958 ms/op 0.94
BLS deserializing 10000 signatures 648.40 ms/op 666.90 ms/op 0.97
BLS deserializing 100000 signatures 6.4875 s/op 6.7677 s/op 0.96
BLS verifyMultipleSignatures - same message - 3 - blst 805.12 us/op 803.18 us/op 1.00
BLS verifyMultipleSignatures - same message - 8 - blst 948.25 us/op 981.40 us/op 0.97
BLS verifyMultipleSignatures - same message - 32 - blst 1.5832 ms/op 1.5452 ms/op 1.02
BLS verifyMultipleSignatures - same message - 64 - blst 2.4167 ms/op 2.5226 ms/op 0.96
BLS verifyMultipleSignatures - same message - 128 - blst 3.9772 ms/op 4.3342 ms/op 0.92
BLS aggregatePubkeys 32 - blst 17.420 us/op 18.802 us/op 0.93
BLS aggregatePubkeys 128 - blst 62.635 us/op 66.567 us/op 0.94
getSlashingsAndExits - default max 49.902 us/op 52.410 us/op 0.95
getSlashingsAndExits - 2k 323.78 us/op 485.41 us/op 0.67
proposeBlockBody type=full, size=empty 850.36 us/op 719.80 us/op 1.18
isKnown best case - 1 super set check 159.00 ns/op 200.00 ns/op 0.80
isKnown normal case - 2 super set checks 157.00 ns/op 169.00 ns/op 0.93
isKnown worse case - 16 super set checks 156.00 ns/op 169.00 ns/op 0.92
validate api signedAggregateAndProof - struct 1.5254 ms/op 1.5846 ms/op 0.96
validate gossip signedAggregateAndProof - struct 1.5157 ms/op 1.5725 ms/op 0.96
batch validate gossip attestation - vc 640000 - chunk 32 104.56 us/op 112.70 us/op 0.93
batch validate gossip attestation - vc 640000 - chunk 64 91.016 us/op 100.65 us/op 0.90
batch validate gossip attestation - vc 640000 - chunk 128 86.014 us/op 94.943 us/op 0.91
batch validate gossip attestation - vc 640000 - chunk 256 83.061 us/op 90.859 us/op 0.91
bytes32 toHexString 289.00 ns/op 301.00 ns/op 0.96
bytes32 Buffer.toString(hex) 166.00 ns/op 168.00 ns/op 0.99
bytes32 Buffer.toString(hex) from Uint8Array 225.00 ns/op 241.00 ns/op 0.93
bytes32 Buffer.toString(hex) + 0x 165.00 ns/op 170.00 ns/op 0.97
Return object 10000 times 0.21430 ns/op 0.22140 ns/op 0.97
Throw Error 10000 times 3.3018 us/op 3.5208 us/op 0.94
toHex 93.471 ns/op 92.221 ns/op 1.01
Buffer.from 88.657 ns/op 88.377 ns/op 1.00
shared Buffer 57.296 ns/op 56.704 ns/op 1.01
fastMsgIdFn sha256 / 200 bytes 1.4670 us/op 1.5140 us/op 0.97
fastMsgIdFn h32 xxhash / 200 bytes 148.00 ns/op 166.00 ns/op 0.89
fastMsgIdFn h64 xxhash / 200 bytes 200.00 ns/op 211.00 ns/op 0.95
fastMsgIdFn sha256 / 1000 bytes 4.7030 us/op 4.9480 us/op 0.95
fastMsgIdFn h32 xxhash / 1000 bytes 243.00 ns/op 250.00 ns/op 0.97
fastMsgIdFn h64 xxhash / 1000 bytes 247.00 ns/op 264.00 ns/op 0.94
fastMsgIdFn sha256 / 10000 bytes 41.570 us/op 43.170 us/op 0.96
fastMsgIdFn h32 xxhash / 10000 bytes 1.2650 us/op 1.3280 us/op 0.95
fastMsgIdFn h64 xxhash / 10000 bytes 821.00 ns/op 866.00 ns/op 0.95
send data - 1000 256B messages 4.4388 ms/op 4.5487 ms/op 0.98
send data - 1000 512B messages 4.5172 ms/op 4.9092 ms/op 0.92
send data - 1000 1024B messages 4.5221 ms/op 4.8415 ms/op 0.93
send data - 1000 1200B messages 4.7143 ms/op 5.3060 ms/op 0.89
send data - 1000 2048B messages 4.9029 ms/op 6.3162 ms/op 0.78
send data - 1000 4096B messages 5.3809 ms/op 6.2555 ms/op 0.86
send data - 1000 16384B messages 16.839 ms/op 40.714 ms/op 0.41
send data - 1000 65536B messages 248.64 ms/op 212.70 ms/op 1.17
enrSubnets - fastDeserialize 64 bits 749.00 ns/op 2.3560 us/op 0.32
enrSubnets - ssz BitVector 64 bits 251.00 ns/op 270.00 ns/op 0.93
enrSubnets - fastDeserialize 4 bits 100.00 ns/op 107.00 ns/op 0.93
enrSubnets - ssz BitVector 4 bits 260.00 ns/op 268.00 ns/op 0.97
prioritizePeers score -10:0 att 32-0.1 sync 2-0 210.28 us/op 210.84 us/op 1.00
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 254.93 us/op 240.97 us/op 1.06
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 353.98 us/op 348.18 us/op 1.02
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 617.86 us/op 626.81 us/op 0.99
prioritizePeers score 0:0 att 64-1 sync 4-1 706.55 us/op 733.33 us/op 0.96
array of 16000 items push then shift 1.3035 us/op 1.3211 us/op 0.99
LinkedList of 16000 items push then shift 7.1410 ns/op 7.3550 ns/op 0.97
array of 16000 items push then pop 67.748 ns/op 70.772 ns/op 0.96
LinkedList of 16000 items push then pop 6.0890 ns/op 6.1160 ns/op 1.00
array of 24000 items push then shift 1.9460 us/op 1.9556 us/op 1.00
LinkedList of 24000 items push then shift 6.5860 ns/op 6.9040 ns/op 0.95
array of 24000 items push then pop 95.004 ns/op 100.62 ns/op 0.94
LinkedList of 24000 items push then pop 6.0960 ns/op 6.1440 ns/op 0.99
intersect bitArray bitLen 8 4.7720 ns/op 4.7910 ns/op 1.00
intersect array and set length 8 29.801 ns/op 29.913 ns/op 1.00
intersect bitArray bitLen 128 24.188 ns/op 24.084 ns/op 1.00
intersect array and set length 128 502.28 ns/op 506.15 ns/op 0.99
bitArray.getTrueBitIndexes() bitLen 128 1.0260 us/op 1.0810 us/op 0.95
bitArray.getTrueBitIndexes() bitLen 248 1.7530 us/op 1.8980 us/op 0.92
bitArray.getTrueBitIndexes() bitLen 512 3.5870 us/op 3.8900 us/op 0.92
Full columns - reconstruct all 6 blobs 156.06 us/op 203.25 us/op 0.77
Full columns - reconstruct half of the blobs out of 6 68.372 us/op 97.023 us/op 0.70
Full columns - reconstruct single blob out of 6 35.738 us/op 36.730 us/op 0.97
Half columns - reconstruct all 6 blobs 398.27 ms/op 393.22 ms/op 1.01
Half columns - reconstruct half of the blobs out of 6 205.35 ms/op 198.59 ms/op 1.03
Half columns - reconstruct single blob out of 6 73.122 ms/op 71.782 ms/op 1.02
Full columns - reconstruct all 10 blobs 202.48 us/op 253.49 us/op 0.80
Full columns - reconstruct half of the blobs out of 10 111.45 us/op 365.88 us/op 0.30
Full columns - reconstruct single blob out of 10 30.359 us/op 30.145 us/op 1.01
Half columns - reconstruct all 10 blobs 678.65 ms/op 645.47 ms/op 1.05
Half columns - reconstruct half of the blobs out of 10 345.63 ms/op 322.99 ms/op 1.07
Half columns - reconstruct single blob out of 10 74.051 ms/op 68.856 ms/op 1.08
Full columns - reconstruct all 20 blobs 1.5532 ms/op 2.1846 ms/op 0.71
Full columns - reconstruct half of the blobs out of 20 215.51 us/op 289.75 us/op 0.74
Full columns - reconstruct single blob out of 20 32.018 us/op 33.645 us/op 0.95
Half columns - reconstruct all 20 blobs 1.3528 s/op 1.2934 s/op 1.05
Half columns - reconstruct half of the blobs out of 20 681.84 ms/op 671.57 ms/op 1.02
Half columns - reconstruct single blob out of 20 73.563 ms/op 71.226 ms/op 1.03
Set add up to 64 items then delete first 2.1789 us/op 2.7383 us/op 0.80
OrderedSet add up to 64 items then delete first 3.4661 us/op 3.5005 us/op 0.99
Set add up to 64 items then delete last 2.4400 us/op 2.4506 us/op 1.00
OrderedSet add up to 64 items then delete last 3.3638 us/op 3.3747 us/op 1.00
Set add up to 64 items then delete middle 2.1935 us/op 2.1937 us/op 1.00
OrderedSet add up to 64 items then delete middle 4.9265 us/op 4.8336 us/op 1.02
Set add up to 128 items then delete first 4.3909 us/op 4.3892 us/op 1.00
OrderedSet add up to 128 items then delete first 6.9256 us/op 7.0041 us/op 0.99
Set add up to 128 items then delete last 4.0680 us/op 4.3011 us/op 0.95
OrderedSet add up to 128 items then delete last 6.1101 us/op 5.9338 us/op 1.03
Set add up to 128 items then delete middle 4.0823 us/op 4.0193 us/op 1.02
OrderedSet add up to 128 items then delete middle 12.390 us/op 11.839 us/op 1.05
Set add up to 256 items then delete first 8.2616 us/op 8.1580 us/op 1.01
OrderedSet add up to 256 items then delete first 12.745 us/op 12.901 us/op 0.99
Set add up to 256 items then delete last 8.1728 us/op 7.9327 us/op 1.03
OrderedSet add up to 256 items then delete last 12.251 us/op 11.750 us/op 1.04
Set add up to 256 items then delete middle 7.9414 us/op 7.8105 us/op 1.02
OrderedSet add up to 256 items then delete middle 39.686 us/op 35.277 us/op 1.12
pass gossip attestations to forkchoice per slot 2.7478 ms/op 2.5572 ms/op 1.07
forkChoice updateHead vc 100000 bc 64 eq 0 430.83 us/op 435.04 us/op 0.99
forkChoice updateHead vc 600000 bc 64 eq 0 2.5604 ms/op 2.6559 ms/op 0.96
forkChoice updateHead vc 1000000 bc 64 eq 0 4.2588 ms/op 4.4127 ms/op 0.97
forkChoice updateHead vc 600000 bc 320 eq 0 2.5732 ms/op 2.6714 ms/op 0.96
forkChoice updateHead vc 600000 bc 1200 eq 0 2.6844 ms/op 2.7037 ms/op 0.99
forkChoice updateHead vc 600000 bc 7200 eq 0 3.5729 ms/op 3.0218 ms/op 1.18
forkChoice updateHead vc 600000 bc 64 eq 1000 3.1064 ms/op 3.1996 ms/op 0.97
forkChoice updateHead vc 600000 bc 64 eq 10000 3.1682 ms/op 3.3204 ms/op 0.95
forkChoice updateHead vc 600000 bc 64 eq 300000 7.5628 ms/op 7.1602 ms/op 1.06
computeDeltas 1400000 validators 0% inactive 12.914 ms/op 13.931 ms/op 0.93
computeDeltas 1400000 validators 10% inactive 12.212 ms/op 12.827 ms/op 0.95
computeDeltas 1400000 validators 20% inactive 11.277 ms/op 11.745 ms/op 0.96
computeDeltas 1400000 validators 50% inactive 8.5516 ms/op 9.1399 ms/op 0.94
computeDeltas 2100000 validators 0% inactive 19.480 ms/op 20.670 ms/op 0.94
computeDeltas 2100000 validators 10% inactive 18.817 ms/op 19.977 ms/op 0.94
computeDeltas 2100000 validators 20% inactive 17.066 ms/op 18.350 ms/op 0.93
computeDeltas 2100000 validators 50% inactive 9.8349 ms/op 13.785 ms/op 0.71
altair processAttestation - 250000 vs - 7PWei normalcase 2.3100 ms/op 1.7804 ms/op 1.30
altair processAttestation - 250000 vs - 7PWei worstcase 3.2250 ms/op 2.7564 ms/op 1.17
altair processAttestation - setStatus - 1/6 committees join 111.18 us/op 101.28 us/op 1.10
altair processAttestation - setStatus - 1/3 committees join 211.34 us/op 207.61 us/op 1.02
altair processAttestation - setStatus - 1/2 committees join 300.02 us/op 283.24 us/op 1.06
altair processAttestation - setStatus - 2/3 committees join 391.15 us/op 379.45 us/op 1.03
altair processAttestation - setStatus - 4/5 committees join 544.70 us/op 519.78 us/op 1.05
altair processAttestation - setStatus - 100% committees join 638.16 us/op 614.01 us/op 1.04
altair processBlock - 250000 vs - 7PWei normalcase 4.6490 ms/op 3.2817 ms/op 1.42
altair processBlock - 250000 vs - 7PWei normalcase hashState 18.529 ms/op 15.481 ms/op 1.20
altair processBlock - 250000 vs - 7PWei worstcase 23.241 ms/op 23.953 ms/op 0.97
altair processBlock - 250000 vs - 7PWei worstcase hashState 47.003 ms/op 44.009 ms/op 1.07
phase0 processBlock - 250000 vs - 7PWei normalcase 1.5803 ms/op 1.3538 ms/op 1.17
phase0 processBlock - 250000 vs - 7PWei worstcase 18.856 ms/op 17.454 ms/op 1.08
altair processEth1Data - 250000 vs - 7PWei normalcase 318.82 us/op 304.69 us/op 1.05
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 10.313 us/op 4.3820 us/op 2.35
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 22.245 us/op 21.842 us/op 1.02
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.1570 us/op 6.5230 us/op 0.94
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.7950 us/op 4.5440 us/op 0.84
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 95.749 us/op 107.73 us/op 0.89
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4208 ms/op 1.5623 ms/op 0.91
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8402 ms/op 2.1314 ms/op 0.86
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8528 ms/op 2.0502 ms/op 0.90
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.6593 ms/op 4.0200 ms/op 0.91
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.1251 ms/op 2.2657 ms/op 0.94
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.9633 ms/op 4.2154 ms/op 0.94
Tree 40 250000 create 385.16 ms/op 338.97 ms/op 1.14
Tree 40 250000 get(125000) 98.841 ns/op 102.27 ns/op 0.97
Tree 40 250000 set(125000) 1.0430 us/op 1.0556 us/op 0.99
Tree 40 250000 toArray() 12.019 ms/op 9.6046 ms/op 1.25
Tree 40 250000 iterate all - toArray() + loop 12.729 ms/op 9.9640 ms/op 1.28
Tree 40 250000 iterate all - get(i) 39.574 ms/op 40.805 ms/op 0.97
Array 250000 create 2.2859 ms/op 2.1267 ms/op 1.07
Array 250000 clone - spread 716.40 us/op 680.39 us/op 1.05
Array 250000 get(125000) 0.30200 ns/op 0.30000 ns/op 1.01
Array 250000 set(125000) 0.30300 ns/op 0.30200 ns/op 1.00
Array 250000 iterate all - loop 58.642 us/op 58.385 us/op 1.00
phase0 afterProcessEpoch - 250000 vs - 7PWei 51.535 ms/op 51.759 ms/op 1.00
Array.fill - length 1000000 2.2683 ms/op 2.2728 ms/op 1.00
Array push - length 1000000 8.3113 ms/op 9.6444 ms/op 0.86
Array.get 0.20881 ns/op 0.21086 ns/op 0.99
Uint8Array.get 0.26299 ns/op 0.25940 ns/op 1.01
phase0 beforeProcessEpoch - 250000 vs - 7PWei 15.198 ms/op 13.228 ms/op 1.15
altair processEpoch - mainnet_e81889 258.87 ms/op 281.20 ms/op 0.92
mainnet_e81889 - altair beforeProcessEpoch 19.355 ms/op 21.201 ms/op 0.91
mainnet_e81889 - altair processJustificationAndFinalization 5.4130 us/op 6.0260 us/op 0.90
mainnet_e81889 - altair processInactivityUpdates 3.5435 ms/op 3.5239 ms/op 1.01
mainnet_e81889 - altair processRewardsAndPenalties 18.895 ms/op 19.574 ms/op 0.97
mainnet_e81889 - altair processRegistryUpdates 575.00 ns/op 551.00 ns/op 1.04
mainnet_e81889 - altair processSlashings 136.00 ns/op 136.00 ns/op 1.00
mainnet_e81889 - altair processEth1DataReset 143.00 ns/op 133.00 ns/op 1.08
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.6758 ms/op 1.6488 ms/op 1.02
mainnet_e81889 - altair processSlashingsReset 706.00 ns/op 730.00 ns/op 0.97
mainnet_e81889 - altair processRandaoMixesReset 1.1830 us/op 1.1870 us/op 1.00
mainnet_e81889 - altair processHistoricalRootsUpdate 133.00 ns/op 133.00 ns/op 1.00
mainnet_e81889 - altair processParticipationFlagUpdates 432.00 ns/op 453.00 ns/op 0.95
mainnet_e81889 - altair processSyncCommitteeUpdates 112.00 ns/op 110.00 ns/op 1.02
mainnet_e81889 - altair afterProcessEpoch 41.559 ms/op 42.548 ms/op 0.98
capella processEpoch - mainnet_e217614 765.07 ms/op 842.67 ms/op 0.91
mainnet_e217614 - capella beforeProcessEpoch 62.077 ms/op 57.620 ms/op 1.08
mainnet_e217614 - capella processJustificationAndFinalization 5.3030 us/op 5.7760 us/op 0.92
mainnet_e217614 - capella processInactivityUpdates 12.640 ms/op 12.606 ms/op 1.00
mainnet_e217614 - capella processRewardsAndPenalties 82.758 ms/op 88.324 ms/op 0.94
mainnet_e217614 - capella processRegistryUpdates 4.5740 us/op 4.5510 us/op 1.01
mainnet_e217614 - capella processSlashings 131.00 ns/op 135.00 ns/op 0.97
mainnet_e217614 - capella processEth1DataReset 134.00 ns/op 126.00 ns/op 1.06
mainnet_e217614 - capella processEffectiveBalanceUpdates 5.5268 ms/op 7.7402 ms/op 0.71
mainnet_e217614 - capella processSlashingsReset 687.00 ns/op 684.00 ns/op 1.00
mainnet_e217614 - capella processRandaoMixesReset 1.1710 us/op 1.1800 us/op 0.99
mainnet_e217614 - capella processHistoricalRootsUpdate 135.00 ns/op 130.00 ns/op 1.04
mainnet_e217614 - capella processParticipationFlagUpdates 431.00 ns/op 434.00 ns/op 0.99
mainnet_e217614 - capella afterProcessEpoch 109.19 ms/op 109.52 ms/op 1.00
phase0 processEpoch - mainnet_e58758 287.59 ms/op 305.08 ms/op 0.94
mainnet_e58758 - phase0 beforeProcessEpoch 60.092 ms/op 61.482 ms/op 0.98
mainnet_e58758 - phase0 processJustificationAndFinalization 5.2490 us/op 6.7470 us/op 0.78
mainnet_e58758 - phase0 processRewardsAndPenalties 15.338 ms/op 16.793 ms/op 0.91
mainnet_e58758 - phase0 processRegistryUpdates 2.2850 us/op 2.3200 us/op 0.98
mainnet_e58758 - phase0 processSlashings 127.00 ns/op 131.00 ns/op 0.97
mainnet_e58758 - phase0 processEth1DataReset 128.00 ns/op 201.00 ns/op 0.64
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.1813 ms/op 985.32 us/op 1.20
mainnet_e58758 - phase0 processSlashingsReset 868.00 ns/op 935.00 ns/op 0.93
mainnet_e58758 - phase0 processRandaoMixesReset 1.1290 us/op 1.3080 us/op 0.86
mainnet_e58758 - phase0 processHistoricalRootsUpdate 130.00 ns/op 132.00 ns/op 0.98
mainnet_e58758 - phase0 processParticipationRecordUpdates 1000.0 ns/op 1.1860 us/op 0.84
mainnet_e58758 - phase0 afterProcessEpoch 32.435 ms/op 34.259 ms/op 0.95
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.0113 ms/op 1.1077 ms/op 0.91
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.2288 ms/op 1.6784 ms/op 0.73
altair processInactivityUpdates - 250000 normalcase 10.893 ms/op 11.887 ms/op 0.92
altair processInactivityUpdates - 250000 worstcase 10.459 ms/op 12.933 ms/op 0.81
phase0 processRegistryUpdates - 250000 normalcase 2.3690 us/op 2.3390 us/op 1.01
phase0 processRegistryUpdates - 250000 badcase_full_deposits 148.96 us/op 142.31 us/op 1.05
phase0 processRegistryUpdates - 250000 worstcase 0.5 61.497 ms/op 66.215 ms/op 0.93
altair processRewardsAndPenalties - 250000 normalcase 12.910 ms/op 15.451 ms/op 0.84
altair processRewardsAndPenalties - 250000 worstcase 12.579 ms/op 14.448 ms/op 0.87
phase0 getAttestationDeltas - 250000 normalcase 5.7834 ms/op 5.4109 ms/op 1.07
phase0 getAttestationDeltas - 250000 worstcase 5.3980 ms/op 5.4879 ms/op 0.98
phase0 processSlashings - 250000 worstcase 62.117 us/op 64.074 us/op 0.97
altair processSyncCommitteeUpdates - 250000 9.8754 ms/op 10.191 ms/op 0.97
BeaconState.hashTreeRoot - No change 166.00 ns/op 164.00 ns/op 1.01
BeaconState.hashTreeRoot - 1 full validator 56.942 us/op 69.478 us/op 0.82
BeaconState.hashTreeRoot - 32 full validator 629.79 us/op 845.26 us/op 0.75
BeaconState.hashTreeRoot - 512 full validator 5.9682 ms/op 6.5126 ms/op 0.92
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 70.320 us/op 83.551 us/op 0.84
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 998.33 us/op 1.2643 ms/op 0.79
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 12.971 ms/op 14.415 ms/op 0.90
BeaconState.hashTreeRoot - 1 balances 56.040 us/op 59.715 us/op 0.94
BeaconState.hashTreeRoot - 32 balances 547.50 us/op 615.89 us/op 0.89
BeaconState.hashTreeRoot - 512 balances 4.7012 ms/op 4.8284 ms/op 0.97
BeaconState.hashTreeRoot - 250000 balances 92.302 ms/op 102.75 ms/op 0.90
aggregationBits - 2048 els - zipIndexesInBitList 19.650 us/op 19.646 us/op 1.00
regular array get 100000 times 22.829 us/op 23.104 us/op 0.99
wrappedArray get 100000 times 22.882 us/op 22.968 us/op 1.00
arrayWithProxy get 100000 times 10.162 ms/op 14.383 ms/op 0.71
ssz.Root.equals 21.444 ns/op 21.695 ns/op 0.99
byteArrayEquals 21.205 ns/op 21.363 ns/op 0.99
Buffer.compare 9.1220 ns/op 9.0370 ns/op 1.01
processSlot - 1 slots 9.0370 us/op 8.8090 us/op 1.03
processSlot - 32 slots 1.7425 ms/op 2.1721 ms/op 0.80
upgradeStateToGloas - onboard 5000 builders 1.5360 s/op
upgradeStateToGloas - onboard 10000 builders 2.9331 s/op
upgradeStateToGloas - onboard 20000 builders 5.7594 s/op
upgradeStateToGloas - onboard 30000 builders 8.6422 s/op
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 2.5102 ms/op 2.4305 ms/op 1.03
getCommitteeAssignments - req 1 vs - 250000 vc 1.6652 ms/op 1.6680 ms/op 1.00
getCommitteeAssignments - req 100 vs - 250000 vc 3.4157 ms/op 3.4869 ms/op 0.98
getCommitteeAssignments - req 1000 vs - 250000 vc 3.7337 ms/op 3.7607 ms/op 0.99
findModifiedValidators - 10000 modified validators 684.84 ms/op 941.51 ms/op 0.73
findModifiedValidators - 1000 modified validators 428.93 ms/op 510.49 ms/op 0.84
findModifiedValidators - 100 modified validators 271.81 ms/op 372.32 ms/op 0.73
findModifiedValidators - 10 modified validators 143.19 ms/op 297.13 ms/op 0.48
findModifiedValidators - 1 modified validators 163.36 ms/op 184.13 ms/op 0.89
findModifiedValidators - no difference 173.53 ms/op 171.88 ms/op 1.01
migrate state 1500000 validators, 3400 modified, 2000 new 4.3794 s/op 2.8013 s/op 1.56
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.5300 ns/op 3.7600 ns/op 0.94
state getBlockRootAtSlot - 250000 vs - 7PWei 383.51 ns/op 299.85 ns/op 1.28
computeProposerIndex 100000 validators 1.3532 ms/op 1.3715 ms/op 0.99
getNextSyncCommitteeIndices 1000 validators 2.9151 ms/op 2.9793 ms/op 0.98
getNextSyncCommitteeIndices 10000 validators 25.786 ms/op 26.860 ms/op 0.96
getNextSyncCommitteeIndices 100000 validators 84.451 ms/op 91.886 ms/op 0.92
computeProposers - vc 250000 560.93 us/op 562.65 us/op 1.00
computeEpochShuffling - vc 250000 40.249 ms/op 39.937 ms/op 1.01
getNextSyncCommittee - vc 250000 9.6368 ms/op 9.6759 ms/op 1.00
nodejs block root to RootHex using toHex 96.347 ns/op 97.105 ns/op 0.99
nodejs block root to RootHex using toRootHex 58.472 ns/op 58.366 ns/op 1.00
nodejs fromHex(blob) 938.84 us/op 983.94 us/op 0.95
nodejs fromHexInto(blob) 646.60 us/op 672.00 us/op 0.96
nodejs block root to RootHex using the deprecated toHexString 392.90 ns/op 525.68 ns/op 0.75
nodejs byteArrayEquals 32 bytes (block root) 25.994 ns/op 26.210 ns/op 0.99
nodejs byteArrayEquals 48 bytes (pubkey) 37.305 ns/op 38.065 ns/op 0.98
nodejs byteArrayEquals 96 bytes (signature) 37.121 ns/op 38.424 ns/op 0.97
nodejs byteArrayEquals 1024 bytes 42.755 ns/op 45.301 ns/op 0.94
nodejs byteArrayEquals 131072 bytes (blob) 1.7620 us/op 1.7954 us/op 0.98
browser block root to RootHex using toHex 145.72 ns/op 147.76 ns/op 0.99
browser block root to RootHex using toRootHex 131.64 ns/op 132.97 ns/op 0.99
browser fromHex(blob) 1.6321 ms/op 1.5438 ms/op 1.06
browser fromHexInto(blob) 643.11 us/op 659.35 us/op 0.98
browser block root to RootHex using the deprecated toHexString 510.06 ns/op 361.43 ns/op 1.41
browser byteArrayEquals 32 bytes (block root) 27.989 ns/op 27.784 ns/op 1.01
browser byteArrayEquals 48 bytes (pubkey) 39.340 ns/op 39.181 ns/op 1.00
browser byteArrayEquals 96 bytes (signature) 73.480 ns/op 73.136 ns/op 1.00
browser byteArrayEquals 1024 bytes 758.67 ns/op 750.63 ns/op 1.01
browser byteArrayEquals 131072 bytes (blob) 95.542 us/op 94.373 us/op 1.01

by benchmarkbot/action

@twoeths

twoeths commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

closing in favor of #9727

@twoeths twoeths closed this Jul 31, 2026
@twoeths
twoeths deleted the te/improve_onboard_builders_2 branch July 31, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants