Skip to content

feat: update stateTransition interface to take raw block bytes instead#9632

Open
spiral-ladder wants to merge 3 commits into
unstablefrom
bing/stf-bytes
Open

feat: update stateTransition interface to take raw block bytes instead#9632
spiral-ladder wants to merge 3 commits into
unstablefrom
bing/stf-bytes

Conversation

@spiral-ladder

@spiral-ladder spiral-ladder commented Jul 9, 2026

Copy link
Copy Markdown
Member

This is a change to align with Zig STF (which takes raw block bytes instead of a block object), see #9516, specifically this comment

Two key points, there a few paths affected, with
two key paths (namely block import and block production) being latency sensitive.

block import: we can get the
serialized block from cache easily since it is cached upon receiving from
gossip
.

block production: we have to serialize/deserialize in an ugly manner unfortunately since we're assembling the block ourselves, so we have to pay the serialization cost

regen: we pass in the serializedCache in RegenModules and use it that way, though it might be a cache miss for older blocks.

Historical state replay: we have to pay the ser/deser cost here unless we store block bytes.

Spec tests are also changed to take bytes. We change all usages of stateTransition to the instance method state.stateTransition, so that the tests also work for the bindings when native stf is turned on

Update on approach in this PR: see this comment

AI disclosure: talked to Codex to track down paths which could be impacted by this change in API, especially those that can't avoid the ser/deser cost

This is a change to align with Zig STF (which takes raw block bytes
instead of a block object), see #9516, specifically [this
comment](#9516 (comment))

Two key points, there a few paths affected, with
two key paths (namely block import and block production) being latency sensitive.

**block import**: we can get the
serialized block from cache easily since it is [cached upon receiving
from
gossip](https://github.com/ChainSafe/lodestar/blob/47eedd48a24ab5833c3a0a58676be812d07ee08d/packages/beacon-node/src/network/processor/gossipHandlers.ts#L704).

**block production**: it seems like we have to serialize/deserialize
in an ugly manner unfortunately since we're assembling the block
ourselves, so we have to pay the serialization cost.

**regen**: we pass in the `serializedCache` in `RegenModules` and use it
that way, though it might be a cache miss for older blocks. 

**Historical state replay**: seems like we have to pay the ser/deser
cost here unless we store block bytes.

Spec tests are also changed to take bytes.
@spiral-ladder spiral-ladder self-assigned this Jul 9, 2026
@spiral-ladder spiral-ladder requested a review from a team as a code owner July 9, 2026 15:58

@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 optimizes the state transition process by changing the stateTransition method signature to accept serialized block bytes (Uint8Array) and an isBlinded flag instead of structured block objects. This change allows the node to leverage cached serialized blocks from gossip, reducing deserialization overhead in critical paths. Feedback on the changes highlights a security concern in readSignedBlockSlot where the length check is too permissive and could lead to out-of-bounds reads on malformed inputs. Additionally, improvements are suggested in the test suites to avoid accessing the internal cachedState property of BeaconStateView by reusing local configuration variables.

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.

Comment on lines +161 to +173
function readSignedBlockSlot(signedBlockBytes: Uint8Array): Slot {
if (signedBlockBytes.byteLength < 12) {
throw Error("Invalid signed block bytes: too short to read message offset and slot");
}

const view = new DataView(signedBlockBytes.buffer, signedBlockBytes.byteOffset, signedBlockBytes.byteLength);
const messageOffset = view.getUint32(0, true);
if (messageOffset + 8 > signedBlockBytes.byteLength) {
throw Error("Invalid signed block bytes: message offset out of range");
}

return Number(view.getBigUint64(messageOffset, true));
}

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.

security-high high

The minimum valid length of a serialized SignedBeaconBlock or SignedBlindedBeaconBlock is 108 bytes (100 bytes for the fixed part containing the 4-byte offset of message and the 96-byte signature, plus at least 8 bytes for the slot field of the message container).

Checking signedBlockBytes.byteLength < 12 is too permissive and can lead to out-of-bounds reads or reading garbage data if the input is malformed.

Additionally, the messageOffset (which is the offset of the first variable-sized field message) must be exactly 100. If it is not 100, the bytes are invalid and we should throw an error immediately to prevent reading arbitrary offsets.

Suggested change
function readSignedBlockSlot(signedBlockBytes: Uint8Array): Slot {
if (signedBlockBytes.byteLength < 12) {
throw Error("Invalid signed block bytes: too short to read message offset and slot");
}
const view = new DataView(signedBlockBytes.buffer, signedBlockBytes.byteOffset, signedBlockBytes.byteLength);
const messageOffset = view.getUint32(0, true);
if (messageOffset + 8 > signedBlockBytes.byteLength) {
throw Error("Invalid signed block bytes: message offset out of range");
}
return Number(view.getBigUint64(messageOffset, true));
}
function readSignedBlockSlot(signedBlockBytes: Uint8Array): Slot {
if (signedBlockBytes.byteLength < 108) {
throw Error("Invalid signed block bytes: too short to contain a valid signed block");
}
const view = new DataView(signedBlockBytes.buffer, signedBlockBytes.byteOffset, signedBlockBytes.byteLength);
const messageOffset = view.getUint32(0, true);
if (messageOffset !== 100) {
throw Error(`Invalid signed block bytes: message offset must be 100, got ${messageOffset}`);
}
return Number(view.getBigUint64(messageOffset, true));
}

Comment on lines +21 to +27
let state = new BeaconStateView(createCachedBeaconStateTest(testcase.pre, getConfig(fork)));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as bellatrix.SignedBeaconBlock;

state = stateTransition(state, signedBlock, {
// Should assume payload valid and blob data available for this test
executionPayloadStatus: ExecutionPayloadStatus.valid,
dataAvailabilityStatus: DataAvailabilityStatus.Available,
verifyStateRoot: false,
verifyProposer: verify,
verifySignatures: verify,
assertCorrectProgressiveBalances,
});
state = state.stateTransition(
state.cachedState.config.getForkTypes(signedBlock.message.slot).SignedBeaconBlock.serialize(signedBlock),

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

Avoid accessing the internal cachedState property of BeaconStateView to retrieve the config. Since getConfig(fork) is already called, we can store the config in a local variable and use it directly.

Suggested change
let state = new BeaconStateView(createCachedBeaconStateTest(testcase.pre, getConfig(fork)));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as bellatrix.SignedBeaconBlock;
state = stateTransition(state, signedBlock, {
// Should assume payload valid and blob data available for this test
executionPayloadStatus: ExecutionPayloadStatus.valid,
dataAvailabilityStatus: DataAvailabilityStatus.Available,
verifyStateRoot: false,
verifyProposer: verify,
verifySignatures: verify,
assertCorrectProgressiveBalances,
});
state = state.stateTransition(
state.cachedState.config.getForkTypes(signedBlock.message.slot).SignedBeaconBlock.serialize(signedBlock),
const config = getConfig(fork);
let state = new BeaconStateView(createCachedBeaconStateTest(testcase.pre, config));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as bellatrix.SignedBeaconBlock;
state = state.stateTransition(
config.getForkTypes(signedBlock.message.slot).SignedBeaconBlock.serialize(signedBlock),

Comment on lines +63 to +71
let wrappedState = new BeaconStateView(createCachedBeaconStateTest(stateTB, getConfig(fork)));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as deneb.SignedBeaconBlock;
wrappedState = stateTransition(wrappedState, signedBlock, {
// Assume valid and available for this test
executionPayloadStatus: ExecutionPayloadStatus.valid,
dataAvailabilityStatus: DataAvailabilityStatus.Available,
verifyStateRoot: verify,
verifyProposer: verify,
verifySignatures: verify,
assertCorrectProgressiveBalances,
});

wrappedState = wrappedState.stateTransition(
wrappedState.cachedState.config
.getForkTypes(signedBlock.message.slot)
.SignedBeaconBlock.serialize(signedBlock),

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

Avoid accessing the internal cachedState property of BeaconStateView to retrieve the config. Since getConfig(fork) is already called, we can store the config in a local variable and use it directly.

Suggested change
let wrappedState = new BeaconStateView(createCachedBeaconStateTest(stateTB, getConfig(fork)));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as deneb.SignedBeaconBlock;
wrappedState = stateTransition(wrappedState, signedBlock, {
// Assume valid and available for this test
executionPayloadStatus: ExecutionPayloadStatus.valid,
dataAvailabilityStatus: DataAvailabilityStatus.Available,
verifyStateRoot: verify,
verifyProposer: verify,
verifySignatures: verify,
assertCorrectProgressiveBalances,
});
wrappedState = wrappedState.stateTransition(
wrappedState.cachedState.config
.getForkTypes(signedBlock.message.slot)
.SignedBeaconBlock.serialize(signedBlock),
const config = getConfig(fork);
let wrappedState = new BeaconStateView(createCachedBeaconStateTest(stateTB, config));
const verify = shouldVerify(testcase);
for (let i = 0; i < testcase.meta.blocks_count; i++) {
const signedBlock = testcase[`blocks_${i}`] as deneb.SignedBeaconBlock;
wrappedState = wrappedState.stateTransition(
config
.getForkTypes(signedBlock.message.slot)
.SignedBeaconBlock.serialize(signedBlock),

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4917710527

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

import {describe, expect, it, vi} from "vitest";
import {BitArray} from "@chainsafe/ssz";
import {IBeaconStateViewNative} from "../../../src/stateView/interface.js";
import {DataAvailabilityStatus, ExecutionPayloadStatus} from "../../../src/index.ts";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use a .js extension for the relative test import

The root AGENTS.md and Biome configuration require .js extensions for relative TypeScript imports, but this new runtime import uses index.ts. In this test file, pnpm lint will reject the import; change it to ../../../src/index.js like the neighboring relative imports.

Useful? React with 👍 / 👎.

Comment thread ; Outdated
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Performance Alert ⚠️

Possible performance regression was detected for some benchmarks.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold.

Benchmark suite Current: 1b4be64 Previous: 47eedd4 Ratio
phase0 processBlock - 250000 vs - 7PWei normalcase 8.3458 ms/op 1.3651 ms/op 6.11
phase0 processBlock - 250000 vs - 7PWei worstcase 51.173 ms/op 16.732 ms/op 3.06
Full benchmark results
Benchmark suite Current: 1b4be64 Previous: 47eedd4 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.4245 ms/op 919.05 us/op 1.55
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 41.694 us/op 39.970 us/op 1.04
BLS verify - blst 708.76 us/op 730.37 us/op 0.97
BLS verifyMultipleSignatures 3 - blst 1.3856 ms/op 1.3096 ms/op 1.06
BLS verifyMultipleSignatures 8 - blst 2.1861 ms/op 2.0417 ms/op 1.07
BLS verifyMultipleSignatures 32 - blst 6.8669 ms/op 6.5327 ms/op 1.05
BLS verifyMultipleSignatures 64 - blst 13.636 ms/op 12.639 ms/op 1.08
BLS verifyMultipleSignatures 128 - blst 26.044 ms/op 24.743 ms/op 1.05
BLS deserializing 10000 signatures 637.77 ms/op 629.56 ms/op 1.01
BLS deserializing 100000 signatures 6.3971 s/op 6.2435 s/op 1.02
BLS verifyMultipleSignatures - same message - 3 - blst 707.50 us/op 792.03 us/op 0.89
BLS verifyMultipleSignatures - same message - 8 - blst 901.04 us/op 923.43 us/op 0.98
BLS verifyMultipleSignatures - same message - 32 - blst 1.4721 ms/op 1.5157 ms/op 0.97
BLS verifyMultipleSignatures - same message - 64 - blst 2.4035 ms/op 2.3227 ms/op 1.03
BLS verifyMultipleSignatures - same message - 128 - blst 4.0685 ms/op 4.0326 ms/op 1.01
BLS aggregatePubkeys 32 - blst 17.861 us/op 17.536 us/op 1.02
BLS aggregatePubkeys 128 - blst 63.717 us/op 63.115 us/op 1.01
getSlashingsAndExits - default max 48.828 us/op 56.645 us/op 0.86
getSlashingsAndExits - 2k 383.35 us/op 468.99 us/op 0.82
proposeBlockBody type=full, size=empty 694.46 us/op 708.80 us/op 0.98
isKnown best case - 1 super set check 178.00 ns/op 162.00 ns/op 1.10
isKnown normal case - 2 super set checks 164.00 ns/op 160.00 ns/op 1.02
isKnown worse case - 16 super set checks 157.00 ns/op 160.00 ns/op 0.98
validate api signedAggregateAndProof - struct 1.5314 ms/op 1.4584 ms/op 1.05
validate gossip signedAggregateAndProof - struct 1.5048 ms/op 1.4621 ms/op 1.03
batch validate gossip attestation - vc 640000 - chunk 32 106.04 us/op 108.46 us/op 0.98
batch validate gossip attestation - vc 640000 - chunk 64 96.308 us/op 94.890 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 128 89.660 us/op 91.053 us/op 0.98
batch validate gossip attestation - vc 640000 - chunk 256 87.344 us/op 88.838 us/op 0.98
bytes32 toHexString 294.00 ns/op 276.00 ns/op 1.07
bytes32 Buffer.toString(hex) 169.00 ns/op 165.00 ns/op 1.02
bytes32 Buffer.toString(hex) from Uint8Array 244.00 ns/op 241.00 ns/op 1.01
bytes32 Buffer.toString(hex) + 0x 169.00 ns/op 170.00 ns/op 0.99
Return object 10000 times 0.21560 ns/op 0.21090 ns/op 1.02
Throw Error 10000 times 3.4041 us/op 3.2988 us/op 1.03
toHex 105.85 ns/op 93.345 ns/op 1.13
Buffer.from 87.763 ns/op 85.589 ns/op 1.03
shared Buffer 60.233 ns/op 62.352 ns/op 0.97
fastMsgIdFn sha256 / 200 bytes 1.4860 us/op 1.4730 us/op 1.01
fastMsgIdFn h32 xxhash / 200 bytes 156.00 ns/op 152.00 ns/op 1.03
fastMsgIdFn h64 xxhash / 200 bytes 205.00 ns/op 205.00 ns/op 1.00
fastMsgIdFn sha256 / 1000 bytes 4.7770 us/op 4.7610 us/op 1.00
fastMsgIdFn h32 xxhash / 1000 bytes 245.00 ns/op 239.00 ns/op 1.03
fastMsgIdFn h64 xxhash / 1000 bytes 251.00 ns/op 256.00 ns/op 0.98
fastMsgIdFn sha256 / 10000 bytes 42.245 us/op 42.500 us/op 0.99
fastMsgIdFn h32 xxhash / 10000 bytes 1.2660 us/op 1.2600 us/op 1.00
fastMsgIdFn h64 xxhash / 10000 bytes 815.00 ns/op 808.00 ns/op 1.01
send data - 1000 256B messages 4.3304 ms/op 5.6576 ms/op 0.77
send data - 1000 512B messages 5.1218 ms/op 5.3618 ms/op 0.96
send data - 1000 1024B messages 5.8480 ms/op 5.6477 ms/op 1.04
send data - 1000 1200B messages 6.3398 ms/op 6.2470 ms/op 1.01
send data - 1000 2048B messages 11.765 ms/op 18.886 ms/op 0.62
send data - 1000 4096B messages 62.846 ms/op 43.588 ms/op 1.44
send data - 1000 16384B messages 222.19 ms/op 79.094 ms/op 2.81
send data - 1000 65536B messages 1.4826 s/op 761.22 ms/op 1.95
enrSubnets - fastDeserialize 64 bits 740.00 ns/op 764.00 ns/op 0.97
enrSubnets - ssz BitVector 64 bits 267.00 ns/op 258.00 ns/op 1.03
enrSubnets - fastDeserialize 4 bits 100.00 ns/op 101.00 ns/op 0.99
enrSubnets - ssz BitVector 4 bits 264.00 ns/op 260.00 ns/op 1.02
prioritizePeers score -10:0 att 32-0.1 sync 2-0 209.70 us/op 198.66 us/op 1.06
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 230.89 us/op 226.44 us/op 1.02
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 340.90 us/op 329.50 us/op 1.03
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 596.80 us/op 583.00 us/op 1.02
prioritizePeers score 0:0 att 64-1 sync 4-1 710.49 us/op 686.61 us/op 1.03
array of 16000 items push then shift 1.2979 us/op 1.2329 us/op 1.05
LinkedList of 16000 items push then shift 7.3880 ns/op 7.3760 ns/op 1.00
array of 16000 items push then pop 70.683 ns/op 70.761 ns/op 1.00
LinkedList of 16000 items push then pop 6.0320 ns/op 5.9140 ns/op 1.02
array of 24000 items push then shift 1.9200 us/op 1.8439 us/op 1.04
LinkedList of 24000 items push then shift 7.0850 ns/op 7.3590 ns/op 0.96
array of 24000 items push then pop 99.446 ns/op 101.50 ns/op 0.98
LinkedList of 24000 items push then pop 6.1280 ns/op 6.0950 ns/op 1.01
intersect bitArray bitLen 8 4.8100 ns/op 4.7240 ns/op 1.02
intersect array and set length 8 29.621 ns/op 29.516 ns/op 1.00
intersect bitArray bitLen 128 24.460 ns/op 24.189 ns/op 1.01
intersect array and set length 128 502.06 ns/op 496.10 ns/op 1.01
bitArray.getTrueBitIndexes() bitLen 128 1.0250 us/op 976.00 ns/op 1.05
bitArray.getTrueBitIndexes() bitLen 248 1.7630 us/op 1.7460 us/op 1.01
bitArray.getTrueBitIndexes() bitLen 512 3.5670 us/op 3.6530 us/op 0.98
Full columns - reconstruct all 6 blobs 119.25 us/op 244.41 us/op 0.49
Full columns - reconstruct half of the blobs out of 6 67.226 us/op 70.121 us/op 0.96
Full columns - reconstruct single blob out of 6 36.225 us/op 30.198 us/op 1.20
Half columns - reconstruct all 6 blobs 395.11 ms/op 389.50 ms/op 1.01
Half columns - reconstruct half of the blobs out of 6 196.47 ms/op 194.97 ms/op 1.01
Half columns - reconstruct single blob out of 6 71.232 ms/op 69.986 ms/op 1.02
Set add up to 64 items then delete first 1.6588 us/op 1.6032 us/op 1.03
OrderedSet add up to 64 items then delete first 2.4061 us/op 2.4863 us/op 0.97
Set add up to 64 items then delete last 1.8004 us/op 2.2154 us/op 0.81
OrderedSet add up to 64 items then delete last 2.8112 us/op 2.7920 us/op 1.01
Set add up to 64 items then delete middle 1.8798 us/op 1.8502 us/op 1.02
OrderedSet add up to 64 items then delete middle 4.3031 us/op 4.2523 us/op 1.01
Set add up to 128 items then delete first 3.7645 us/op 3.6825 us/op 1.02
OrderedSet add up to 128 items then delete first 5.7706 us/op 5.7284 us/op 1.01
Set add up to 128 items then delete last 3.6294 us/op 3.5258 us/op 1.03
OrderedSet add up to 128 items then delete last 5.4002 us/op 5.6598 us/op 0.95
Set add up to 128 items then delete middle 3.8028 us/op 3.5514 us/op 1.07
OrderedSet add up to 128 items then delete middle 11.319 us/op 11.223 us/op 1.01
Set add up to 256 items then delete first 7.4435 us/op 7.2776 us/op 1.02
OrderedSet add up to 256 items then delete first 11.888 us/op 11.989 us/op 0.99
Set add up to 256 items then delete last 7.5034 us/op 7.3488 us/op 1.02
OrderedSet add up to 256 items then delete last 11.316 us/op 11.253 us/op 1.01
Set add up to 256 items then delete middle 7.2020 us/op 7.0186 us/op 1.03
OrderedSet add up to 256 items then delete middle 35.245 us/op 35.178 us/op 1.00
runFastConfirmationRules vc:100000 bc:96 eq:0 5.5685 ms/op 4.4229 ms/op 1.26
runFastConfirmationRules vc:600000 bc:96 eq:0 36.220 ms/op 34.901 ms/op 1.04
runFastConfirmationRules vc:1000000 bc:96 eq:0 60.305 ms/op 58.498 ms/op 1.03
runFastConfirmationRules vc:600000 bc:320 eq:0 36.290 ms/op 35.072 ms/op 1.03
runFastConfirmationRules vc:100000 bc:96 eq:1000 1.1487 s/op 1.1384 s/op 1.01
pass gossip attestations to forkchoice per slot 2.5715 ms/op 2.5725 ms/op 1.00
forkChoice updateHead vc 100000 bc 64 eq 0 414.23 us/op 400.48 us/op 1.03
forkChoice updateHead vc 600000 bc 64 eq 0 2.4540 ms/op 2.3988 ms/op 1.02
forkChoice updateHead vc 1000000 bc 64 eq 0 4.1131 ms/op 4.0181 ms/op 1.02
forkChoice updateHead vc 600000 bc 320 eq 0 2.4861 ms/op 2.4363 ms/op 1.02
forkChoice updateHead vc 600000 bc 1200 eq 0 2.5439 ms/op 2.5094 ms/op 1.01
forkChoice updateHead vc 600000 bc 7200 eq 0 2.8503 ms/op 3.3134 ms/op 0.86
forkChoice updateHead vc 600000 bc 64 eq 1000 2.4612 ms/op 2.4212 ms/op 1.02
forkChoice updateHead vc 600000 bc 64 eq 10000 2.5912 ms/op 2.4888 ms/op 1.04
forkChoice updateHead vc 600000 bc 64 eq 300000 6.6211 ms/op 6.4802 ms/op 1.02
computeDeltas 1400000 validators 0% inactive 12.245 ms/op 11.910 ms/op 1.03
computeDeltas 1400000 validators 10% inactive 11.609 ms/op 11.299 ms/op 1.03
computeDeltas 1400000 validators 20% inactive 10.900 ms/op 10.850 ms/op 1.00
computeDeltas 1400000 validators 50% inactive 8.9540 ms/op 8.7998 ms/op 1.02
computeDeltas 2100000 validators 0% inactive 18.293 ms/op 18.031 ms/op 1.01
computeDeltas 2100000 validators 10% inactive 17.403 ms/op 16.996 ms/op 1.02
computeDeltas 2100000 validators 20% inactive 16.399 ms/op 15.982 ms/op 1.03
computeDeltas 2100000 validators 50% inactive 10.921 ms/op 10.535 ms/op 1.04
altair processAttestation - 250000 vs - 7PWei normalcase 1.9154 ms/op 2.4636 ms/op 0.78
altair processAttestation - 250000 vs - 7PWei worstcase 2.6419 ms/op 2.9279 ms/op 0.90
altair processAttestation - setStatus - 1/6 committees join 103.92 us/op 101.91 us/op 1.02
altair processAttestation - setStatus - 1/3 committees join 205.01 us/op 201.88 us/op 1.02
altair processAttestation - setStatus - 1/2 committees join 293.39 us/op 264.73 us/op 1.11
altair processAttestation - setStatus - 2/3 committees join 370.89 us/op 354.54 us/op 1.05
altair processAttestation - setStatus - 4/5 committees join 525.04 us/op 496.59 us/op 1.06
altair processAttestation - setStatus - 100% committees join 614.51 us/op 592.92 us/op 1.04
altair processBlock - 250000 vs - 7PWei normalcase 6.1964 ms/op 4.2436 ms/op 1.46
altair processBlock - 250000 vs - 7PWei normalcase hashState 21.437 ms/op 13.411 ms/op 1.60
altair processBlock - 250000 vs - 7PWei worstcase 27.825 ms/op 21.724 ms/op 1.28
altair processBlock - 250000 vs - 7PWei worstcase hashState 57.718 ms/op 41.246 ms/op 1.40
phase0 processBlock - 250000 vs - 7PWei normalcase 8.3458 ms/op 1.3651 ms/op 6.11
phase0 processBlock - 250000 vs - 7PWei worstcase 51.173 ms/op 16.732 ms/op 3.06
altair processEth1Data - 250000 vs - 7PWei normalcase 295.00 us/op 275.65 us/op 1.07
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.1060 us/op 3.3200 us/op 0.94
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 20.177 us/op 21.143 us/op 0.95
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.8560 us/op 6.8380 us/op 1.00
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.5040 us/op 3.5780 us/op 0.98
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 91.945 us/op 92.236 us/op 1.00
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.3952 ms/op 1.3592 ms/op 1.03
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8288 ms/op 1.8328 ms/op 1.00
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.7417 ms/op 1.9202 ms/op 0.91
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.5256 ms/op 4.3836 ms/op 0.80
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.0716 ms/op 2.2531 ms/op 0.92
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.7813 ms/op 3.9941 ms/op 0.95
Tree 40 250000 create 346.80 ms/op 356.18 ms/op 0.97
Tree 40 250000 get(125000) 91.987 ns/op 92.959 ns/op 0.99
Tree 40 250000 set(125000) 963.80 ns/op 1.0213 us/op 0.94
Tree 40 250000 toArray() 14.721 ms/op 16.922 ms/op 0.87
Tree 40 250000 iterate all - toArray() + loop 14.751 ms/op 15.822 ms/op 0.93
Tree 40 250000 iterate all - get(i) 37.386 ms/op 38.227 ms/op 0.98
Array 250000 create 2.2555 ms/op 2.1861 ms/op 1.03
Array 250000 clone - spread 687.96 us/op 671.04 us/op 1.03
Array 250000 get(125000) 0.28500 ns/op 0.29100 ns/op 0.98
Array 250000 set(125000) 0.28700 ns/op 0.29600 ns/op 0.97
Array 250000 iterate all - loop 55.714 us/op 57.607 us/op 0.97
phase0 afterProcessEpoch - 250000 vs - 7PWei 53.317 ms/op 41.168 ms/op 1.30
Array.fill - length 1000000 2.3011 ms/op 2.1874 ms/op 1.05
Array push - length 1000000 9.5111 ms/op 9.4167 ms/op 1.01
Array.get 0.20698 ns/op 0.20469 ns/op 1.01
Uint8Array.get 0.23338 ns/op 0.22778 ns/op 1.02
phase0 beforeProcessEpoch - 250000 vs - 7PWei 16.087 ms/op 14.869 ms/op 1.08
altair processEpoch - mainnet_e81889 334.46 ms/op 301.74 ms/op 1.11
mainnet_e81889 - altair beforeProcessEpoch 19.050 ms/op 20.937 ms/op 0.91
mainnet_e81889 - altair processJustificationAndFinalization 8.3100 us/op 6.6740 us/op 1.25
mainnet_e81889 - altair processInactivityUpdates 3.4869 ms/op 4.9618 ms/op 0.70
mainnet_e81889 - altair processRewardsAndPenalties 18.452 ms/op 23.150 ms/op 0.80
mainnet_e81889 - altair processRegistryUpdates 556.00 ns/op 568.00 ns/op 0.98
mainnet_e81889 - altair processSlashings 131.00 ns/op 139.00 ns/op 0.94
mainnet_e81889 - altair processEth1DataReset 127.00 ns/op 135.00 ns/op 0.94
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.6830 ms/op 1.2721 ms/op 1.32
mainnet_e81889 - altair processSlashingsReset 683.00 ns/op 681.00 ns/op 1.00
mainnet_e81889 - altair processRandaoMixesReset 1.3030 us/op 1.2460 us/op 1.05
mainnet_e81889 - altair processHistoricalRootsUpdate 130.00 ns/op 136.00 ns/op 0.96
mainnet_e81889 - altair processParticipationFlagUpdates 457.00 ns/op 423.00 ns/op 1.08
mainnet_e81889 - altair processSyncCommitteeUpdates 106.00 ns/op 105.00 ns/op 1.01
mainnet_e81889 - altair afterProcessEpoch 41.231 ms/op 41.990 ms/op 0.98
capella processEpoch - mainnet_e217614 983.00 ms/op 805.86 ms/op 1.22
mainnet_e217614 - capella beforeProcessEpoch 73.428 ms/op 58.578 ms/op 1.25
mainnet_e217614 - capella processJustificationAndFinalization 6.7420 us/op 6.5920 us/op 1.02
mainnet_e217614 - capella processInactivityUpdates 13.692 ms/op 16.645 ms/op 0.82
mainnet_e217614 - capella processRewardsAndPenalties 95.214 ms/op 95.884 ms/op 0.99
mainnet_e217614 - capella processRegistryUpdates 4.3890 us/op 4.3710 us/op 1.00
mainnet_e217614 - capella processSlashings 125.00 ns/op 130.00 ns/op 0.96
mainnet_e217614 - capella processEth1DataReset 124.00 ns/op 126.00 ns/op 0.98
mainnet_e217614 - capella processEffectiveBalanceUpdates 16.090 ms/op 15.431 ms/op 1.04
mainnet_e217614 - capella processSlashingsReset 676.00 ns/op 683.00 ns/op 0.99
mainnet_e217614 - capella processRandaoMixesReset 1.3990 us/op 1.3690 us/op 1.02
mainnet_e217614 - capella processHistoricalRootsUpdate 122.00 ns/op 129.00 ns/op 0.95
mainnet_e217614 - capella processParticipationFlagUpdates 423.00 ns/op 433.00 ns/op 0.98
mainnet_e217614 - capella afterProcessEpoch 109.14 ms/op 111.59 ms/op 0.98
phase0 processEpoch - mainnet_e58758 348.35 ms/op 391.04 ms/op 0.89
mainnet_e58758 - phase0 beforeProcessEpoch 70.389 ms/op 83.140 ms/op 0.85
mainnet_e58758 - phase0 processJustificationAndFinalization 6.8370 us/op 6.8960 us/op 0.99
mainnet_e58758 - phase0 processRewardsAndPenalties 16.292 ms/op 17.412 ms/op 0.94
mainnet_e58758 - phase0 processRegistryUpdates 2.2580 us/op 2.2800 us/op 0.99
mainnet_e58758 - phase0 processSlashings 124.00 ns/op 132.00 ns/op 0.94
mainnet_e58758 - phase0 processEth1DataReset 125.00 ns/op 167.00 ns/op 0.75
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 821.70 us/op 840.71 us/op 0.98
mainnet_e58758 - phase0 processSlashingsReset 1.0030 us/op 965.00 ns/op 1.04
mainnet_e58758 - phase0 processRandaoMixesReset 1.3890 us/op 1.4230 us/op 0.98
mainnet_e58758 - phase0 processHistoricalRootsUpdate 130.00 ns/op 131.00 ns/op 0.99
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.2280 us/op 1.3240 us/op 0.93
mainnet_e58758 - phase0 afterProcessEpoch 32.602 ms/op 33.872 ms/op 0.96
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.0940 ms/op 1.0164 ms/op 1.08
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.6299 ms/op 1.6290 ms/op 1.00
altair processInactivityUpdates - 250000 normalcase 10.676 ms/op 10.965 ms/op 0.97
altair processInactivityUpdates - 250000 worstcase 10.656 ms/op 12.755 ms/op 0.84
phase0 processRegistryUpdates - 250000 normalcase 2.1660 us/op 2.3310 us/op 0.93
phase0 processRegistryUpdates - 250000 badcase_full_deposits 144.76 us/op 139.32 us/op 1.04
phase0 processRegistryUpdates - 250000 worstcase 0.5 67.481 ms/op 65.967 ms/op 1.02
altair processRewardsAndPenalties - 250000 normalcase 15.794 ms/op 16.760 ms/op 0.94
altair processRewardsAndPenalties - 250000 worstcase 15.636 ms/op 15.884 ms/op 0.98
phase0 getAttestationDeltas - 250000 normalcase 5.5374 ms/op 5.4825 ms/op 1.01
phase0 getAttestationDeltas - 250000 worstcase 8.4924 ms/op 5.4786 ms/op 1.55
phase0 processSlashings - 250000 worstcase 61.126 us/op 63.435 us/op 0.96
altair processSyncCommitteeUpdates - 250000 10.503 ms/op 11.984 ms/op 0.88
BeaconState.hashTreeRoot - No change 185.00 ns/op 163.00 ns/op 1.13
BeaconState.hashTreeRoot - 1 full validator 98.421 us/op 84.342 us/op 1.17
BeaconState.hashTreeRoot - 32 full validator 1.0996 ms/op 905.87 us/op 1.21
BeaconState.hashTreeRoot - 512 full validator 9.3547 ms/op 9.3505 ms/op 1.00
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 111.43 us/op 113.45 us/op 0.98
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 2.1923 ms/op 1.5297 ms/op 1.43
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 17.965 ms/op 21.734 ms/op 0.83
BeaconState.hashTreeRoot - 1 balances 93.965 us/op 83.699 us/op 1.12
BeaconState.hashTreeRoot - 32 balances 1.3115 ms/op 766.65 us/op 1.71
BeaconState.hashTreeRoot - 512 balances 6.7204 ms/op 6.8077 ms/op 0.99
BeaconState.hashTreeRoot - 250000 balances 183.02 ms/op 143.46 ms/op 1.28
aggregationBits - 2048 els - zipIndexesInBitList 20.537 us/op 20.683 us/op 0.99
regular array get 100000 times 23.252 us/op 22.984 us/op 1.01
wrappedArray get 100000 times 23.313 us/op 23.047 us/op 1.01
arrayWithProxy get 100000 times 13.158 ms/op 10.034 ms/op 1.31
ssz.Root.equals 21.810 ns/op 21.402 ns/op 1.02
byteArrayEquals 21.570 ns/op 21.191 ns/op 1.02
Buffer.compare 8.9220 ns/op 8.8120 ns/op 1.01
processSlot - 1 slots 11.248 us/op 9.6300 us/op 1.17
processSlot - 32 slots 2.7503 ms/op 2.2314 ms/op 1.23
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 6.7734 ms/op 4.8978 ms/op 1.38
getCommitteeAssignments - req 1 vs - 250000 vc 1.6527 ms/op 1.6728 ms/op 0.99
getCommitteeAssignments - req 100 vs - 250000 vc 3.4870 ms/op 3.4447 ms/op 1.01
getCommitteeAssignments - req 1000 vs - 250000 vc 3.6621 ms/op 3.6348 ms/op 1.01
findModifiedValidators - 10000 modified validators 776.83 ms/op 685.05 ms/op 1.13
findModifiedValidators - 1000 modified validators 445.19 ms/op 555.92 ms/op 0.80
findModifiedValidators - 100 modified validators 281.81 ms/op 319.46 ms/op 0.88
findModifiedValidators - 10 modified validators 266.30 ms/op 284.96 ms/op 0.93
findModifiedValidators - 1 modified validators 189.65 ms/op 189.14 ms/op 1.00
findModifiedValidators - no difference 184.75 ms/op 169.68 ms/op 1.09
migrate state 1500000 validators, 3400 modified, 2000 new 4.0080 s/op 3.3349 s/op 1.20
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.9200 ns/op 3.4400 ns/op 1.14
state getBlockRootAtSlot - 250000 vs - 7PWei 380.98 ns/op 404.51 ns/op 0.94
computeProposerIndex 100000 validators 1.3719 ms/op 1.3885 ms/op 0.99
getNextSyncCommitteeIndices 1000 validators 2.9261 ms/op 2.9111 ms/op 1.01
getNextSyncCommitteeIndices 10000 validators 25.969 ms/op 25.664 ms/op 1.01
getNextSyncCommitteeIndices 100000 validators 88.040 ms/op 89.791 ms/op 0.98
computeProposers - vc 250000 552.00 us/op 548.97 us/op 1.01
computeEpochShuffling - vc 250000 38.993 ms/op 39.967 ms/op 0.98
getNextSyncCommittee - vc 250000 9.5877 ms/op 11.304 ms/op 0.85
nodejs block root to RootHex using toHex 99.159 ns/op 95.918 ns/op 1.03
nodejs block root to RootHex using toRootHex 63.167 ns/op 62.236 ns/op 1.01
nodejs fromHex(blob) 862.55 us/op 842.31 us/op 1.02
nodejs fromHexInto(blob) 618.76 us/op 641.80 us/op 0.96
nodejs block root to RootHex using the deprecated toHexString 523.70 ns/op 549.92 ns/op 0.95
nodejs byteArrayEquals 32 bytes (block root) 25.929 ns/op 26.721 ns/op 0.97
nodejs byteArrayEquals 48 bytes (pubkey) 37.503 ns/op 38.206 ns/op 0.98
nodejs byteArrayEquals 96 bytes (signature) 33.574 ns/op 36.845 ns/op 0.91
nodejs byteArrayEquals 1024 bytes 40.985 ns/op 43.927 ns/op 0.93
nodejs byteArrayEquals 131072 bytes (blob) 1.7456 us/op 1.8165 us/op 0.96
browser block root to RootHex using toHex 141.38 ns/op 146.06 ns/op 0.97
browser block root to RootHex using toRootHex 128.87 ns/op 135.26 ns/op 0.95
browser fromHex(blob) 1.7502 ms/op 1.6041 ms/op 1.09
browser fromHexInto(blob) 639.59 us/op 612.90 us/op 1.04
browser block root to RootHex using the deprecated toHexString 366.19 ns/op 359.59 ns/op 1.02
browser byteArrayEquals 32 bytes (block root) 28.326 ns/op 28.275 ns/op 1.00
browser byteArrayEquals 48 bytes (pubkey) 39.989 ns/op 39.936 ns/op 1.00
browser byteArrayEquals 96 bytes (signature) 74.409 ns/op 74.918 ns/op 0.99
browser byteArrayEquals 1024 bytes 758.04 ns/op 762.61 ns/op 0.99
browser byteArrayEquals 131072 bytes (blob) 96.159 us/op 95.487 us/op 1.01

by benchmarkbot/action

@twoeths twoeths left a comment

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.

we should pass in both the SignedBlock* object and the ssz bytes
current state-transition will only consume object
while native BeaconStateView will only consume Uint8Array
this is the same approach I'm using for BeaconEngine

@spiral-ladder

Copy link
Copy Markdown
Member Author

spoke to @twoeths in a private conversation, this PR needs some semi-big changes. The interface should take both bytes and object to be compatible for now:

export interface IBeaconStateView {
  .. // other defs

  stateTransition(
    signedBlockBytes: Uint8Array,
    signedBlock: SignedBlock | SignedBlindedBlock,
    isBlinded: boolean,
    options: StateTransitionOpts,
    modules: StateTransitionModules
  ): IBeaconStateView;

The only path that we can't get around this cost is block production since we always build a block from object. We can consider creating a new computeNewStateRoot API that takes params {block: SignedBlock; ssz?: Uint8Array} for compatibility

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.

4 participants