Skip to content

ci: expand native portability checks#9642

Open
nflaig wants to merge 7 commits into
unstablefrom
nflaig/check-native-portability
Open

ci: expand native portability checks#9642
nflaig wants to merge 7 commits into
unstablefrom
nflaig/check-native-portability

Conversation

@nflaig

@nflaig nflaig commented Jul 10, 2026

Copy link
Copy Markdown
Member

What changed

  • verify that @chainsafe/lodestar-z declares every native target supported by Lodestar
  • verify that each target has a matching optional dependency
  • preserve the existing x64 AVX/CPUID portability scan and report failures from both checks

Why

The existing check only inspected installed x64 binaries, so it could not detect a missing prebuilt target such as ARM64 musl/Alpine.

Validation

  • bash -n scripts/ci/check_native_portability.sh
  • scripts/ci/check_native_portability.sh (correctly reports the currently installed lodestar-z package is missing aarch64-unknown-linux-musl; existing CPU-dispatch checks pass)

This PR was written with assistance from Codex.

@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 updates the check_native_portability.sh script to verify native package target coverage for @chainsafe/lodestar-z in addition to scanning prebuilt native binaries for unconditional AVX/AVX2 usage. Feedback suggests failing the check in CI environments if the package is not installed, rather than silently skipping it, to prevent false positives.

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 thread scripts/ci/check_native_portability.sh Outdated
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: 19bf819 Previous: ea1ba2d Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.0465 ms/op 1.9443 ms/op 0.54
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 41.079 us/op 45.800 us/op 0.90
BLS verify - blst 763.05 us/op 769.01 us/op 0.99
BLS verifyMultipleSignatures 3 - blst 1.3800 ms/op 1.3775 ms/op 1.00
BLS verifyMultipleSignatures 8 - blst 2.1763 ms/op 2.2079 ms/op 0.99
BLS verifyMultipleSignatures 32 - blst 6.8422 ms/op 7.3129 ms/op 0.94
BLS verifyMultipleSignatures 64 - blst 13.213 ms/op 13.846 ms/op 0.95
BLS verifyMultipleSignatures 128 - blst 25.643 ms/op 26.387 ms/op 0.97
BLS deserializing 10000 signatures 639.05 ms/op 653.82 ms/op 0.98
BLS deserializing 100000 signatures 6.7532 s/op 6.4407 s/op 1.05
BLS verifyMultipleSignatures - same message - 3 - blst 730.30 us/op 812.29 us/op 0.90
BLS verifyMultipleSignatures - same message - 8 - blst 986.61 us/op 941.51 us/op 1.05
BLS verifyMultipleSignatures - same message - 32 - blst 1.6987 ms/op 1.5040 ms/op 1.13
BLS verifyMultipleSignatures - same message - 64 - blst 2.5476 ms/op 2.3712 ms/op 1.07
BLS verifyMultipleSignatures - same message - 128 - blst 4.3274 ms/op 4.0764 ms/op 1.06
BLS aggregatePubkeys 32 - blst 17.994 us/op 17.787 us/op 1.01
BLS aggregatePubkeys 128 - blst 64.074 us/op 63.452 us/op 1.01
getSlashingsAndExits - default max 50.293 us/op 56.158 us/op 0.90
getSlashingsAndExits - 2k 376.85 us/op 383.98 us/op 0.98
proposeBlockBody type=full, size=empty 781.73 us/op 677.34 us/op 1.15
isKnown best case - 1 super set check 165.00 ns/op 163.00 ns/op 1.01
isKnown normal case - 2 super set checks 170.00 ns/op 162.00 ns/op 1.05
isKnown worse case - 16 super set checks 167.00 ns/op 162.00 ns/op 1.03
validate api signedAggregateAndProof - struct 1.5477 ms/op 1.5232 ms/op 1.02
validate gossip signedAggregateAndProof - struct 1.5426 ms/op 1.5203 ms/op 1.01
batch validate gossip attestation - vc 640000 - chunk 32 112.09 us/op 109.17 us/op 1.03
batch validate gossip attestation - vc 640000 - chunk 64 97.659 us/op 94.840 us/op 1.03
batch validate gossip attestation - vc 640000 - chunk 128 90.221 us/op 89.233 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 256 90.016 us/op 88.745 us/op 1.01
bytes32 toHexString 296.00 ns/op 292.00 ns/op 1.01
bytes32 Buffer.toString(hex) 162.00 ns/op 163.00 ns/op 0.99
bytes32 Buffer.toString(hex) from Uint8Array 239.00 ns/op 237.00 ns/op 1.01
bytes32 Buffer.toString(hex) + 0x 167.00 ns/op 166.00 ns/op 1.01
Return object 10000 times 0.21510 ns/op 0.21220 ns/op 1.01
Throw Error 10000 times 3.4189 us/op 3.3829 us/op 1.01
toHex 98.357 ns/op 98.014 ns/op 1.00
Buffer.from 90.591 ns/op 89.752 ns/op 1.01
shared Buffer 65.281 ns/op 63.822 ns/op 1.02
fastMsgIdFn sha256 / 200 bytes 1.4850 us/op 1.4600 us/op 1.02
fastMsgIdFn h32 xxhash / 200 bytes 154.00 ns/op 150.00 ns/op 1.03
fastMsgIdFn h64 xxhash / 200 bytes 200.00 ns/op 209.00 ns/op 0.96
fastMsgIdFn sha256 / 1000 bytes 4.7880 us/op 4.7100 us/op 1.02
fastMsgIdFn h32 xxhash / 1000 bytes 244.00 ns/op 241.00 ns/op 1.01
fastMsgIdFn h64 xxhash / 1000 bytes 251.00 ns/op 256.00 ns/op 0.98
fastMsgIdFn sha256 / 10000 bytes 42.294 us/op 41.641 us/op 1.02
fastMsgIdFn h32 xxhash / 10000 bytes 1.3120 us/op 1.2780 us/op 1.03
fastMsgIdFn h64 xxhash / 10000 bytes 857.00 ns/op 841.00 ns/op 1.02
send data - 1000 256B messages 4.4552 ms/op 4.5833 ms/op 0.97
send data - 1000 512B messages 5.4122 ms/op 5.4926 ms/op 0.99
send data - 1000 1024B messages 5.4536 ms/op 5.7289 ms/op 0.95
send data - 1000 1200B messages 6.5476 ms/op 6.3928 ms/op 1.02
send data - 1000 2048B messages 13.236 ms/op 12.096 ms/op 1.09
send data - 1000 4096B messages 76.739 ms/op 53.073 ms/op 1.45
send data - 1000 16384B messages 381.69 ms/op 337.64 ms/op 1.13
send data - 1000 65536B messages 1.6213 s/op 1.1146 s/op 1.45
enrSubnets - fastDeserialize 64 bits 765.00 ns/op 751.00 ns/op 1.02
enrSubnets - ssz BitVector 64 bits 265.00 ns/op 267.00 ns/op 0.99
enrSubnets - fastDeserialize 4 bits 102.00 ns/op 101.00 ns/op 1.01
enrSubnets - ssz BitVector 4 bits 264.00 ns/op 268.00 ns/op 0.99
prioritizePeers score -10:0 att 32-0.1 sync 2-0 208.05 us/op 206.41 us/op 1.01
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 236.12 us/op 235.44 us/op 1.00
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 350.22 us/op 340.19 us/op 1.03
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 618.80 us/op 602.94 us/op 1.03
prioritizePeers score 0:0 att 64-1 sync 4-1 715.38 us/op 695.72 us/op 1.03
array of 16000 items push then shift 1.3164 us/op 1.2866 us/op 1.02
LinkedList of 16000 items push then shift 7.5950 ns/op 6.8730 ns/op 1.11
array of 16000 items push then pop 84.605 ns/op 66.622 ns/op 1.27
LinkedList of 16000 items push then pop 6.1960 ns/op 5.9440 ns/op 1.04
array of 24000 items push then shift 1.9605 us/op 1.9030 us/op 1.03
LinkedList of 24000 items push then shift 6.8020 ns/op 6.6690 ns/op 1.02
array of 24000 items push then pop 118.67 ns/op 94.775 ns/op 1.25
LinkedList of 24000 items push then pop 6.2000 ns/op 5.9750 ns/op 1.04
intersect bitArray bitLen 8 4.8170 ns/op 4.6960 ns/op 1.03
intersect array and set length 8 29.907 ns/op 29.546 ns/op 1.01
intersect bitArray bitLen 128 24.690 ns/op 23.934 ns/op 1.03
intersect array and set length 128 505.64 ns/op 495.62 ns/op 1.02
bitArray.getTrueBitIndexes() bitLen 128 1.1550 us/op 971.00 ns/op 1.19
bitArray.getTrueBitIndexes() bitLen 248 1.7930 us/op 1.7240 us/op 1.04
bitArray.getTrueBitIndexes() bitLen 512 3.7170 us/op 3.5660 us/op 1.04
Full columns - reconstruct all 6 blobs 173.93 us/op 112.06 us/op 1.55
Full columns - reconstruct half of the blobs out of 6 68.900 us/op 62.559 us/op 1.10
Full columns - reconstruct single blob out of 6 36.267 us/op 36.690 us/op 0.99
Half columns - reconstruct all 6 blobs 403.88 ms/op 392.39 ms/op 1.03
Half columns - reconstruct half of the blobs out of 6 202.62 ms/op 194.85 ms/op 1.04
Half columns - reconstruct single blob out of 6 71.710 ms/op 68.553 ms/op 1.05
Set add up to 64 items then delete first 1.6652 us/op 1.6743 us/op 0.99
OrderedSet add up to 64 items then delete first 2.5446 us/op 2.5369 us/op 1.00
Set add up to 64 items then delete last 1.9181 us/op 1.9367 us/op 0.99
OrderedSet add up to 64 items then delete last 2.8602 us/op 2.8008 us/op 1.02
Set add up to 64 items then delete middle 1.9154 us/op 1.9200 us/op 1.00
OrderedSet add up to 64 items then delete middle 4.3307 us/op 4.2991 us/op 1.01
Set add up to 128 items then delete first 3.8087 us/op 3.8459 us/op 0.99
OrderedSet add up to 128 items then delete first 5.8455 us/op 5.7834 us/op 1.01
Set add up to 128 items then delete last 3.6733 us/op 3.7476 us/op 0.98
OrderedSet add up to 128 items then delete last 5.7398 us/op 5.4552 us/op 1.05
Set add up to 128 items then delete middle 3.6769 us/op 3.6696 us/op 1.00
OrderedSet add up to 128 items then delete middle 11.408 us/op 11.253 us/op 1.01
Set add up to 256 items then delete first 7.5582 us/op 7.5473 us/op 1.00
OrderedSet add up to 256 items then delete first 12.321 us/op 11.925 us/op 1.03
Set add up to 256 items then delete last 7.3311 us/op 7.4801 us/op 0.98
OrderedSet add up to 256 items then delete last 11.609 us/op 11.336 us/op 1.02
Set add up to 256 items then delete middle 7.3394 us/op 7.2424 us/op 1.01
OrderedSet add up to 256 items then delete middle 35.227 us/op 34.757 us/op 1.01
runFastConfirmationRules vc:100000 bc:96 eq:0 4.7972 ms/op 4.9622 ms/op 0.97
runFastConfirmationRules vc:600000 bc:96 eq:0 36.693 ms/op 34.829 ms/op 1.05
runFastConfirmationRules vc:1000000 bc:96 eq:0 61.477 ms/op 57.172 ms/op 1.08
runFastConfirmationRules vc:600000 bc:320 eq:0 36.848 ms/op 34.692 ms/op 1.06
runFastConfirmationRules vc:100000 bc:96 eq:1000 1.1652 s/op 1.0897 s/op 1.07
pass gossip attestations to forkchoice per slot 2.6548 ms/op 2.5593 ms/op 1.04
forkChoice updateHead vc 100000 bc 64 eq 0 446.64 us/op 447.85 us/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 0 2.6377 ms/op 2.6317 ms/op 1.00
forkChoice updateHead vc 1000000 bc 64 eq 0 4.5215 ms/op 4.3943 ms/op 1.03
forkChoice updateHead vc 600000 bc 320 eq 0 2.6911 ms/op 2.6803 ms/op 1.00
forkChoice updateHead vc 600000 bc 1200 eq 0 2.7172 ms/op 2.7279 ms/op 1.00
forkChoice updateHead vc 600000 bc 7200 eq 0 3.2406 ms/op 3.0089 ms/op 1.08
forkChoice updateHead vc 600000 bc 64 eq 1000 2.7627 ms/op 2.7078 ms/op 1.02
forkChoice updateHead vc 600000 bc 64 eq 10000 2.8064 ms/op 2.7550 ms/op 1.02
forkChoice updateHead vc 600000 bc 64 eq 300000 7.2203 ms/op 6.6388 ms/op 1.09
computeDeltas 1400000 validators 0% inactive 13.176 ms/op 13.290 ms/op 0.99
computeDeltas 1400000 validators 10% inactive 12.899 ms/op 12.482 ms/op 1.03
computeDeltas 1400000 validators 20% inactive 11.850 ms/op 11.619 ms/op 1.02
computeDeltas 1400000 validators 50% inactive 9.7312 ms/op 9.3103 ms/op 1.05
computeDeltas 2100000 validators 0% inactive 20.004 ms/op 19.561 ms/op 1.02
computeDeltas 2100000 validators 10% inactive 18.819 ms/op 17.988 ms/op 1.05
computeDeltas 2100000 validators 20% inactive 17.943 ms/op 17.535 ms/op 1.02
computeDeltas 2100000 validators 50% inactive 14.324 ms/op 13.680 ms/op 1.05
altair processAttestation - 250000 vs - 7PWei normalcase 1.9809 ms/op 1.7050 ms/op 1.16
altair processAttestation - 250000 vs - 7PWei worstcase 2.6912 ms/op 2.4994 ms/op 1.08
altair processAttestation - setStatus - 1/6 committees join 104.02 us/op 101.16 us/op 1.03
altair processAttestation - setStatus - 1/3 committees join 203.28 us/op 202.16 us/op 1.01
altair processAttestation - setStatus - 1/2 committees join 298.18 us/op 281.06 us/op 1.06
altair processAttestation - setStatus - 2/3 committees join 374.74 us/op 370.19 us/op 1.01
altair processAttestation - setStatus - 4/5 committees join 540.62 us/op 512.44 us/op 1.05
altair processAttestation - setStatus - 100% committees join 624.96 us/op 601.54 us/op 1.04
altair processBlock - 250000 vs - 7PWei normalcase 4.7198 ms/op 2.8397 ms/op 1.66
altair processBlock - 250000 vs - 7PWei normalcase hashState 20.215 ms/op 11.314 ms/op 1.79
altair processBlock - 250000 vs - 7PWei worstcase 22.927 ms/op 18.742 ms/op 1.22
altair processBlock - 250000 vs - 7PWei worstcase hashState 42.261 ms/op 38.269 ms/op 1.10
phase0 processBlock - 250000 vs - 7PWei normalcase 1.6113 ms/op 1.2984 ms/op 1.24
phase0 processBlock - 250000 vs - 7PWei worstcase 18.376 ms/op 17.332 ms/op 1.06
altair processEth1Data - 250000 vs - 7PWei normalcase 317.33 us/op 294.38 us/op 1.08
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 4.3480 us/op 3.6070 us/op 1.21
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 22.711 us/op 23.045 us/op 0.99
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.6710 us/op 6.1350 us/op 1.09
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.6250 us/op 3.6150 us/op 1.00
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 93.071 us/op 98.315 us/op 0.95
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4598 ms/op 1.3511 ms/op 1.08
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8932 ms/op 1.8043 ms/op 1.05
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8940 ms/op 1.7746 ms/op 1.07
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.8004 ms/op 3.5217 ms/op 1.08
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.1715 ms/op 2.0242 ms/op 1.07
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.2116 ms/op 3.8673 ms/op 1.09
Tree 40 250000 create 342.56 ms/op 308.72 ms/op 1.11
Tree 40 250000 get(125000) 100.25 ns/op 99.066 ns/op 1.01
Tree 40 250000 set(125000) 1.0425 us/op 1.0680 us/op 0.98
Tree 40 250000 toArray() 12.786 ms/op 9.4700 ms/op 1.35
Tree 40 250000 iterate all - toArray() + loop 14.006 ms/op 10.071 ms/op 1.39
Tree 40 250000 iterate all - get(i) 41.741 ms/op 33.897 ms/op 1.23
Array 250000 create 2.6209 ms/op 2.0171 ms/op 1.30
Array 250000 clone - spread 814.47 us/op 648.03 us/op 1.26
Array 250000 get(125000) 0.29700 ns/op 0.28800 ns/op 1.03
Array 250000 set(125000) 0.29700 ns/op 0.30100 ns/op 0.99
Array 250000 iterate all - loop 58.344 us/op 55.464 us/op 1.05
phase0 afterProcessEpoch - 250000 vs - 7PWei 39.913 ms/op 37.797 ms/op 1.06
Array.fill - length 1000000 2.1415 ms/op 1.8990 ms/op 1.13
Array push - length 1000000 9.3642 ms/op 7.1925 ms/op 1.30
Array.get 0.20822 ns/op 0.20054 ns/op 1.04
Uint8Array.get 0.25869 ns/op 0.25027 ns/op 1.03
phase0 beforeProcessEpoch - 250000 vs - 7PWei 15.331 ms/op 13.033 ms/op 1.18
altair processEpoch - mainnet_e81889 250.23 ms/op 245.85 ms/op 1.02
mainnet_e81889 - altair beforeProcessEpoch 16.064 ms/op 14.053 ms/op 1.14
mainnet_e81889 - altair processJustificationAndFinalization 5.8730 us/op 4.9080 us/op 1.20
mainnet_e81889 - altair processInactivityUpdates 3.6527 ms/op 3.5498 ms/op 1.03
mainnet_e81889 - altair processRewardsAndPenalties 20.424 ms/op 17.134 ms/op 1.19
mainnet_e81889 - altair processRegistryUpdates 551.00 ns/op 512.00 ns/op 1.08
mainnet_e81889 - altair processSlashings 133.00 ns/op 134.00 ns/op 0.99
mainnet_e81889 - altair processEth1DataReset 132.00 ns/op 124.00 ns/op 1.06
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.3009 ms/op 1.7247 ms/op 0.75
mainnet_e81889 - altair processSlashingsReset 691.00 ns/op 691.00 ns/op 1.00
mainnet_e81889 - altair processRandaoMixesReset 1.1910 us/op 1.0580 us/op 1.13
mainnet_e81889 - altair processHistoricalRootsUpdate 133.00 ns/op 126.00 ns/op 1.06
mainnet_e81889 - altair processParticipationFlagUpdates 439.00 ns/op 508.00 ns/op 0.86
mainnet_e81889 - altair processSyncCommitteeUpdates 109.00 ns/op 103.00 ns/op 1.06
mainnet_e81889 - altair afterProcessEpoch 41.653 ms/op 40.252 ms/op 1.03
capella processEpoch - mainnet_e217614 922.58 ms/op 835.54 ms/op 1.10
mainnet_e217614 - capella beforeProcessEpoch 82.309 ms/op 58.158 ms/op 1.42
mainnet_e217614 - capella processJustificationAndFinalization 6.4310 us/op 5.5080 us/op 1.17
mainnet_e217614 - capella processInactivityUpdates 15.071 ms/op 13.590 ms/op 1.11
mainnet_e217614 - capella processRewardsAndPenalties 97.305 ms/op 94.604 ms/op 1.03
mainnet_e217614 - capella processRegistryUpdates 4.5690 us/op 4.4830 us/op 1.02
mainnet_e217614 - capella processSlashings 135.00 ns/op 133.00 ns/op 1.02
mainnet_e217614 - capella processEth1DataReset 130.00 ns/op 128.00 ns/op 1.02
mainnet_e217614 - capella processEffectiveBalanceUpdates 20.477 ms/op 7.0944 ms/op 2.89
mainnet_e217614 - capella processSlashingsReset 697.00 ns/op 689.00 ns/op 1.01
mainnet_e217614 - capella processRandaoMixesReset 1.3310 us/op 1.1910 us/op 1.12
mainnet_e217614 - capella processHistoricalRootsUpdate 132.00 ns/op 128.00 ns/op 1.03
mainnet_e217614 - capella processParticipationFlagUpdates 452.00 ns/op 432.00 ns/op 1.05
mainnet_e217614 - capella afterProcessEpoch 110.66 ms/op 111.73 ms/op 0.99
phase0 processEpoch - mainnet_e58758 299.95 ms/op 281.80 ms/op 1.06
mainnet_e58758 - phase0 beforeProcessEpoch 64.388 ms/op 54.915 ms/op 1.17
mainnet_e58758 - phase0 processJustificationAndFinalization 6.1630 us/op 5.5380 us/op 1.11
mainnet_e58758 - phase0 processRewardsAndPenalties 16.911 ms/op 15.622 ms/op 1.08
mainnet_e58758 - phase0 processRegistryUpdates 2.2800 us/op 2.2700 us/op 1.00
mainnet_e58758 - phase0 processSlashings 135.00 ns/op 136.00 ns/op 0.99
mainnet_e58758 - phase0 processEth1DataReset 138.00 ns/op 125.00 ns/op 1.10
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.0509 ms/op 1.0019 ms/op 1.05
mainnet_e58758 - phase0 processSlashingsReset 950.00 ns/op 884.00 ns/op 1.07
mainnet_e58758 - phase0 processRandaoMixesReset 1.2850 us/op 1.1550 us/op 1.11
mainnet_e58758 - phase0 processHistoricalRootsUpdate 139.00 ns/op 129.00 ns/op 1.08
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.1860 us/op 1.1870 us/op 1.00
mainnet_e58758 - phase0 afterProcessEpoch 34.046 ms/op 34.342 ms/op 0.99
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.2340 ms/op 1.0179 ms/op 1.21
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.7183 ms/op 1.6334 ms/op 1.05
altair processInactivityUpdates - 250000 normalcase 10.976 ms/op 10.675 ms/op 1.03
altair processInactivityUpdates - 250000 worstcase 11.468 ms/op 11.357 ms/op 1.01
phase0 processRegistryUpdates - 250000 normalcase 2.4340 us/op 2.5520 us/op 0.95
phase0 processRegistryUpdates - 250000 badcase_full_deposits 150.71 us/op 147.03 us/op 1.03
phase0 processRegistryUpdates - 250000 worstcase 0.5 62.441 ms/op 62.244 ms/op 1.00
altair processRewardsAndPenalties - 250000 normalcase 16.183 ms/op 13.869 ms/op 1.17
altair processRewardsAndPenalties - 250000 worstcase 15.536 ms/op 15.337 ms/op 1.01
phase0 getAttestationDeltas - 250000 normalcase 5.6848 ms/op 5.4385 ms/op 1.05
phase0 getAttestationDeltas - 250000 worstcase 5.7572 ms/op 5.9912 ms/op 0.96
phase0 processSlashings - 250000 worstcase 64.064 us/op 62.355 us/op 1.03
altair processSyncCommitteeUpdates - 250000 10.490 ms/op 10.387 ms/op 1.01
BeaconState.hashTreeRoot - No change 180.00 ns/op 172.00 ns/op 1.05
BeaconState.hashTreeRoot - 1 full validator 74.192 us/op 72.347 us/op 1.03
BeaconState.hashTreeRoot - 32 full validator 902.08 us/op 850.03 us/op 1.06
BeaconState.hashTreeRoot - 512 full validator 7.2786 ms/op 7.6666 ms/op 0.95
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 96.687 us/op 87.817 us/op 1.10
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.5049 ms/op 1.1324 ms/op 1.33
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 16.347 ms/op 16.611 ms/op 0.98
BeaconState.hashTreeRoot - 1 balances 77.178 us/op 86.681 us/op 0.89
BeaconState.hashTreeRoot - 32 balances 764.65 us/op 805.91 us/op 0.95
BeaconState.hashTreeRoot - 512 balances 6.1021 ms/op 5.9733 ms/op 1.02
BeaconState.hashTreeRoot - 250000 balances 128.94 ms/op 108.51 ms/op 1.19
aggregationBits - 2048 els - zipIndexesInBitList 21.444 us/op 19.879 us/op 1.08
regular array get 100000 times 23.241 us/op 23.404 us/op 0.99
wrappedArray get 100000 times 23.332 us/op 23.335 us/op 1.00
arrayWithProxy get 100000 times 10.929 ms/op 10.712 ms/op 1.02
ssz.Root.equals 21.721 ns/op 21.686 ns/op 1.00
byteArrayEquals 21.554 ns/op 21.530 ns/op 1.00
Buffer.compare 9.0970 ns/op 8.9580 ns/op 1.02
processSlot - 1 slots 9.4280 us/op 7.9750 us/op 1.18
processSlot - 32 slots 2.0958 ms/op 1.8668 ms/op 1.12
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 4.1087 ms/op 3.9693 ms/op 1.04
getCommitteeAssignments - req 1 vs - 250000 vc 1.7017 ms/op 1.6799 ms/op 1.01
getCommitteeAssignments - req 100 vs - 250000 vc 3.4762 ms/op 3.4313 ms/op 1.01
getCommitteeAssignments - req 1000 vs - 250000 vc 3.7499 ms/op 3.6797 ms/op 1.02
findModifiedValidators - 10000 modified validators 919.19 ms/op 744.57 ms/op 1.23
findModifiedValidators - 1000 modified validators 566.03 ms/op 646.44 ms/op 0.88
findModifiedValidators - 100 modified validators 377.33 ms/op 299.67 ms/op 1.26
findModifiedValidators - 10 modified validators 280.79 ms/op 245.77 ms/op 1.14
findModifiedValidators - 1 modified validators 311.52 ms/op 212.26 ms/op 1.47
findModifiedValidators - no difference 215.81 ms/op 200.97 ms/op 1.07
migrate state 1500000 validators, 3400 modified, 2000 new 2.6916 s/op 2.6453 s/op 1.02
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 5.6500 ns/op 3.8000 ns/op 1.49
state getBlockRootAtSlot - 250000 vs - 7PWei 292.64 ns/op 359.27 ns/op 0.81
computeProposerIndex 100000 validators 1.3397 ms/op 1.3134 ms/op 1.02
getNextSyncCommitteeIndices 1000 validators 2.8886 ms/op 2.8707 ms/op 1.01
getNextSyncCommitteeIndices 10000 validators 25.448 ms/op 25.065 ms/op 1.02
getNextSyncCommitteeIndices 100000 validators 84.868 ms/op 82.997 ms/op 1.02
computeProposers - vc 250000 539.48 us/op 560.33 us/op 0.96
computeEpochShuffling - vc 250000 40.466 ms/op 41.287 ms/op 0.98
getNextSyncCommittee - vc 250000 9.8197 ms/op 9.5593 ms/op 1.03
nodejs block root to RootHex using toHex 101.56 ns/op 102.74 ns/op 0.99
nodejs block root to RootHex using toRootHex 65.207 ns/op 65.219 ns/op 1.00
nodejs fromHex(blob) 859.25 us/op 745.88 us/op 1.15
nodejs fromHexInto(blob) 661.42 us/op 661.40 us/op 1.00
nodejs block root to RootHex using the deprecated toHexString 484.32 ns/op 540.41 ns/op 0.90
nodejs byteArrayEquals 32 bytes (block root) 26.114 ns/op 25.925 ns/op 1.01
nodejs byteArrayEquals 48 bytes (pubkey) 37.866 ns/op 37.255 ns/op 1.02
nodejs byteArrayEquals 96 bytes (signature) 39.442 ns/op 37.709 ns/op 1.05
nodejs byteArrayEquals 1024 bytes 45.106 ns/op 47.529 ns/op 0.95
nodejs byteArrayEquals 131072 bytes (blob) 1.7799 us/op 1.7672 us/op 1.01
browser block root to RootHex using toHex 146.47 ns/op 145.83 ns/op 1.00
browser block root to RootHex using toRootHex 131.87 ns/op 131.28 ns/op 1.00
browser fromHex(blob) 1.6632 ms/op 1.6866 ms/op 0.99
browser fromHexInto(blob) 663.97 us/op 656.61 us/op 1.01
browser block root to RootHex using the deprecated toHexString 346.08 ns/op 367.07 ns/op 0.94
browser byteArrayEquals 32 bytes (block root) 27.673 ns/op 27.928 ns/op 0.99
browser byteArrayEquals 48 bytes (pubkey) 39.016 ns/op 39.410 ns/op 0.99
browser byteArrayEquals 96 bytes (signature) 72.997 ns/op 73.994 ns/op 0.99
browser byteArrayEquals 1024 bytes 749.33 ns/op 756.61 ns/op 0.99
browser byteArrayEquals 131072 bytes (blob) 94.209 us/op 94.782 us/op 0.99

