ChainSafe / lodestar

🌟 TypeScript Implementation of Ethereum Consensus
https://lodestar.chainsafe.io
Apache License 2.0
1.1k stars 273 forks source link

refactor: improve types package to use forks as generics #6825

Closed nazarhussain closed 1 week ago

nazarhussain commented 1 month ago

Motivation

Simplify the usage of Fork specific types around the repo.

Description

Closes #4656

Steps to test or reproduce

github-actions[bot] commented 1 month ago

Performance Report

✔️ no performance regression detected

Full benchmark results | Benchmark suite | Current: b731f866ecedb9168f80bfc095a4f3fc87ea5e16 | Previous: a2c389fc97b03c415e3bccaef5cf2d512065b7a7 | Ratio | |-|-|-|-| | getPubkeys - index2pubkey - req 1000 vs - 250000 vc | 758.91 us/op | 661.76 us/op | 1.15 | | getPubkeys - validatorsArr - req 1000 vs - 250000 vc | 38.884 us/op | 43.150 us/op | 0.90 | | BLS verify - blst-native | 1.1086 ms/op | 1.0887 ms/op | 1.02 | | BLS verifyMultipleSignatures 3 - blst-native | 2.3641 ms/op | 2.3169 ms/op | 1.02 | | BLS verifyMultipleSignatures 8 - blst-native | 5.2291 ms/op | 5.1351 ms/op | 1.02 | | BLS verifyMultipleSignatures 32 - blst-native | 19.214 ms/op | 18.986 ms/op | 1.01 | | BLS verifyMultipleSignatures 64 - blst-native | 37.837 ms/op | 37.351 ms/op | 1.01 | | BLS verifyMultipleSignatures 128 - blst-native | 75.097 ms/op | 73.779 ms/op | 1.02 | | BLS deserializing 10000 signatures | 773.94 ms/op | 781.45 ms/op | 0.99 | | BLS deserializing 100000 signatures | 7.7564 s/op | 7.8412 s/op | 0.99 | | BLS verifyMultipleSignatures - same message - 3 - blst-native | 1.0921 ms/op | 1.1036 ms/op | 0.99 | | BLS verifyMultipleSignatures - same message - 8 - blst-native | 1.2417 ms/op | 1.2645 ms/op | 0.98 | | BLS verifyMultipleSignatures - same message - 32 - blst-native | 1.9452 ms/op | 2.2742 ms/op | 0.86 | | BLS verifyMultipleSignatures - same message - 64 - blst-native | 3.4660 ms/op | 2.8690 ms/op | 1.21 | | BLS verifyMultipleSignatures - same message - 128 - blst-native | 5.0081 ms/op | 4.9112 ms/op | 1.02 | | BLS aggregatePubkeys 32 - blst-native | 22.876 us/op | 22.416 us/op | 1.02 | | BLS aggregatePubkeys 128 - blst-native | 90.156 us/op | 88.066 us/op | 1.02 | | notSeenSlots=1 numMissedVotes=1 numBadVotes=10 | 54.299 ms/op | 63.538 ms/op | 0.85 | | notSeenSlots=1 numMissedVotes=0 numBadVotes=4 | 41.883 ms/op | 42.288 ms/op | 0.99 | | notSeenSlots=2 numMissedVotes=1 numBadVotes=10 | 30.035 ms/op | 29.100 ms/op | 1.03 | | getSlashingsAndExits - default max | 78.748 us/op | 99.212 us/op | 0.79 | | getSlashingsAndExits - 2k | 218.18 us/op | 290.75 us/op | 0.75 | | proposeBlockBody type=full, size=empty | 5.1653 ms/op | 4.7611 ms/op | 1.08 | | isKnown best case - 1 super set check | 435.00 ns/op | 504.00 ns/op | 0.86 | | isKnown normal case - 2 super set checks | 452.00 ns/op | 460.00 ns/op | 0.98 | | isKnown worse case - 16 super set checks | 445.00 ns/op | 458.00 ns/op | 0.97 | | InMemoryCheckpointStateCache - add get delete | 4.0320 us/op | 4.4050 us/op | 0.92 | | validate api signedAggregateAndProof - struct | 2.3871 ms/op | 2.2834 ms/op | 1.05 | | validate gossip signedAggregateAndProof - struct | 2.3903 ms/op | 2.3724 ms/op | 1.01 | | validate gossip attestation - vc 640000 | 1.1167 ms/op | 1.1088 ms/op | 1.01 | | batch validate gossip attestation - vc 640000 - chunk 32 | 130.90 us/op | 133.21 us/op | 0.98 | | batch validate gossip attestation - vc 640000 - chunk 64 | 117.89 us/op | 116.46 us/op | 1.01 | | batch validate gossip attestation - vc 640000 - chunk 128 | 109.04 us/op | 105.57 us/op | 1.03 | | batch validate gossip attestation - vc 640000 - chunk 256 | 105.24 us/op | 105.29 us/op | 1.00 | | pickEth1Vote - no votes | 865.33 us/op | 869.83 us/op | 0.99 | | pickEth1Vote - max votes | 6.6471 ms/op | 5.4982 ms/op | 1.21 | | pickEth1Vote - Eth1Data hashTreeRoot value x2048 | 11.915 ms/op | 9.3177 ms/op | 1.28 | | pickEth1Vote - Eth1Data hashTreeRoot tree x2048 | 18.884 ms/op | 15.009 ms/op | 1.26 | | pickEth1Vote - Eth1Data fastSerialize value x2048 | 370.51 us/op | 371.66 us/op | 1.00 | | pickEth1Vote - Eth1Data fastSerialize tree x2048 | 4.0254 ms/op | 5.5175 ms/op | 0.73 | | bytes32 toHexString | 595.00 ns/op | 606.00 ns/op | 0.98 | | bytes32 Buffer.toString(hex) | 466.00 ns/op | 428.00 ns/op | 1.09 | | bytes32 Buffer.toString(hex) from Uint8Array | 550.00 ns/op | 541.00 ns/op | 1.02 | | bytes32 Buffer.toString(hex) + 0x | 443.00 ns/op | 433.00 ns/op | 1.02 | | Object access 1 prop | 0.32300 ns/op | 0.33800 ns/op | 0.96 | | Map access 1 prop | 0.32800 ns/op | 0.32900 ns/op | 1.00 | | Object get x1000 | 5.7750 ns/op | 5.2490 ns/op | 1.10 | | Map get x1000 | 6.1420 ns/op | 6.0260 ns/op | 1.02 | | Object set x1000 | 27.205 ns/op | 25.083 ns/op | 1.08 | | Map set x1000 | 19.396 ns/op | 18.543 ns/op | 1.05 | | Return object 10000 times | 0.29920 ns/op | 0.29790 ns/op | 1.00 | | Throw Error 10000 times | 2.7456 us/op | 2.7153 us/op | 1.01 | | fastMsgIdFn sha256 / 200 bytes | 2.0040 us/op | 1.9580 us/op | 1.02 | | fastMsgIdFn h32 xxhash / 200 bytes | 416.00 ns/op | 410.00 ns/op | 1.01 | | fastMsgIdFn h64 xxhash / 200 bytes | 457.00 ns/op | 455.00 ns/op | 1.00 | | fastMsgIdFn sha256 / 1000 bytes | 5.9820 us/op | 5.9250 us/op | 1.01 | | fastMsgIdFn h32 xxhash / 1000 bytes | 585.00 ns/op | 578.00 ns/op | 1.01 | | fastMsgIdFn h64 xxhash / 1000 bytes | 524.00 ns/op | 551.00 ns/op | 0.95 | | fastMsgIdFn sha256 / 10000 bytes | 48.866 us/op | 50.609 us/op | 0.97 | | fastMsgIdFn h32 xxhash / 10000 bytes | 1.8890 us/op | 1.9610 us/op | 0.96 | | fastMsgIdFn h64 xxhash / 10000 bytes | 1.3330 us/op | 1.3210 us/op | 1.01 | | send data - 1000 256B messages | 9.8307 ms/op | 10.575 ms/op | 0.93 | | send data - 1000 512B messages | 13.503 ms/op | 14.028 ms/op | 0.96 | | send data - 1000 1024B messages | 20.533 ms/op | 24.156 ms/op | 0.85 | | send data - 1000 1200B messages | 23.271 ms/op | 23.967 ms/op | 0.97 | | send data - 1000 2048B messages | 28.563 ms/op | 28.135 ms/op | 1.02 | | send data - 1000 4096B messages | 25.632 ms/op | 25.565 ms/op | 1.00 | | send data - 1000 16384B messages | 60.428 ms/op | 61.234 ms/op | 0.99 | | send data - 1000 65536B messages | 233.84 ms/op | 235.41 ms/op | 0.99 | | enrSubnets - fastDeserialize 64 bits | 1.1040 us/op | 1.0770 us/op | 1.03 | | enrSubnets - ssz BitVector 64 bits | 507.00 ns/op | 492.00 ns/op | 1.03 | | enrSubnets - fastDeserialize 4 bits | 327.00 ns/op | 326.00 ns/op | 1.00 | | enrSubnets - ssz BitVector 4 bits | 515.00 ns/op | 504.00 ns/op | 1.02 | | prioritizePeers score -10:0 att 32-0.1 sync 2-0 | 126.52 us/op | 119.13 us/op | 1.06 | | prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 | 151.68 us/op | 142.91 us/op | 1.06 | | prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 | 301.74 us/op | 281.28 us/op | 1.07 | | prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 | 501.48 us/op | 518.32 us/op | 0.97 | | prioritizePeers score 0:0 att 64-1 sync 4-1 | 490.25 us/op | 637.87 us/op | 0.77 | | array of 16000 items push then shift | 1.2135 us/op | 1.2317 us/op | 0.99 | | LinkedList of 16000 items push then shift | 7.2430 ns/op | 7.4080 ns/op | 0.98 | | array of 16000 items push then pop | 80.871 ns/op | 88.156 ns/op | 0.92 | | LinkedList of 16000 items push then pop | 6.1320 ns/op | 6.2600 ns/op | 0.98 | | array of 24000 items push then shift | 1.8802 us/op | 1.7756 us/op | 1.06 | | LinkedList of 24000 items push then shift | 6.6810 ns/op | 6.9080 ns/op | 0.97 | | array of 24000 items push then pop | 107.76 ns/op | 117.86 ns/op | 0.91 | | LinkedList of 24000 items push then pop | 6.3080 ns/op | 6.3690 ns/op | 0.99 | | intersect bitArray bitLen 8 | 5.5720 ns/op | 5.3940 ns/op | 1.03 | | intersect array and set length 8 | 39.664 ns/op | 39.979 ns/op | 0.99 | | intersect bitArray bitLen 128 | 26.842 ns/op | 26.402 ns/op | 1.02 | | intersect array and set length 128 | 587.81 ns/op | 584.82 ns/op | 1.01 | | bitArray.getTrueBitIndexes() bitLen 128 | 1.4240 us/op | 2.1650 us/op | 0.66 | | bitArray.getTrueBitIndexes() bitLen 248 | 3.2270 us/op | 3.2120 us/op | 1.00 | | bitArray.getTrueBitIndexes() bitLen 512 | 6.1270 us/op | 6.3880 us/op | 0.96 | | Buffer.concat 32 items | 1.0340 us/op | 1.0580 us/op | 0.98 | | Uint8Array.set 32 items | 1.8840 us/op | 2.1340 us/op | 0.88 | | Buffer.copy | 1.7620 us/op | 1.7990 us/op | 0.98 | | Uint8Array.set - with subarray | 2.1900 us/op | 2.3020 us/op | 0.95 | | Uint8Array.set - without subarray | 1.5380 us/op | 1.6320 us/op | 0.94 | | Set add up to 64 items then delete first | 1.8562 us/op | 1.7756 us/op | 1.05 | | OrderedSet add up to 64 items then delete first | 2.8820 us/op | 2.7437 us/op | 1.05 | | Set add up to 64 items then delete last | 2.1063 us/op | 2.0063 us/op | 1.05 | | OrderedSet add up to 64 items then delete last | 3.2990 us/op | 3.0327 us/op | 1.09 | | Set add up to 64 items then delete middle | 2.1298 us/op | 2.0154 us/op | 1.06 | | OrderedSet add up to 64 items then delete middle | 4.7118 us/op | 4.1594 us/op | 1.13 | | Set add up to 128 items then delete first | 4.0944 us/op | 3.7022 us/op | 1.11 | | OrderedSet add up to 128 items then delete first | 6.1954 us/op | 5.8177 us/op | 1.06 | | Set add up to 128 items then delete last | 4.0818 us/op | 3.5464 us/op | 1.15 | | OrderedSet add up to 128 items then delete last | 6.3653 us/op | 5.4407 us/op | 1.17 | | Set add up to 128 items then delete middle | 4.0547 us/op | 3.5578 us/op | 1.14 | | OrderedSet add up to 128 items then delete middle | 12.216 us/op | 10.668 us/op | 1.15 | | Set add up to 256 items then delete first | 7.9957 us/op | 7.2768 us/op | 1.10 | | OrderedSet add up to 256 items then delete first | 12.272 us/op | 11.693 us/op | 1.05 | | Set add up to 256 items then delete last | 8.0390 us/op | 7.0342 us/op | 1.14 | | OrderedSet add up to 256 items then delete last | 12.668 us/op | 10.973 us/op | 1.15 | | Set add up to 256 items then delete middle | 7.9495 us/op | 6.9961 us/op | 1.14 | | OrderedSet add up to 256 items then delete middle | 38.504 us/op | 32.368 us/op | 1.19 | | transfer serialized Status (84 B) | 1.3260 us/op | 1.3490 us/op | 0.98 | | copy serialized Status (84 B) | 1.1350 us/op | 1.2680 us/op | 0.90 | | transfer serialized SignedVoluntaryExit (112 B) | 1.4380 us/op | 1.6460 us/op | 0.87 | | copy serialized SignedVoluntaryExit (112 B) | 1.1670 us/op | 1.3120 us/op | 0.89 | | transfer serialized ProposerSlashing (416 B) | 1.5960 us/op | 1.9880 us/op | 0.80 | | copy serialized ProposerSlashing (416 B) | 1.3410 us/op | 1.9690 us/op | 0.68 | | transfer serialized Attestation (485 B) | 1.6340 us/op | 1.9450 us/op | 0.84 | | copy serialized Attestation (485 B) | 1.3360 us/op | 1.8700 us/op | 0.71 | | transfer serialized AttesterSlashing (33232 B) | 1.7490 us/op | 2.2130 us/op | 0.79 | | copy serialized AttesterSlashing (33232 B) | 3.8410 us/op | 4.1770 us/op | 0.92 | | transfer serialized Small SignedBeaconBlock (128000 B) | 2.0560 us/op | 3.0470 us/op | 0.67 | | copy serialized Small SignedBeaconBlock (128000 B) | 9.7800 us/op | 9.4910 us/op | 1.03 | | transfer serialized Avg SignedBeaconBlock (200000 B) | 2.2540 us/op | 2.7380 us/op | 0.82 | | copy serialized Avg SignedBeaconBlock (200000 B) | 13.533 us/op | 12.317 us/op | 1.10 | | transfer serialized BlobsSidecar (524380 B) | 3.0710 us/op | 3.1160 us/op | 0.99 | | copy serialized BlobsSidecar (524380 B) | 79.058 us/op | 73.556 us/op | 1.07 | | transfer serialized Big SignedBeaconBlock (1000000 B) | 3.3090 us/op | 3.1330 us/op | 1.06 | | copy serialized Big SignedBeaconBlock (1000000 B) | 139.90 us/op | 185.07 us/op | 0.76 | | pass gossip attestations to forkchoice per slot | 2.7040 ms/op | 2.5791 ms/op | 1.05 | | forkChoice updateHead vc 100000 bc 64 eq 0 | 423.71 us/op | 457.97 us/op | 0.93 | | forkChoice updateHead vc 600000 bc 64 eq 0 | 2.6915 ms/op | 2.4127 ms/op | 1.12 | | forkChoice updateHead vc 1000000 bc 64 eq 0 | 4.3178 ms/op | 4.1020 ms/op | 1.05 | | forkChoice updateHead vc 600000 bc 320 eq 0 | 2.6131 ms/op | 2.3828 ms/op | 1.10 | | forkChoice updateHead vc 600000 bc 1200 eq 0 | 2.6441 ms/op | 2.4128 ms/op | 1.10 | | forkChoice updateHead vc 600000 bc 7200 eq 0 | 2.9152 ms/op | 2.9998 ms/op | 0.97 | | forkChoice updateHead vc 600000 bc 64 eq 1000 | 9.7557 ms/op | 9.2182 ms/op | 1.06 | | forkChoice updateHead vc 600000 bc 64 eq 10000 | 9.6172 ms/op | 9.4072 ms/op | 1.02 | | forkChoice updateHead vc 600000 bc 64 eq 300000 | 11.745 ms/op | 11.566 ms/op | 1.02 | | computeDeltas 500000 validators 300 proto nodes | 3.0598 ms/op | 2.9359 ms/op | 1.04 | | computeDeltas 500000 validators 1200 proto nodes | 3.0116 ms/op | 2.9445 ms/op | 1.02 | | computeDeltas 500000 validators 7200 proto nodes | 2.9682 ms/op | 2.9549 ms/op | 1.00 | | computeDeltas 750000 validators 300 proto nodes | 4.5319 ms/op | 4.3171 ms/op | 1.05 | | computeDeltas 750000 validators 1200 proto nodes | 4.3566 ms/op | 4.4219 ms/op | 0.99 | | computeDeltas 750000 validators 7200 proto nodes | 4.3500 ms/op | 4.4190 ms/op | 0.98 | | computeDeltas 1400000 validators 300 proto nodes | 8.1595 ms/op | 8.1297 ms/op | 1.00 | | computeDeltas 1400000 validators 1200 proto nodes | 9.4770 ms/op | 8.0901 ms/op | 1.17 | | computeDeltas 1400000 validators 7200 proto nodes | 9.1947 ms/op | 8.1714 ms/op | 1.13 | | computeDeltas 2100000 validators 300 proto nodes | 14.169 ms/op | 12.286 ms/op | 1.15 | | computeDeltas 2100000 validators 1200 proto nodes | 14.717 ms/op | 12.257 ms/op | 1.20 | | computeDeltas 2100000 validators 7200 proto nodes | 14.042 ms/op | 12.514 ms/op | 1.12 | | altair processAttestation - 250000 vs - 7PWei normalcase | 1.3589 ms/op | 1.4322 ms/op | 0.95 | | altair processAttestation - 250000 vs - 7PWei worstcase | 2.1223 ms/op | 2.2491 ms/op | 0.94 | | altair processAttestation - setStatus - 1/6 committees join | 67.409 us/op | 69.400 us/op | 0.97 | | altair processAttestation - setStatus - 1/3 committees join | 127.18 us/op | 140.23 us/op | 0.91 | | altair processAttestation - setStatus - 1/2 committees join | 188.16 us/op | 202.00 us/op | 0.93 | | altair processAttestation - setStatus - 2/3 committees join | 268.66 us/op | 273.30 us/op | 0.98 | | altair processAttestation - setStatus - 4/5 committees join | 392.49 us/op | 404.46 us/op | 0.97 | | altair processAttestation - setStatus - 100% committees join | 458.48 us/op | 488.54 us/op | 0.94 | | altair processBlock - 250000 vs - 7PWei normalcase | 4.1858 ms/op | 3.8838 ms/op | 1.08 | | altair processBlock - 250000 vs - 7PWei normalcase hashState | 24.676 ms/op | 21.932 ms/op | 1.13 | | altair processBlock - 250000 vs - 7PWei worstcase | 44.228 ms/op | 36.544 ms/op | 1.21 | | altair processBlock - 250000 vs - 7PWei worstcase hashState | 72.326 ms/op | 62.966 ms/op | 1.15 | | phase0 processBlock - 250000 vs - 7PWei normalcase | 1.9810 ms/op | 1.5789 ms/op | 1.25 | | phase0 processBlock - 250000 vs - 7PWei worstcase | 23.049 ms/op | 22.862 ms/op | 1.01 | | altair processEth1Data - 250000 vs - 7PWei normalcase | 242.48 us/op | 240.97 us/op | 1.01 | | getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:15 | 4.6460 us/op | 2.7500 us/op | 1.69 | | getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:219 | 16.763 us/op | 10.213 us/op | 1.64 | | getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:42 | 6.4230 us/op | 6.5560 us/op | 0.98 | | getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:18 | 5.0410 us/op | 4.7240 us/op | 1.07 | | getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1020 | 86.065 us/op | 79.541 us/op | 1.08 | | getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11777 | 817.12 us/op | 779.98 us/op | 1.05 | | getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 | 645.25 us/op | 1.0418 ms/op | 0.62 | | getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 | 706.49 us/op | 636.31 us/op | 1.11 | | getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 | 2.0045 ms/op | 1.9680 ms/op | 1.02 | | getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 | 1.1757 ms/op | 1.1005 ms/op | 1.07 | | getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 | 2.8672 ms/op | 3.0118 ms/op | 0.95 | | Tree 40 250000 create | 180.25 ms/op | 179.04 ms/op | 1.01 | | Tree 40 250000 get(125000) | 111.05 ns/op | 103.54 ns/op | 1.07 | | Tree 40 250000 set(125000) | 515.26 ns/op | 545.50 ns/op | 0.94 | | Tree 40 250000 toArray() | 9.7643 ms/op | 13.609 ms/op | 0.72 | | Tree 40 250000 iterate all - toArray() + loop | 9.8709 ms/op | 13.722 ms/op | 0.72 | | Tree 40 250000 iterate all - get(i) | 38.186 ms/op | 42.274 ms/op | 0.90 | | MutableVector 250000 create | 7.9757 ms/op | 9.7735 ms/op | 0.82 | | MutableVector 250000 get(125000) | 5.8370 ns/op | 5.8440 ns/op | 1.00 | | MutableVector 250000 set(125000) | 184.87 ns/op | 158.77 ns/op | 1.16 | | MutableVector 250000 toArray() | 2.6555 ms/op | 2.9122 ms/op | 0.91 | | MutableVector 250000 iterate all - toArray() + loop | 2.7472 ms/op | 2.9661 ms/op | 0.93 | | MutableVector 250000 iterate all - get(i) | 1.4406 ms/op | 1.3940 ms/op | 1.03 | | Array 250000 create | 2.3313 ms/op | 2.7192 ms/op | 0.86 | | Array 250000 clone - spread | 1.3068 ms/op | 1.2656 ms/op | 1.03 | | Array 250000 get(125000) | 0.58200 ns/op | 0.57800 ns/op | 1.01 | | Array 250000 set(125000) | 0.59400 ns/op | 0.58500 ns/op | 1.02 | | Array 250000 iterate all - loop | 77.210 us/op | 75.528 us/op | 1.02 | | effectiveBalanceIncrements clone Uint8Array 300000 | 14.427 us/op | 19.230 us/op | 0.75 | | effectiveBalanceIncrements clone MutableVector 300000 | 316.00 ns/op | 308.00 ns/op | 1.03 | | effectiveBalanceIncrements rw all Uint8Array 300000 | 167.57 us/op | 164.85 us/op | 1.02 | | effectiveBalanceIncrements rw all MutableVector 300000 | 57.806 ms/op | 53.212 ms/op | 1.09 | | phase0 afterProcessEpoch - 250000 vs - 7PWei | 76.153 ms/op | 74.425 ms/op | 1.02 | | phase0 beforeProcessEpoch - 250000 vs - 7PWei | 36.362 ms/op | 40.440 ms/op | 0.90 | | altair processEpoch - mainnet_e81889 | 357.69 ms/op | 346.10 ms/op | 1.03 | | mainnet_e81889 - altair beforeProcessEpoch | 54.019 ms/op | 60.546 ms/op | 0.89 | | mainnet_e81889 - altair processJustificationAndFinalization | 5.5380 us/op | 11.624 us/op | 0.48 | | mainnet_e81889 - altair processInactivityUpdates | 5.4043 ms/op | 4.0399 ms/op | 1.34 | | mainnet_e81889 - altair processRewardsAndPenalties | 54.691 ms/op | 51.193 ms/op | 1.07 | | mainnet_e81889 - altair processRegistryUpdates | 1.8890 us/op | 2.1790 us/op | 0.87 | | mainnet_e81889 - altair processSlashings | 740.00 ns/op | 815.00 ns/op | 0.91 | | mainnet_e81889 - altair processEth1DataReset | 680.00 ns/op | 774.00 ns/op | 0.88 | | mainnet_e81889 - altair processEffectiveBalanceUpdates | 1.2160 ms/op | 1.3026 ms/op | 0.93 | | mainnet_e81889 - altair processSlashingsReset | 1.3140 us/op | 3.0050 us/op | 0.44 | | mainnet_e81889 - altair processRandaoMixesReset | 1.5400 us/op | 3.2870 us/op | 0.47 | | mainnet_e81889 - altair processHistoricalRootsUpdate | 676.00 ns/op | 735.00 ns/op | 0.92 | | mainnet_e81889 - altair processParticipationFlagUpdates | 1.5550 us/op | 1.5150 us/op | 1.03 | | mainnet_e81889 - altair processSyncCommitteeUpdates | 785.00 ns/op | 648.00 ns/op | 1.21 | | mainnet_e81889 - altair afterProcessEpoch | 79.261 ms/op | 78.764 ms/op | 1.01 | | capella processEpoch - mainnet_e217614 | 1.2247 s/op | 1.1442 s/op | 1.07 | | mainnet_e217614 - capella beforeProcessEpoch | 221.02 ms/op | 227.63 ms/op | 0.97 | | mainnet_e217614 - capella processJustificationAndFinalization | 5.3830 us/op | 5.5700 us/op | 0.97 | | mainnet_e217614 - capella processInactivityUpdates | 17.381 ms/op | 14.328 ms/op | 1.21 | | mainnet_e217614 - capella processRewardsAndPenalties | 268.51 ms/op | 265.14 ms/op | 1.01 | | mainnet_e217614 - capella processRegistryUpdates | 10.788 us/op | 11.808 us/op | 0.91 | | mainnet_e217614 - capella processSlashings | 817.00 ns/op | 765.00 ns/op | 1.07 | | mainnet_e217614 - capella processEth1DataReset | 728.00 ns/op | 680.00 ns/op | 1.07 | | mainnet_e217614 - capella processEffectiveBalanceUpdates | 5.2249 ms/op | 2.8255 ms/op | 1.85 | | mainnet_e217614 - capella processSlashingsReset | 1.4740 us/op | 2.4590 us/op | 0.60 | | mainnet_e217614 - capella processRandaoMixesReset | 2.7660 us/op | 2.7040 us/op | 1.02 | | mainnet_e217614 - capella processHistoricalRootsUpdate | 688.00 ns/op | 671.00 ns/op | 1.03 | | mainnet_e217614 - capella processParticipationFlagUpdates | 1.3730 us/op | 1.7060 us/op | 0.80 | | mainnet_e217614 - capella afterProcessEpoch | 238.29 ms/op | 244.86 ms/op | 0.97 | | phase0 processEpoch - mainnet_e58758 | 361.06 ms/op | 341.61 ms/op | 1.06 | | mainnet_e58758 - phase0 beforeProcessEpoch | 106.43 ms/op | 94.743 ms/op | 1.12 | | mainnet_e58758 - phase0 processJustificationAndFinalization | 10.927 us/op | 10.869 us/op | 1.01 | | mainnet_e58758 - phase0 processRewardsAndPenalties | 32.733 ms/op | 31.338 ms/op | 1.04 | | mainnet_e58758 - phase0 processRegistryUpdates | 6.6320 us/op | 6.0750 us/op | 1.09 | | mainnet_e58758 - phase0 processSlashings | 766.00 ns/op | 781.00 ns/op | 0.98 | | mainnet_e58758 - phase0 processEth1DataReset | 728.00 ns/op | 697.00 ns/op | 1.04 | | mainnet_e58758 - phase0 processEffectiveBalanceUpdates | 1.1069 ms/op | 1.0692 ms/op | 1.04 | | mainnet_e58758 - phase0 processSlashingsReset | 2.1290 us/op | 2.2670 us/op | 0.94 | | mainnet_e58758 - phase0 processRandaoMixesReset | 2.5120 us/op | 2.7120 us/op | 0.93 | | mainnet_e58758 - phase0 processHistoricalRootsUpdate | 1.4050 us/op | 677.00 ns/op | 2.08 | | mainnet_e58758 - phase0 processParticipationRecordUpdates | 2.7740 us/op | 2.3590 us/op | 1.18 | | mainnet_e58758 - phase0 afterProcessEpoch | 63.054 ms/op | 65.384 ms/op | 0.96 | | phase0 processEffectiveBalanceUpdates - 250000 normalcase | 761.61 us/op | 1.3128 ms/op | 0.58 | | phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 | 1.2947 ms/op | 1.3210 ms/op | 0.98 | | altair processInactivityUpdates - 250000 normalcase | 17.699 ms/op | 16.147 ms/op | 1.10 | | altair processInactivityUpdates - 250000 worstcase | 16.572 ms/op | 18.938 ms/op | 0.88 | | phase0 processRegistryUpdates - 250000 normalcase | 3.0570 us/op | 3.4080 us/op | 0.90 | | phase0 processRegistryUpdates - 250000 badcase_full_deposits | 290.09 us/op | 301.32 us/op | 0.96 | | phase0 processRegistryUpdates - 250000 worstcase 0.5 | 110.48 ms/op | 104.20 ms/op | 1.06 | | altair processRewardsAndPenalties - 250000 normalcase | 41.467 ms/op | 40.430 ms/op | 1.03 | | altair processRewardsAndPenalties - 250000 worstcase | 47.264 ms/op | 37.722 ms/op | 1.25 | | phase0 getAttestationDeltas - 250000 normalcase | 5.7137 ms/op | 5.9492 ms/op | 0.96 | | phase0 getAttestationDeltas - 250000 worstcase | 5.6545 ms/op | 6.2687 ms/op | 0.90 | | phase0 processSlashings - 250000 worstcase | 81.691 us/op | 83.685 us/op | 0.98 | | altair processSyncCommitteeUpdates - 250000 | 102.59 ms/op | 97.642 ms/op | 1.05 | | BeaconState.hashTreeRoot - No change | 455.00 ns/op | 491.00 ns/op | 0.93 | | BeaconState.hashTreeRoot - 1 full validator | 145.62 us/op | 117.68 us/op | 1.24 | | BeaconState.hashTreeRoot - 32 full validator | 1.5032 ms/op | 874.94 us/op | 1.72 | | BeaconState.hashTreeRoot - 512 full validator | 10.453 ms/op | 15.227 ms/op | 0.69 | | BeaconState.hashTreeRoot - 1 validator.effectiveBalance | 147.32 us/op | 134.56 us/op | 1.09 | | BeaconState.hashTreeRoot - 32 validator.effectiveBalance | 1.7651 ms/op | 1.6311 ms/op | 1.08 | | BeaconState.hashTreeRoot - 512 validator.effectiveBalance | 23.181 ms/op | 24.149 ms/op | 0.96 | | BeaconState.hashTreeRoot - 1 balances | 121.69 us/op | 104.35 us/op | 1.17 | | BeaconState.hashTreeRoot - 32 balances | 1.0305 ms/op | 1.2897 ms/op | 0.80 | | BeaconState.hashTreeRoot - 512 balances | 11.746 ms/op | 11.950 ms/op | 0.98 | | BeaconState.hashTreeRoot - 250000 balances | 185.11 ms/op | 171.18 ms/op | 1.08 | | aggregationBits - 2048 els - zipIndexesInBitList | 19.074 us/op | 19.354 us/op | 0.99 | | byteArrayEquals 32 | 48.363 ns/op | 48.244 ns/op | 1.00 | | Buffer.compare 32 | 15.765 ns/op | 15.508 ns/op | 1.02 | | byteArrayEquals 1024 | 1.2826 us/op | 1.2712 us/op | 1.01 | | Buffer.compare 1024 | 24.609 ns/op | 24.251 ns/op | 1.01 | | byteArrayEquals 16384 | 20.345 us/op | 20.010 us/op | 1.02 | | Buffer.compare 16384 | 202.16 ns/op | 198.87 ns/op | 1.02 | | byteArrayEquals 123687377 | 152.19 ms/op | 146.27 ms/op | 1.04 | | Buffer.compare 123687377 | 4.6268 ms/op | 4.2791 ms/op | 1.08 | | byteArrayEquals 32 - diff last byte | 46.803 ns/op | 46.886 ns/op | 1.00 | | Buffer.compare 32 - diff last byte | 16.716 ns/op | 15.872 ns/op | 1.05 | | byteArrayEquals 1024 - diff last byte | 1.2508 us/op | 1.2452 us/op | 1.00 | | Buffer.compare 1024 - diff last byte | 23.361 ns/op | 24.209 ns/op | 0.96 | | byteArrayEquals 16384 - diff last byte | 19.750 us/op | 19.809 us/op | 1.00 | | Buffer.compare 16384 - diff last byte | 202.32 ns/op | 199.70 ns/op | 1.01 | | byteArrayEquals 123687377 - diff last byte | 146.40 ms/op | 148.46 ms/op | 0.99 | | Buffer.compare 123687377 - diff last byte | 5.2883 ms/op | 3.6926 ms/op | 1.43 | | byteArrayEquals 32 - random bytes | 4.5350 ns/op | 4.9590 ns/op | 0.91 | | Buffer.compare 32 - random bytes | 14.564 ns/op | 16.340 ns/op | 0.89 | | byteArrayEquals 1024 - random bytes | 4.4910 ns/op | 4.9280 ns/op | 0.91 | | Buffer.compare 1024 - random bytes | 14.506 ns/op | 16.660 ns/op | 0.87 | | byteArrayEquals 16384 - random bytes | 4.5990 ns/op | 4.9290 ns/op | 0.93 | | Buffer.compare 16384 - random bytes | 14.403 ns/op | 16.627 ns/op | 0.87 | | byteArrayEquals 123687377 - random bytes | 7.2700 ns/op | 7.8400 ns/op | 0.93 | | Buffer.compare 123687377 - random bytes | 17.210 ns/op | 19.700 ns/op | 0.87 | | regular array get 100000 times | 28.214 us/op | 30.840 us/op | 0.91 | | wrappedArray get 100000 times | 28.251 us/op | 30.812 us/op | 0.92 | | arrayWithProxy get 100000 times | 8.6148 ms/op | 10.237 ms/op | 0.84 | | ssz.Root.equals | 39.824 ns/op | 43.771 ns/op | 0.91 | | byteArrayEquals | 39.726 ns/op | 38.781 ns/op | 1.02 | | Buffer.compare | 8.4550 ns/op | 9.4380 ns/op | 0.90 | | shuffle list - 16384 els | 5.5247 ms/op | 4.9618 ms/op | 1.11 | | shuffle list - 250000 els | 81.361 ms/op | 80.669 ms/op | 1.01 | | processSlot - 1 slots | 11.706 us/op | 12.990 us/op | 0.90 | | processSlot - 32 slots | 2.5484 ms/op | 3.0961 ms/op | 0.82 | | getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei | 42.565 ms/op | 41.978 ms/op | 1.01 | | getCommitteeAssignments - req 1 vs - 250000 vc | 1.7170 ms/op | 1.6444 ms/op | 1.04 | | getCommitteeAssignments - req 100 vs - 250000 vc | 3.3674 ms/op | 3.2276 ms/op | 1.04 | | getCommitteeAssignments - req 1000 vs - 250000 vc | 3.7608 ms/op | 3.4782 ms/op | 1.08 | | findModifiedValidators - 10000 modified validators | 223.74 ms/op | 238.91 ms/op | 0.94 | | findModifiedValidators - 1000 modified validators | 142.67 ms/op | 157.31 ms/op | 0.91 | | findModifiedValidators - 100 modified validators | 170.57 ms/op | 157.65 ms/op | 1.08 | | findModifiedValidators - 10 modified validators | 134.58 ms/op | 149.18 ms/op | 0.90 | | findModifiedValidators - 1 modified validators | 154.09 ms/op | 137.60 ms/op | 1.12 | | findModifiedValidators - no difference | 150.47 ms/op | 139.36 ms/op | 1.08 | | compare ViewDUs | 3.0622 s/op | 3.2588 s/op | 0.94 | | compare each validator Uint8Array | 1.4644 s/op | 1.4972 s/op | 0.98 | | compare ViewDU to Uint8Array | 708.05 ms/op | 635.00 ms/op | 1.12 | | migrate state 1000000 validators, 24 modified, 0 new | 588.02 ms/op | 554.46 ms/op | 1.06 | | migrate state 1000000 validators, 1700 modified, 1000 new | 811.67 ms/op | 750.82 ms/op | 1.08 | | migrate state 1000000 validators, 3400 modified, 2000 new | 1.0254 s/op | 900.88 ms/op | 1.14 | | migrate state 1500000 validators, 24 modified, 0 new | 569.97 ms/op | 574.88 ms/op | 0.99 | | migrate state 1500000 validators, 1700 modified, 1000 new | 813.85 ms/op | 661.34 ms/op | 1.23 | | migrate state 1500000 validators, 3400 modified, 2000 new | 1.0297 s/op | 845.59 ms/op | 1.22 | | RootCache.getBlockRootAtSlot - 250000 vs - 7PWei | 6.0000 ns/op | 6.3800 ns/op | 0.94 | | state getBlockRootAtSlot - 250000 vs - 7PWei | 815.88 ns/op | 715.69 ns/op | 1.14 | | computeProposers - vc 250000 | 5.7327 ms/op | 6.0927 ms/op | 0.94 | | computeEpochShuffling - vc 250000 | 81.767 ms/op | 82.161 ms/op | 1.00 | | getNextSyncCommittee - vc 250000 | 108.11 ms/op | 94.633 ms/op | 1.14 | | computeSigningRoot for AttestationData | 24.856 us/op | 14.059 us/op | 1.77 | | hash AttestationData serialized data then Buffer.toString(base64) | 1.1939 us/op | 1.1662 us/op | 1.02 | | toHexString serialized data | 783.04 ns/op | 774.53 ns/op | 1.01 | | Buffer.toString(base64) | 141.60 ns/op | 127.92 ns/op | 1.11 |

