Skip to content

feat: make lodestar-z BLS opt-in#9652

Draft
wemeetagain wants to merge 128 commits into
unstablefrom
wemeetagain/opt-in-zig-bls
Draft

feat: make lodestar-z BLS opt-in#9652
wemeetagain wants to merge 128 commits into
unstablefrom
wemeetagain/opt-in-zig-bls

Conversation

@wemeetagain

Copy link
Copy Markdown
Member

Summary

  • add a single @lodestar/state-transition/bls subpath that selects the BLS implementation at module load time
  • preserve @chainsafe/blst and the original JavaScript pubkey cache as the default
  • route application BLS and pubkey-cache usage consistently through the subpath
  • add the hidden --zig-bls CLI flag, applied in applyPreset.ts before the rest of the source tree loads
  • use the lodestar-z native pubkey cache process-wide, including worker threads, only when explicitly enabled

Impact

Existing users keep the current BLS implementation and cache behavior by default. The lodestar-z implementation can be evaluated explicitly with --zig-bls without mixing implementations within one process.

Validation

  • pnpm lint
  • pnpm check-types
  • pnpm test:unit — 3,239 passed, 9 skipped, 1 todo
  • focused BLS selection, native pubkey-cache worker, and early CLI configuration tests

This PR was written with assistance from OpenAI Codex.

spiral-ladder and others added 27 commits July 1, 2026 18:36
## Motivation

The lodestar-z pubkey cache introduced in #8900 is a process-global
singleton shared across threads without locking. The historical state
regen worker reads it (`getIndex` during sync committee cache
computation) and writes it (`addPubkey` when replaying deposit blocks)
while the main thread does the same. A main-thread `set()` that outgrows
the reserved capacity reallocates both native structures and frees the
old buffers, so a concurrent read from the worker thread is a
use-after-free. The previous `ensureCapacity(validators.length)` left
zero headroom, meaning the first deposit after startup triggered exactly
this realloc.

## Description

- Reserve headroom when populating the cache at startup so it does not
realloc during a realistic process lifetime. Sized as 3 months of
worst-case registry growth (`MAX_PENDING_DEPOSITS_PER_EPOCH` new
validators per epoch), which is over a year at organic rates and beyond
the update cadence of any Lodestar node observed on mainnet. On mainnet
this is ~324k slots, ~31MB of virtual memory and ~0 resident.
ChainSafe/lodestar-z#481 aligns the native growth policy to the same
step, so if the reservation is ever exceeded the cache extends by
another 3-month window instead of doubling.
- Skip no-op rewrites in `EpochCache.addPubkey` when the identical
pubkey is already cached at that index. Replayed deposits during
historical state regen always hit this path, so the worker thread no
longer writes to the shared cache at all. A different pubkey at an
existing index still overwrites, preserving existing behavior on forks
with conflicting deposits.

This is a mitigation, not a fix. The remaining race (a genuinely new
main-thread insert concurrent with a worker hashmap probe) can at worst
fail a single historical state query. The proper fix is locking around
the pubkeys binding in lodestar-z, at which point the headroom becomes a
realloc-avoidance nicety.

---------

Co-authored-by: matthewkeil <me@matthewkeil.com>

@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 support for a new Zig-based BLS implementation (lodestar-z) alongside the existing blst library. It abstracts BLS types and operations under a unified interface in @lodestar/state-transition/bls and updates imports across the codebase. Review feedback suggests guarding against undefined process.env in non-Node environments to prevent runtime errors, and avoiding the mutation of imported bindings with Object.assign by using class inheritance instead.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

const BLS_IMPLEMENTATION_ENV = "LODESTAR_BLS_IMPLEMENTATION";