by benchmarkbot/action

Comment thread scripts/ci/check_native_portability.sh Outdated
@nflaig
nflaig marked this pull request as ready for review July 10, 2026 20:30
@nflaig
nflaig requested a review from a team as a code owner July 10, 2026 20:30
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.49%. Comparing base (2e78647) to head (94bc653).

Additional details and impacted files
@@            Coverage Diff            @@
##           unstable    #9642   +/-   ##
=========================================
  Coverage     52.49%   52.49%           
=========================================
  Files           848      848           
  Lines         60485    60485           
  Branches       4466     4466           
=========================================
  Hits          31754    31754           
  Misses        28670    28670           
  Partials         61       61           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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: 94bc653d6c

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/ci/check_native_portability.sh Outdated
requiredTargets.some((target) => target.aliases.some((alias) => name.endsWith(`-${alias}`)))
);

if (hasTargetPackages || prebuildFiles.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include zapi targets in native coverage

For zapi packages such as @chainsafe/lodestar-z, the supported platform matrix is declared as package.json#zapi.targets and the built .node can live under zig-out/lib, so both hasTargetPackages and prebuildFiles.length are false. In that install shape this condition skips the package entirely for target coverage, so a missing ARM64/musl zapi target passes CI—the exact regression this check is meant to catch. Please treat manifest.zapi?.targets as a native package signal and compare it with requiredTargets.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@lodekeeper can you check if this is relevant, if so open a PR against mine

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.

Relevant — verified. A zapi package with no per-target optionalDependencies and no prebuilds/ dir (.node under zig-out/lib) hits hasTargetPackages === false && prebuildFiles.length === 0 and is skipped entirely, so a dropped target passes CI. lodestar-z@0.1.2 is only caught today because it also ships per-target optionalDeps.

Opened #9644 against this branch: adds zapi.targets to native-package detection + validates it against the required matrix (fallback after optionalDeps/prebuilds, so lodestar-z keeps its current behavior). Verified byte-identical output on the real beacon-node/validator graph, and a synthetic pure-zapi fixture missing aarch64-unknown-linux-musl now fails where the base script silently passed (exit 0).

…#9644)

Follow-up to #9642 (against `nflaig/check-native-portability`),
addressing the Codex bot P2 comment (`r3561870210`).

## Relevant? Yes

`check_native_portability.sh` detects native packages via **(a)**
per-target `optionalDependencies` naming or **(b)** a `prebuilds/` dir.
zapi packages (e.g. `@chainsafe/lodestar-z`) declare their platform
matrix as `package.json#zapi.targets` and build the `.node` under
`zig-out/lib`. A zapi package with **neither** per-target
optionalDependencies **nor** a `prebuilds/` dir is `hasTargetPackages
=== false && prebuildFiles.length === 0` → **skipped entirely** for
target coverage, so a dropped target (e.g. `aarch64-unknown-linux-musl`)
passes CI — the exact regression this check exists to catch.

`lodestar-z@0.1.2` happens to also ship per-target
`optionalDependencies` today, so it is detected via (a) — but that
relies on the optionalDependency naming convention rather than the
authoritative `zapi.targets` declaration.

## Fix

Treat `zapi.targets` as a native-target signal:
- add it to native-package **detection** (`hasTargetPackages ||
prebuildFiles.length > 0 || zapiTargets.length > 0`) so zapi packages
are never silently skipped;
- **validate** it against the required matrix as a fallback, after
optionalDependencies and prebuilds.

Precedence is optionalDeps → prebuilds → `zapi.targets`, so packages
that ship both (like `lodestar-z`) keep their existing verified
behavior; `zapi.targets` only kicks in for the otherwise-skipped shape.

## Verification

- **No regression on the real graph:** running the patched script
against the installed `beacon-node`/`validator` dependency graph
produces byte-identical output to the base branch (exit 0).
- **Catches the gap:** a synthetic pure-zapi package (only
`zapi.targets`, missing `aarch64-unknown-linux-musl`, `.node` under
`zig-out/lib`, no optionalDeps/prebuilds) alongside a complete native
package:
- base script → **exit 0** (silently passes; package never appears in
target coverage)
- patched script → **exit 1**, `FAIL: @chainsafe/fake-zapi@1.0.0 /
missing zapi target for aarch64-unknown-linux-musl`

🤖 Generated with AI assistance

Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>

@matthewkeil matthewkeil 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.

@lodekeeper I would like to convert this script into a node script, instead of having node code inlined into a bash/shell script and then some mixed in shell commands. Please convert the shell portion of the file over to node format and then get the new node code integrated so its all nice and tidy. Might also need to update the trigger in the CI.

@nflaig

nflaig commented Jul 15, 2026

Copy link
Copy Markdown
Member Author

@lodekeeper I would like to convert this script into a node script, instead of having node code inlined into a bash/shell script and then some mixed in shell commands. Please convert the shell portion of the file over to node format and then get the new node code integrated so its all nice and tidy. Might also need to update the trigger in the CI.

I had my codex that was working on this re-write the script, no idea if it's correct though, especially the AVX stuff we need to make sure doesn't happen again

Comment on lines +29 to +35
const knownMissingTargets = new Set([
"@chainsafe/hashtree@1.0.2:x86_64-apple-darwin",
"@crate-crypto/node-eth-kzg@0.9.1:aarch64-unknown-linux-musl",
"@crate-crypto/node-eth-kzg@0.9.1:x86_64-unknown-linux-musl",
"classic-level@1.4.1:aarch64-unknown-linux-musl",
"classic-level@3.0.0:aarch64-unknown-linux-musl",
]);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this makes me question if we can even run on arm64 musl right now, at least natively, classic-level seems to build locally, but node-eth-kzg it doesn't seem like it, I wonder if that's relevant, eg. rocketpool users likely run via docker, and we switched from alpine to debian a while ago, not sure when we switched to rust-eth-kzg, was it before fusaka, I would think so

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.

Sooo, that means we can drop support for musl? If we can, I would prefer to do so. This is one less thing we need to test. (We already aren't testing it in our fleet, so its not like its "well supported" in the first place)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

not sure if we can fix the blind spots in the testing fleet, would probably be a stretch, what I am hoping is at least with this script we have a way to document what platforms we need and want to support, and be bit more deliberate about it, native dependencies breaking user setups was by far the most common reason we had to cut a hotfix, I think we also had an issue with hashtree, or maybe it just fell back to a less performant implementation? arguably that is even worse. but regarding the question, I don't see a setup we would break right now not shipping arm64 musl, most seem debian or docker based

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.

Native musl is already effectively unsupported for a beacon node today, so dropping it is mostly formalizing reality.

Per-target, from the current production graph:

  • x86_64-musl: blst ✓, hashtree ✓, classic-level ✓ — but @crate-crypto/node-eth-kzg ✗ (no musl prebuild).
  • aarch64-musl: blst ✓, hashtree ✓ — but classic-level ✗ and node-eth-kzg ✗.

node-eth-kzg (our rust-eth-kzg, required for blobs/PeerDAS) ships zero musl builds — still true on latest 0.10.0, not just the pinned 0.9.1, so it's an upstream gap we can't just bump our way out of. Without a musl KZG binary there's no npm install-able native beacon node on Alpine (short of building the Rust crate from source, which we don't wire up), so there's no working setup to lose.

Given that + the official image is node:24-slim (Debian/glibc since #7004, Aug 2024, so docker/RocketPool users are glibc) + it's not in the fleet — agreed, dropping it is reasonable. In this script that's just removing the two *-linux-musl entries from requiredTargets (and the now-moot musl knownMissingTargets). Can push that once you and Nico settle on it.

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.

+1 — documenting the matrix deliberately is the point; native-dep breakage being the top hotfix cause is exactly why this check earns its keep.

On hashtree: it's not a musl gap — @chainsafe/hashtree@1.0.2 ships both linux-x64-musl and linux-arm64-musl; its only missing target is macOS-Intel (x86_64-apple-darwin), which the script flags. But the "fell back to a less performant impl, which is worse" instinct is right and real: if the hashtree native binary isn't loaded (missing prebuild, or the module gets duplicated in the tree), persistent-merkle-tree silently drops to the JS hasher — much slower, and it's caused GC regressions before (we added a CI dedup-lint after one such incident). Silent degradation is worse than a hard failure, so a gate like this is the right call.

One clarification on the runtime idea: this script is a build/CI-time gate — it inspects the installed graph in CI, it never runs on a user's node, so it won't warn an Alpine user whose binary "works" until it hits a missing native dep. Catching that needs a small separate startup check — detect musl at runtime (absence of glibcVersionRuntime in process.report, or detect-libc) and warn/refuse to start. Happy to draft that as a follow-up, plus add the supported-platform matrix to the install docs so musl users aren't surprised in the first place.

Comment thread scripts/ci/check_native_portability.mjs Outdated
errors.push({target: target.name, message: `missing bundled prebuild for ${target.name}`});
}

// zapi.targets is the build matrix, while optionalDependencies describes

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

seems sketchy that zapi requires this special handling

@nflaig
nflaig requested a review from matthewkeil July 15, 2026 18:01
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