by benchmarkbot/action

codecov[bot] commented 3 weeks ago

Codecov Report

Attention: Patch coverage is 81.70895% with 137 lines in your changes missing coverage. Please review.

Project coverage is 62.56%. Comparing base (a2c389f) to head (536d8c9).

Additional details and impacted files ```diff @@ Coverage Diff @@ ## unstable #6825 +/- ## ============================================ - Coverage 62.67% 62.56% -0.11% ============================================ Files 578 575 -3 Lines 61220 61066 -154 Branches 2113 2116 +3 ============================================ - Hits 38367 38207 -160 - Misses 22815 22820 +5 - Partials 38 39 +1 ```
nazarhussain commented 3 weeks ago

Converted to draft to resolve huge conflicts after #6749 merged.

nazarhussain commented 2 weeks ago

The Motviation of this PR is not obvious to me

It was much clearly explained in the presentation.

Looking at the diff, I don't see a single line where it simplified things or removed unsafe type casts (it rather added more). The semantics are mostly the same, we still use "all forks" for types that do not exist on all forks. I don't think that's a big issue though, I always though of "all forks" as "all supported forks" anyways.

We had the usage of allForks contextually wrong on most of the places. e.g. If we refer allForks which are defined under ForkName enum, then allForks.LightClientUpdate is a wrong type as phase0 does not have this type. What we intended here is the LightClientUpdate only for forks which had light-client support. So instead of referring allForks.LightClientUpdate it's much clear to specify it as LightClientUpdate<ForkLightClient>.

we still use "all forks" for types that do not exist on all forks.

It is not possible because there is no name-space allForks anymore. Either you can use directly from fork specific type phase0, altair or you use utility types exposed directly form root of package. If you are referring to the SSZ Types, that will be cleanup up as well in upcoming PR as explained in presentation.

I don't see a single line where it simplified things or removed unsafe type casts

If you observe where we are passing fork collection to generics are all unsafe types referred via allForks. We didn't encountered type error before because of the structural typing of TS and boilerplate code in the root export where we filter every type explicitly for allForks namespace.

The extra type machinery required looks scary to maintain, hoping this just works and does not require type debugging in the future.

I don't see it scary, it's simple generic types we used everywhere. During this PR I faced some issues which were mostly assigning BeaconBlock<ForkAll> to where BeaconBlock<ForkExecution> were expected. So I feel this helps me identify the problems much better.

Would be great to get more examples on type simplification we plan with this in the future to get a better picture of the whole solution, as I understood from the presentation there are 3 stages.

Once you start using these generics types, you will find these more flexible and useful. Anyhow have plans to add docs guiding steps to add more generic types, or adding new forks.

Is this in any way also related to https://github.com/ChainSafe/lodestar/issues/6799?

Yes this refactoring will facilitate to have more typesafe code for upcoming forks.

nflaig commented 2 weeks ago

It was much clearly explained in the presentation.

We should document the main points in the motivation of the PR. I tried to rewatch the recording but it seems to be no longer accessible. Or at least share the slides via link, and reference in PR.

It is not possible because there is no name-space allForks anymore

this is what I don't quite get, we are still doing this, but instead of allForks namespace we now pass ForkAll but isn't this the same?

e.g. previous

allForks.SignedBlindedBeaconBlock;

and now

SignedBeaconBlock<ForkAll, "blinded">;

what's the difference between those two? we still claim that blinded beacon block exists on all forks but that's not the case

wemeetagain commented 2 weeks ago

I think the main benefit we get is more expressiveness wrt features that exist in subsets of all forks. If we're blindly transliterating from what we have, that means a lot of Type<ForkAll>. But I think this will be helpful as the # of forks keeps proliferating.

SignedBeaconBlock<ForkAll, "blinded">;

Maybe worth considering just having a single generic param for all generic types here.

so, BeaconBlock, BlindedBeaconBlock, and FullOrBlindedBeaconBlock can be all separate types.

This may be easier devex, since we won't have to remember which generic types have 2 or more params, and which possibilities exist. Rather, these generic types will have a single generic param for the fork.

nazarhussain commented 2 weeks ago

what's the difference between those two? we still claim that blinded beacon block exists on all forks but that's not the case

The difference of allForks.SignedBlindedBeaconBlock vs SignedBeaconBlock<ForkAll, "blinded"> is very clear by itself and explained with the PR title, use forks as generics.

Now if question you are referring why generics, that I explained in my earlier comment as well. The usage of allForks is contextually wrong in many places as some types does not belongs to all forks. Also when comes to generics, it's obvious we can pass those as parameters.

As we add more forks it will be unmanageable to refer those via allForks, rather we intended to refer types with respective to different categories of forks by feature e.g. ForkExecution or ForkBlobs. Having this flexibility is not possible via allForks namespace usage.

nflaig commented 2 weeks ago

The difference of allForks.SignedBlindedBeaconBlock vs SignedBeaconBlock<ForkAll, "blinded"> is very clear by itself and explained with the PR title, use forks as generics. Now if question you are referring why generics,

I am not asking about why generics vs. namespaces, besides the additional type complexity making them work, the overall ergonomics of the usage looks good to me.

The usage of allForks is contextually wrong in many places as some types does not belongs to all forks

I agree with this but I am confused why we still do

SignedBeaconBlock<ForkAll, "blinded">; // <-- this shouldn't even be allowed, fork must be subset of ForkExecution

instead of

SignedBeaconBlock<ForkExecution, "blinded">;

The code contradicts with what you are saying

nflaig commented 2 weeks ago

This may be easier devex, since we won't have to remember which generic types have 2 or more params

What was the reason for combining blinded and full block? Do we have some examples where this improves type handling?

so, BeaconBlock, BlindedBeaconBlock, and FullOrBlindedBeaconBlock can be all separate types.

I like it more, it's more expressive and would be more inline with current types

wemeetagain commented 2 weeks ago

What was the reason for combining blinded and full block? Do we have some examples where this improves type handling?

I guess the reason is that these variants blow up the # of types and that generally the variants can be used interchangeably (and if not, the type will be narrowed to show that). If the default is 'all variants', then we will either automatically support all variants, or the type will be explicitly narrowed to suit. For example, if SignedBeaconBlock is both FullOrBlinded by default, then more functions would automatically support both full and blinded signed blocks. The use of generics just made it more concise and highlighted the point that "there is just one type, but with several variants".

so, BeaconBlock, BlindedBeaconBlock, and FullOrBlindedBeaconBlock can be all separate types.

I like it more, it's more expressive and would be more inline with current types

Yea I don't really have a strong opinion about this point in particular. I think just using the generics for specifying the fork will already be a big win long term.

nflaig commented 2 weeks ago

I guess the reason is that these variants blow up the # of types and that generally the variants can be used interchangeably (and if not, the type will be narrowed to show that). If the default is 'all variants', then we will either automatically support all variants, or the type will be explicitly narrowed to suit. For example, if SignedBeaconBlock is both FullOrBlinded by default, then more functions would automatically support both full and blinded signed blocks. The use of generics just made it more concise and highlighted the point that "there is just one type, but with several variants".

They can be used interchangeably in a lot of places but not all, e.g. consider produceBlockV3, it either returns just a blinded block, or a full block as part of block contents. Especially since deneb, a full block has additional implications in some places due to attached sidecars like blobs / kzg proofs while a blinded block won't have that.

Either way, we could focus this PR on just the fork type as generics part and apply other changes later.

I think just using the generics for specifying the fork will already be a big win long term.

yeah, I like the generics, the fact that the type guards in packages/types/src/utils/typeguards.ts can also keep context of the fork is a good indicator that the design makes sense.

nazarhussain commented 2 weeks ago

But can we please not approach the type refactor with the mindset that type casts don't reduce type safety unless you use as unknown..#6825 (comment)

That's not a mindset that's how TS works. See the code example below.

function mul(val: number | string, factor: number): number | string {
  if(Number(val) === val) {
    return val * factor;
  }

  return repeat(val as string, factor);
}

function repeat(val: string, factor: number): string {
   // some code
}

We have to use as string here because typescript could not infer the correct type here. Such situations could be avoided by having type-guards everywhere, but that's not what is a best practice either. And type-guards does not work well on the nested properties, so in such situations you have to infer the correct type yourself.

There are some coding patterns to avoid such situations but that's out of scope of this PR.

nazarhussain commented 2 weeks ago

I agree with this but I am confused why we still do

SignedBeaconBlock<ForkAll, "blinded">; // <-- this shouldn't even be allowed, fork must be subset of ForkExecution

instead of

SignedBeaconBlock<ForkExecution, "blinded">;

The code contradicts with what you are saying

Seems you commented without actually experience the change. Now branch status is changed a lot but even earlier above SignedBeaconBlock<ForkAll, "blinded"> was not allowed will give you type error.

nflaig commented 2 weeks ago

Seems you commented without actually experience the change.

yes, looks good now. This comment was done on an older state of the branch.

That's not a mindset that's how TS works. See the code example below.

How is this example relevant? Of course there are situations where type-casts are fine but claiming those do not reduce type safety unless you use as unknown is just a false claim. Please see my earlier example https://github.com/ChainSafe/lodestar/pull/6825#discussion_r1638468234.

nflaig commented 2 weeks ago

Curious if you have explored type inference for the cases were we use the object itself to determine the fork.

e.g. in packages/state-transition/src/signatureSets/index.ts

 // fork based validations
  const fork = state.config.getForkSeq(signedBlock.message.slot);

  // Only after altair fork, validate tSyncCommitteeSignature
  if (fork >= ForkSeq.altair) {
    const syncCommitteeSignatureSet = getSyncCommitteeSignatureSet(
      state as CachedBeaconStateAltair,
      (signedBlock as altair.SignedBeaconBlock).message // <-- would be nice if it could infer altair here as we check fork above
    );
    // There may be no participants in this syncCommitteeSignature, so it must not be validated
    if (syncCommitteeSignatureSet) {
      signatureSets.push(syncCommitteeSignatureSet);
    }
  }

  // only after capella fork
  if (fork >= ForkSeq.capella) {
    const blsToExecutionChangeSignatureSets = getBlsToExecutionChangeSignatureSets(
      state.config,
      signedBlock as capella.SignedBeaconBlock // <-- same here
    );
    if (blsToExecutionChangeSignatureSets.length > 0) {
      signatureSets.push(...blsToExecutionChangeSignatureSets);
    }
  }

We do a lot of those type casts in different places, while the type casts are safe here would be nice to have. This was definitely impossible before (without generics) could work now...assuming typescript is smart enough but we would likely need a wrapped object like this {fork: F, signedBlock: SignedBeaconBlock<F>}