export function getBlsImplementation(): BlsImplementation {
const implementation = typeof process === "undefined" ? undefined : process.env[BLS_IMPLEMENTATION_ENV];

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

In environments where process is polyfilled or defined but process.env is not present (such as certain edge runtimes or bundler configurations), accessing process.env directly will throw a TypeError. Guarding the access to process.env ensures better compatibility and robustness across different environments.

Suggested change
const implementation = typeof process === "undefined" ? undefined : process.env[BLS_IMPLEMENTATION_ENV];
const implementation = typeof process === "undefined" || !process.env ? undefined : process.env[BLS_IMPLEMENTATION_ENV];

Comment on lines +121 to +131
bls = {
...(blsModule as unknown as BlsModule),
PublicKey: Object.assign(blsModule.PublicKey, {
COMPRESS_SIZE: blsModule.PUBLIC_KEY_LENGTH_COMPRESSED,
SERIALIZE_SIZE: blsModule.PUBLIC_KEY_LENGTH_UNCOMPRESSED,
}),
Signature: Object.assign(blsModule.Signature, {
COMPRESS_SIZE: blsModule.SIGNATURE_LENGTH_COMPRESSED,
SERIALIZE_SIZE: blsModule.SIGNATURE_LENGTH_UNCOMPRESSED,
}),
};

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

Using Object.assign directly on the imported blsModule.PublicKey and blsModule.Signature classes mutates the shared module's exports. In strict ESM environments, or when bundled/minified, mutating imported bindings can throw errors or lead to unexpected side effects.\n\nUsing standard class inheritance avoids mutating the imported classes while cleanly inheriting all static and prototype methods.

  class PublicKey extends blsModule.PublicKey {\n    static COMPRESS_SIZE = blsModule.PUBLIC_KEY_LENGTH_COMPRESSED;\n    static SERIALIZE_SIZE = blsModule.PUBLIC_KEY_LENGTH_UNCOMPRESSED;\n  }\n  class Signature extends blsModule.Signature {\n    static COMPRESS_SIZE = blsModule.SIGNATURE_LENGTH_COMPRESSED;\n    static SERIALIZE_SIZE = blsModule.SIGNATURE_LENGTH_UNCOMPRESSED;\n  }\n  bls = {\n    ...(blsModule as unknown as BlsModule),\n    PublicKey,\n    Signature,\n  };

@github-actions

Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: ae959d2 Previous: 242f99e Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 854.35 us/op 849.75 us/op 1.01
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 40.052 us/op 40.335 us/op 0.99
BLS verify - blst 721.56 us/op 774.62 us/op 0.93
BLS verifyMultipleSignatures 3 - blst 1.3787 ms/op 1.3995 ms/op 0.99
BLS verifyMultipleSignatures 8 - blst 2.2029 ms/op 2.2066 ms/op 1.00
BLS verifyMultipleSignatures 32 - blst 7.0186 ms/op 6.8880 ms/op 1.02
BLS verifyMultipleSignatures 64 - blst 13.688 ms/op 13.358 ms/op 1.02
BLS verifyMultipleSignatures 128 - blst 26.376 ms/op 25.886 ms/op 1.02
BLS deserializing 10000 signatures 645.68 ms/op 642.21 ms/op 1.01
BLS deserializing 100000 signatures 6.4198 s/op 6.3907 s/op 1.00
BLS verifyMultipleSignatures - same message - 3 - blst 821.86 us/op 823.40 us/op 1.00
BLS verifyMultipleSignatures - same message - 8 - blst 899.53 us/op 960.64 us/op 0.94
BLS verifyMultipleSignatures - same message - 32 - blst 1.5252 ms/op 1.6328 ms/op 0.93
BLS verifyMultipleSignatures - same message - 64 - blst 2.3706 ms/op 2.3814 ms/op 1.00
BLS verifyMultipleSignatures - same message - 128 - blst 4.0652 ms/op 4.1683 ms/op 0.98
BLS aggregatePubkeys 32 - blst 17.808 us/op 17.647 us/op 1.01
BLS aggregatePubkeys 128 - blst 64.400 us/op 64.377 us/op 1.00
getSlashingsAndExits - default max 46.657 us/op 48.178 us/op 0.97
getSlashingsAndExits - 2k 345.70 us/op 327.53 us/op 1.06
proposeBlockBody type=full, size=empty 700.98 us/op 889.08 us/op 0.79
isKnown best case - 1 super set check 210.00 ns/op 160.00 ns/op 1.31
isKnown normal case - 2 super set checks 181.00 ns/op 163.00 ns/op 1.11
isKnown worse case - 16 super set checks 178.00 ns/op 159.00 ns/op 1.12
validate api signedAggregateAndProof - struct 1.5297 ms/op 1.5611 ms/op 0.98
validate gossip signedAggregateAndProof - struct 1.5287 ms/op 1.5537 ms/op 0.98
batch validate gossip attestation - vc 640000 - chunk 32 105.57 us/op 109.46 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 64 92.951 us/op 94.226 us/op 0.99
batch validate gossip attestation - vc 640000 - chunk 128 85.965 us/op 87.893 us/op 0.98
batch validate gossip attestation - vc 640000 - chunk 256 82.651 us/op 84.951 us/op 0.97
bytes32 toHexString 303.00 ns/op 299.00 ns/op 1.01
bytes32 Buffer.toString(hex) 166.00 ns/op 158.00 ns/op 1.05
bytes32 Buffer.toString(hex) from Uint8Array 236.00 ns/op 218.00 ns/op 1.08
bytes32 Buffer.toString(hex) + 0x 166.00 ns/op 158.00 ns/op 1.05
Return object 10000 times 0.21600 ns/op 0.21170 ns/op 1.02
Throw Error 10000 times 3.3603 us/op 3.3242 us/op 1.01
toHex 98.993 ns/op 99.550 ns/op 0.99
Buffer.from 89.758 ns/op 88.863 ns/op 1.01
shared Buffer 63.013 ns/op 60.890 ns/op 1.03
fastMsgIdFn sha256 / 200 bytes 1.4870 us/op 1.4880 us/op 1.00
fastMsgIdFn h32 xxhash / 200 bytes 161.00 ns/op 155.00 ns/op 1.04
fastMsgIdFn h64 xxhash / 200 bytes 204.00 ns/op 196.00 ns/op 1.04
fastMsgIdFn sha256 / 1000 bytes 4.7730 us/op 4.8110 us/op 0.99
fastMsgIdFn h32 xxhash / 1000 bytes 245.00 ns/op 245.00 ns/op 1.00
fastMsgIdFn h64 xxhash / 1000 bytes 255.00 ns/op 248.00 ns/op 1.03
fastMsgIdFn sha256 / 10000 bytes 42.096 us/op 42.939 us/op 0.98
fastMsgIdFn h32 xxhash / 10000 bytes 1.2840 us/op 1.2550 us/op 1.02
fastMsgIdFn h64 xxhash / 10000 bytes 826.00 ns/op 813.00 ns/op 1.02
send data - 1000 256B messages 3.8398 ms/op 3.9462 ms/op 0.97
send data - 1000 512B messages 5.1044 ms/op 4.8017 ms/op 1.06
send data - 1000 1024B messages 5.1002 ms/op 5.2959 ms/op 0.96
send data - 1000 1200B messages 5.7104 ms/op 6.0873 ms/op 0.94
send data - 1000 2048B messages 22.654 ms/op 17.904 ms/op 1.27
send data - 1000 4096B messages 85.632 ms/op 101.56 ms/op 0.84
send data - 1000 16384B messages 418.14 ms/op 299.08 ms/op 1.40
send data - 1000 65536B messages 2.0308 s/op 1.3788 s/op 1.47
enrSubnets - fastDeserialize 64 bits 749.00 ns/op 768.00 ns/op 0.98
enrSubnets - ssz BitVector 64 bits 269.00 ns/op 256.00 ns/op 1.05
enrSubnets - fastDeserialize 4 bits 104.00 ns/op 103.00 ns/op 1.01
enrSubnets - ssz BitVector 4 bits 268.00 ns/op 264.00 ns/op 1.02
prioritizePeers score -10:0 att 32-0.1 sync 2-0 204.66 us/op 210.45 us/op 0.97
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 243.64 us/op 239.43 us/op 1.02
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 346.96 us/op 347.13 us/op 1.00
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 609.84 us/op 625.55 us/op 0.97
prioritizePeers score 0:0 att 64-1 sync 4-1 709.79 us/op 718.79 us/op 0.99
array of 16000 items push then shift 1.3273 us/op 1.3033 us/op 1.02
LinkedList of 16000 items push then shift 7.1020 ns/op 6.9030 ns/op 1.03
array of 16000 items push then pop 66.872 ns/op 66.728 ns/op 1.00
LinkedList of 16000 items push then pop 6.0510 ns/op 5.9840 ns/op 1.01
array of 24000 items push then shift 1.9493 us/op 1.9184 us/op 1.02
LinkedList of 24000 items push then shift 6.6120 ns/op 6.5110 ns/op 1.02
array of 24000 items push then pop 93.913 ns/op 93.364 ns/op 1.01
LinkedList of 24000 items push then pop 6.0310 ns/op 6.0900 ns/op 0.99
intersect bitArray bitLen 8 4.8330 ns/op 4.7540 ns/op 1.02
intersect array and set length 8 29.812 ns/op 31.272 ns/op 0.95
intersect bitArray bitLen 128 24.391 ns/op 23.986 ns/op 1.02
intersect array and set length 128 503.80 ns/op 506.21 ns/op 1.00
bitArray.getTrueBitIndexes() bitLen 128 1.0120 us/op 1.0790 us/op 0.94
bitArray.getTrueBitIndexes() bitLen 248 1.7510 us/op 1.7720 us/op 0.99
bitArray.getTrueBitIndexes() bitLen 512 3.5110 us/op 3.5980 us/op 0.98
Full columns - reconstruct all 6 blobs 113.76 us/op 112.48 us/op 1.01
Full columns - reconstruct half of the blobs out of 6 65.795 us/op 126.12 us/op 0.52
Full columns - reconstruct single blob out of 6 30.389 us/op 32.456 us/op 0.94
Half columns - reconstruct all 6 blobs 381.12 ms/op 391.21 ms/op 0.97
Half columns - reconstruct half of the blobs out of 6 190.90 ms/op 198.02 ms/op 0.96
Half columns - reconstruct single blob out of 6 67.650 ms/op 68.949 ms/op 0.98
Set add up to 64 items then delete first 1.6809 us/op 1.6908 us/op 0.99
OrderedSet add up to 64 items then delete first 2.6811 us/op 2.5632 us/op 1.05
Set add up to 64 items then delete last 1.9804 us/op 2.0332 us/op 0.97
OrderedSet add up to 64 items then delete last 3.0230 us/op 2.8726 us/op 1.05
Set add up to 64 items then delete middle 1.9663 us/op 1.9459 us/op 1.01
OrderedSet add up to 64 items then delete middle 4.6791 us/op 4.3348 us/op 1.08
Set add up to 128 items then delete first 3.8286 us/op 3.9569 us/op 0.97
OrderedSet add up to 128 items then delete first 6.0093 us/op 5.8860 us/op 1.02
Set add up to 128 items then delete last 3.8711 us/op 3.9394 us/op 0.98
OrderedSet add up to 128 items then delete last 5.6318 us/op 5.8259 us/op 0.97
Set add up to 128 items then delete middle 3.8401 us/op 3.7874 us/op 1.01
OrderedSet add up to 128 items then delete middle 11.656 us/op 11.390 us/op 1.02
Set add up to 256 items then delete first 7.5406 us/op 7.8167 us/op 0.96
OrderedSet add up to 256 items then delete first 12.152 us/op 12.139 us/op 1.00
Set add up to 256 items then delete last 7.4968 us/op 7.9914 us/op 0.94
OrderedSet add up to 256 items then delete last 11.482 us/op 11.449 us/op 1.00
Set add up to 256 items then delete middle 7.5476 us/op 7.5067 us/op 1.01
OrderedSet add up to 256 items then delete middle 35.849 us/op 34.879 us/op 1.03
runFastConfirmationRules vc:100000 bc:96 eq:0 4.5390 ms/op 5.2062 ms/op 0.87
runFastConfirmationRules vc:600000 bc:96 eq:0 34.465 ms/op 34.913 ms/op 0.99
runFastConfirmationRules vc:1000000 bc:96 eq:0 57.514 ms/op 58.268 ms/op 0.99
runFastConfirmationRules vc:600000 bc:320 eq:0 34.889 ms/op 34.264 ms/op 1.02
runFastConfirmationRules vc:100000 bc:96 eq:1000 1.1027 s/op 1.0972 s/op 1.01
pass gossip attestations to forkchoice per slot 2.5999 ms/op 2.5620 ms/op 1.01
forkChoice updateHead vc 100000 bc 64 eq 0 431.75 us/op 469.25 us/op 0.92
forkChoice updateHead vc 600000 bc 64 eq 0 2.5275 ms/op 2.6961 ms/op 0.94
forkChoice updateHead vc 1000000 bc 64 eq 0 4.2292 ms/op 4.5438 ms/op 0.93
forkChoice updateHead vc 600000 bc 320 eq 0 2.5202 ms/op 2.7730 ms/op 0.91
forkChoice updateHead vc 600000 bc 1200 eq 0 2.6073 ms/op 2.7414 ms/op 0.95
forkChoice updateHead vc 600000 bc 7200 eq 0 2.8007 ms/op 3.0956 ms/op 0.90
forkChoice updateHead vc 600000 bc 64 eq 1000 2.5751 ms/op 2.8101 ms/op 0.92
forkChoice updateHead vc 600000 bc 64 eq 10000 2.6383 ms/op 2.8573 ms/op 0.92
forkChoice updateHead vc 600000 bc 64 eq 300000 6.9111 ms/op 6.9766 ms/op 0.99
computeDeltas 1400000 validators 0% inactive 12.565 ms/op 13.643 ms/op 0.92
computeDeltas 1400000 validators 10% inactive 11.820 ms/op 12.650 ms/op 0.93
computeDeltas 1400000 validators 20% inactive 11.222 ms/op 11.926 ms/op 0.94
computeDeltas 1400000 validators 50% inactive 9.2135 ms/op 9.6828 ms/op 0.95
computeDeltas 2100000 validators 0% inactive 20.283 ms/op 19.912 ms/op 1.02
computeDeltas 2100000 validators 10% inactive 17.885 ms/op 18.535 ms/op 0.96
computeDeltas 2100000 validators 20% inactive 17.216 ms/op 17.921 ms/op 0.96
computeDeltas 2100000 validators 50% inactive 11.342 ms/op 14.587 ms/op 0.78
altair processAttestation - 250000 vs - 7PWei normalcase 1.6915 ms/op 1.6992 ms/op 1.00
altair processAttestation - 250000 vs - 7PWei worstcase 2.4753 ms/op 2.4287 ms/op 1.02
altair processAttestation - setStatus - 1/6 committees join 100.11 us/op 100.00 us/op 1.00
altair processAttestation - setStatus - 1/3 committees join 202.84 us/op 201.11 us/op 1.01
altair processAttestation - setStatus - 1/2 committees join 300.08 us/op 287.20 us/op 1.04
altair processAttestation - setStatus - 2/3 committees join 382.36 us/op 377.70 us/op 1.01
altair processAttestation - setStatus - 4/5 committees join 511.41 us/op 526.53 us/op 0.97
altair processAttestation - setStatus - 100% committees join 619.27 us/op 627.51 us/op 0.99
altair processBlock - 250000 vs - 7PWei normalcase 4.6572 ms/op 3.0758 ms/op 1.51
altair processBlock - 250000 vs - 7PWei normalcase hashState 20.338 ms/op 11.982 ms/op 1.70
altair processBlock - 250000 vs - 7PWei worstcase 23.977 ms/op 20.063 ms/op 1.20
altair processBlock - 250000 vs - 7PWei worstcase hashState 43.239 ms/op 40.500 ms/op 1.07
phase0 processBlock - 250000 vs - 7PWei normalcase 1.3085 ms/op 1.2782 ms/op 1.02
phase0 processBlock - 250000 vs - 7PWei worstcase 19.624 ms/op 17.330 ms/op 1.13
altair processEth1Data - 250000 vs - 7PWei normalcase 326.79 us/op 302.30 us/op 1.08
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.6270 us/op 7.9130 us/op 0.46
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 20.974 us/op 21.598 us/op 0.97
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 5.9020 us/op 6.9170 us/op 0.85
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 4.9340 us/op 3.7450 us/op 1.32
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 95.333 us/op 97.795 us/op 0.97
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4216 ms/op 1.4110 ms/op 1.01
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8674 ms/op 1.8415 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8504 ms/op 1.8406 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.6766 ms/op 3.6775 ms/op 1.00
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.0837 ms/op 2.0596 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.0207 ms/op 4.1019 ms/op 0.98
Tree 40 250000 create 308.47 ms/op 323.94 ms/op 0.95
Tree 40 250000 get(125000) 96.724 ns/op 103.30 ns/op 0.94
Tree 40 250000 set(125000) 1.0759 us/op 1.1147 us/op 0.97
Tree 40 250000 toArray() 9.3221 ms/op 9.9179 ms/op 0.94
Tree 40 250000 iterate all - toArray() + loop 9.2819 ms/op 9.7793 ms/op 0.95
Tree 40 250000 iterate all - get(i) 34.816 ms/op 39.048 ms/op 0.89
Array 250000 create 2.0852 ms/op 2.1598 ms/op 0.97
Array 250000 clone - spread 657.79 us/op 666.17 us/op 0.99
Array 250000 get(125000) 0.29500 ns/op 0.29600 ns/op 1.00
Array 250000 set(125000) 0.29800 ns/op 0.29600 ns/op 1.01
Array 250000 iterate all - loop 57.621 us/op 56.803 us/op 1.01
phase0 afterProcessEpoch - 250000 vs - 7PWei 39.012 ms/op 52.527 ms/op 0.74
Array.fill - length 1000000 2.1766 ms/op 2.2633 ms/op 0.96
Array push - length 1000000 7.4520 ms/op 7.8570 ms/op 0.95
Array.get 0.20638 ns/op 0.20569 ns/op 1.00
Uint8Array.get 0.27383 ns/op 0.25173 ns/op 1.09
phase0 beforeProcessEpoch - 250000 vs - 7PWei 13.024 ms/op 14.411 ms/op 0.90
altair processEpoch - mainnet_e81889 243.84 ms/op 261.00 ms/op 0.93
mainnet_e81889 - altair beforeProcessEpoch 13.993 ms/op 14.689 ms/op 0.95
mainnet_e81889 - altair processJustificationAndFinalization 4.6350 us/op 5.4560 us/op 0.85
mainnet_e81889 - altair processInactivityUpdates 3.5154 ms/op 3.7063 ms/op 0.95
mainnet_e81889 - altair processRewardsAndPenalties 17.699 ms/op 19.795 ms/op 0.89
mainnet_e81889 - altair processRegistryUpdates 546.00 ns/op 565.00 ns/op 0.97
mainnet_e81889 - altair processSlashings 137.00 ns/op 137.00 ns/op 1.00
mainnet_e81889 - altair processEth1DataReset 135.00 ns/op 128.00 ns/op 1.05
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.4151 ms/op 1.7601 ms/op 0.80
mainnet_e81889 - altair processSlashingsReset 695.00 ns/op 694.00 ns/op 1.00
mainnet_e81889 - altair processRandaoMixesReset 994.00 ns/op 1.1210 us/op 0.89
mainnet_e81889 - altair processHistoricalRootsUpdate 137.00 ns/op 133.00 ns/op 1.03
mainnet_e81889 - altair processParticipationFlagUpdates 433.00 ns/op 433.00 ns/op 1.00
mainnet_e81889 - altair processSyncCommitteeUpdates 108.00 ns/op 117.00 ns/op 0.92
mainnet_e81889 - altair afterProcessEpoch 41.173 ms/op 43.501 ms/op 0.95
capella processEpoch - mainnet_e217614 783.78 ms/op 780.16 ms/op 1.00
mainnet_e217614 - capella beforeProcessEpoch 64.971 ms/op 57.543 ms/op 1.13
mainnet_e217614 - capella processJustificationAndFinalization 6.2000 us/op 5.4830 us/op 1.13
mainnet_e217614 - capella processInactivityUpdates 13.673 ms/op 11.423 ms/op 1.20
mainnet_e217614 - capella processRewardsAndPenalties 91.695 ms/op 91.911 ms/op 1.00
mainnet_e217614 - capella processRegistryUpdates 4.6040 us/op 4.6530 us/op 0.99
mainnet_e217614 - capella processSlashings 133.00 ns/op 131.00 ns/op 1.02
mainnet_e217614 - capella processEth1DataReset 132.00 ns/op 126.00 ns/op 1.05
mainnet_e217614 - capella processEffectiveBalanceUpdates 6.6395 ms/op 8.4449 ms/op 0.79
mainnet_e217614 - capella processSlashingsReset 691.00 ns/op 674.00 ns/op 1.03
mainnet_e217614 - capella processRandaoMixesReset 1.0690 us/op 1.0410 us/op 1.03
mainnet_e217614 - capella processHistoricalRootsUpdate 134.00 ns/op 126.00 ns/op 1.06
mainnet_e217614 - capella processParticipationFlagUpdates 448.00 ns/op 419.00 ns/op 1.07
mainnet_e217614 - capella afterProcessEpoch 110.28 ms/op 110.85 ms/op 0.99
phase0 processEpoch - mainnet_e58758 276.69 ms/op 274.52 ms/op 1.01
mainnet_e58758 - phase0 beforeProcessEpoch 52.017 ms/op 56.788 ms/op 0.92
mainnet_e58758 - phase0 processJustificationAndFinalization 5.3220 us/op 5.5860 us/op 0.95
mainnet_e58758 - phase0 processRewardsAndPenalties 15.423 ms/op 15.674 ms/op 0.98
mainnet_e58758 - phase0 processRegistryUpdates 2.2680 us/op 2.2610 us/op 1.00
mainnet_e58758 - phase0 processSlashings 134.00 ns/op 256.00 ns/op 0.52
mainnet_e58758 - phase0 processEth1DataReset 215.00 ns/op 131.00 ns/op 1.64
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 940.29 us/op 863.10 us/op 1.09
mainnet_e58758 - phase0 processSlashingsReset 836.00 ns/op 852.00 ns/op 0.98
mainnet_e58758 - phase0 processRandaoMixesReset 1.2610 us/op 1.0990 us/op 1.15
mainnet_e58758 - phase0 processHistoricalRootsUpdate 135.00 ns/op 132.00 ns/op 1.02
mainnet_e58758 - phase0 processParticipationRecordUpdates 973.00 ns/op 960.00 ns/op 1.01
mainnet_e58758 - phase0 afterProcessEpoch 33.669 ms/op 33.471 ms/op 1.01
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.2939 ms/op 1.0212 ms/op 1.27
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.7798 ms/op 1.6336 ms/op 1.09
altair processInactivityUpdates - 250000 normalcase 10.966 ms/op 10.583 ms/op 1.04
altair processInactivityUpdates - 250000 worstcase 11.414 ms/op 10.613 ms/op 1.08
phase0 processRegistryUpdates - 250000 normalcase 3.6770 us/op 2.2770 us/op 1.61
phase0 processRegistryUpdates - 250000 badcase_full_deposits 152.42 us/op 147.14 us/op 1.04
phase0 processRegistryUpdates - 250000 worstcase 0.5 57.999 ms/op 59.306 ms/op 0.98
altair processRewardsAndPenalties - 250000 normalcase 14.493 ms/op 13.827 ms/op 1.05
altair processRewardsAndPenalties - 250000 worstcase 14.250 ms/op 13.516 ms/op 1.05
phase0 getAttestationDeltas - 250000 normalcase 5.7003 ms/op 5.3247 ms/op 1.07
phase0 getAttestationDeltas - 250000 worstcase 5.7206 ms/op 5.3756 ms/op 1.06
phase0 processSlashings - 250000 worstcase 61.655 us/op 60.273 us/op 1.02
altair processSyncCommitteeUpdates - 250000 10.599 ms/op 9.9461 ms/op 1.07
BeaconState.hashTreeRoot - No change 171.00 ns/op 173.00 ns/op 0.99
BeaconState.hashTreeRoot - 1 full validator 77.455 us/op 56.923 us/op 1.36
BeaconState.hashTreeRoot - 32 full validator 771.39 us/op 665.08 us/op 1.16
BeaconState.hashTreeRoot - 512 full validator 8.6901 ms/op 5.8858 ms/op 1.48
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 93.842 us/op 68.798 us/op 1.36
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.1780 ms/op 1.0610 ms/op 1.11
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 16.891 ms/op 11.897 ms/op 1.42
BeaconState.hashTreeRoot - 1 balances 72.958 us/op 71.137 us/op 1.03
BeaconState.hashTreeRoot - 32 balances 627.46 us/op 562.37 us/op 1.12
BeaconState.hashTreeRoot - 512 balances 6.5191 ms/op 4.6270 ms/op 1.41
BeaconState.hashTreeRoot - 250000 balances 128.13 ms/op 106.70 ms/op 1.20
aggregationBits - 2048 els - zipIndexesInBitList 20.741 us/op 19.844 us/op 1.05
regular array get 100000 times 23.726 us/op 22.863 us/op 1.04
wrappedArray get 100000 times 23.683 us/op 22.756 us/op 1.04
arrayWithProxy get 100000 times 10.718 ms/op 17.410 ms/op 0.62
ssz.Root.equals 22.385 ns/op 21.518 ns/op 1.04
byteArrayEquals 22.218 ns/op 21.170 ns/op 1.05
Buffer.compare 9.2320 ns/op 9.3030 ns/op 0.99
processSlot - 1 slots 9.3460 us/op 7.9370 us/op 1.18
processSlot - 32 slots 1.6167 ms/op 1.5649 ms/op 1.03
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.1247 ms/op 1.9117 ms/op 1.63
getCommitteeAssignments - req 1 vs - 250000 vc 1.7511 ms/op 1.6804 ms/op 1.04
getCommitteeAssignments - req 100 vs - 250000 vc 3.5511 ms/op 3.3894 ms/op 1.05
getCommitteeAssignments - req 1000 vs - 250000 vc 3.8774 ms/op 3.6354 ms/op 1.07
findModifiedValidators - 10000 modified validators 769.99 ms/op 650.38 ms/op 1.18
findModifiedValidators - 1000 modified validators 581.27 ms/op 674.03 ms/op 0.86
findModifiedValidators - 100 modified validators 441.43 ms/op 383.39 ms/op 1.15
findModifiedValidators - 10 modified validators 298.82 ms/op 146.11 ms/op 2.05
findModifiedValidators - 1 modified validators 187.95 ms/op 157.95 ms/op 1.19
findModifiedValidators - no difference 245.05 ms/op 160.99 ms/op 1.52
migrate state 1500000 validators, 3400 modified, 2000 new 2.7938 s/op 2.6809 s/op 1.04
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.8600 ns/op 3.7000 ns/op 1.04
state getBlockRootAtSlot - 250000 vs - 7PWei 295.09 ns/op 270.43 ns/op 1.09
computeProposerIndex 100000 validators 1.3822 ms/op 1.3293 ms/op 1.04
getNextSyncCommitteeIndices 1000 validators 2.9971 ms/op 2.8647 ms/op 1.05
getNextSyncCommitteeIndices 10000 validators 26.996 ms/op 25.300 ms/op 1.07
getNextSyncCommitteeIndices 100000 validators 91.253 ms/op 87.516 ms/op 1.04
computeProposers - vc 250000 560.77 us/op 548.54 us/op 1.02
computeEpochShuffling - vc 250000 39.954 ms/op 39.375 ms/op 1.01
getNextSyncCommittee - vc 250000 10.023 ms/op 9.5858 ms/op 1.05
nodejs block root to RootHex using toHex 101.03 ns/op 103.12 ns/op 0.98
nodejs block root to RootHex using toRootHex 66.381 ns/op 67.877 ns/op 0.98
nodejs fromHex(blob) 819.10 us/op 792.46 us/op 1.03
nodejs fromHexInto(blob) 660.43 us/op 649.46 us/op 1.02
nodejs block root to RootHex using the deprecated toHexString 527.08 ns/op 491.75 ns/op 1.07
nodejs byteArrayEquals 32 bytes (block root) 26.964 ns/op 26.350 ns/op 1.02
nodejs byteArrayEquals 48 bytes (pubkey) 39.012 ns/op 38.259 ns/op 1.02
nodejs byteArrayEquals 96 bytes (signature) 36.866 ns/op 36.405 ns/op 1.01
nodejs byteArrayEquals 1024 bytes 44.575 ns/op 43.771 ns/op 1.02
nodejs byteArrayEquals 131072 bytes (blob) 1.8158 us/op 1.7840 us/op 1.02
browser block root to RootHex using toHex 149.71 ns/op 147.11 ns/op 1.02
browser block root to RootHex using toRootHex 136.16 ns/op 132.43 ns/op 1.03
browser fromHex(blob) 1.5988 ms/op 1.6748 ms/op 0.95
browser fromHexInto(blob) 657.95 us/op 654.91 us/op 1.00
browser block root to RootHex using the deprecated toHexString 362.20 ns/op 345.30 ns/op 1.05
browser byteArrayEquals 32 bytes (block root) 29.079 ns/op 28.109 ns/op 1.03
browser byteArrayEquals 48 bytes (pubkey) 41.038 ns/op 39.616 ns/op 1.04
browser byteArrayEquals 96 bytes (signature) 76.664 ns/op 74.107 ns/op 1.03
browser byteArrayEquals 1024 bytes 782.28 ns/op 755.51 ns/op 1.04
browser byteArrayEquals 131072 bytes (blob) 99.096 us/op 95.201 us/op 1.04

by benchmarkbot/action

@spiral-ladder spiral-ladder Jul 14, 2026

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.

hm first impression is this is not as ugly as i thought it might be but i still prefer that it seems more straightforward to introduce zig bls directly rather than do this

I guess doing this like u said mainly kicks the can down the road. even doing this we still probably should run zig bls X amount of time to be comfortable with permanently removing blst-ts, the question seems to be what this X is

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants