Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add syncing_status query parameter to eth/v1/node/health #5790

Merged
merged 12 commits into from
Aug 1, 2023

Conversation

nflaig
Copy link
Member

@nflaig nflaig commented Jul 23, 2023

Motivation

Closes #5702

Description

  • Adds syncing_status query parameter to eth/v1/node/health
  • Fixes current issue of always returning 200 status code irrespective of node sync status
  • Returns status code based on node sync status
    • 200: Node is ready
    • 206: Node is syncing but can serve incomplete data
    • 100-599: Customized syncing status while node is syncing
    • 400: Invalid syncing status code provided in query parameter
    • 503: Lodestar does not start its API until fully initialized, so this status can never be served

@nflaig nflaig requested a review from a team as a code owner July 23, 2023 18:07
@github-actions
Copy link
Contributor

github-actions bot commented Jul 23, 2023

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: 32b5ac1 Previous: 19b723c Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 778.49 us/op 772.23 us/op 1.01
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 73.702 us/op 75.456 us/op 0.98
BLS verify - blst-native 1.2008 ms/op 1.2092 ms/op 0.99
BLS verifyMultipleSignatures 3 - blst-native 2.4445 ms/op 2.4736 ms/op 0.99
BLS verifyMultipleSignatures 8 - blst-native 5.2414 ms/op 5.3734 ms/op 0.98
BLS verifyMultipleSignatures 32 - blst-native 18.940 ms/op 19.118 ms/op 0.99
BLS aggregatePubkeys 32 - blst-native 25.068 us/op 25.582 us/op 0.98
BLS aggregatePubkeys 128 - blst-native 98.517 us/op 99.168 us/op 0.99
getAttestationsForBlock 49.059 ms/op 50.251 ms/op 0.98
isKnown best case - 1 super set check 265.00 ns/op 271.00 ns/op 0.98
isKnown normal case - 2 super set checks 249.00 ns/op 263.00 ns/op 0.95
isKnown worse case - 16 super set checks 257.00 ns/op 284.00 ns/op 0.90
CheckpointStateCache - add get delete 4.7770 us/op 4.7000 us/op 1.02
validate api signedAggregateAndProof - struct 2.7512 ms/op 2.7113 ms/op 1.01
validate gossip signedAggregateAndProof - struct 2.7542 ms/op 2.7131 ms/op 1.02
validate api attestation - struct 1.3156 ms/op 1.2954 ms/op 1.02
validate gossip attestation - struct 1.3487 ms/op 1.3149 ms/op 1.03
pickEth1Vote - no votes 1.1148 ms/op 1.1595 ms/op 0.96
pickEth1Vote - max votes 8.9618 ms/op 10.659 ms/op 0.84
pickEth1Vote - Eth1Data hashTreeRoot value x2048 8.3322 ms/op 8.5414 ms/op 0.98
pickEth1Vote - Eth1Data hashTreeRoot tree x2048 13.676 ms/op 14.207 ms/op 0.96
pickEth1Vote - Eth1Data fastSerialize value x2048 553.64 us/op 562.29 us/op 0.98
pickEth1Vote - Eth1Data fastSerialize tree x2048 5.8666 ms/op 6.5854 ms/op 0.89
bytes32 toHexString 476.00 ns/op 486.00 ns/op 0.98
bytes32 Buffer.toString(hex) 297.00 ns/op 294.00 ns/op 1.01
bytes32 Buffer.toString(hex) from Uint8Array 438.00 ns/op 430.00 ns/op 1.02
bytes32 Buffer.toString(hex) + 0x 300.00 ns/op 298.00 ns/op 1.01
Object access 1 prop 0.15500 ns/op 0.15700 ns/op 0.99
Map access 1 prop 0.14800 ns/op 0.14300 ns/op 1.03
Object get x1000 7.4370 ns/op 7.7880 ns/op 0.95
Map get x1000 0.61700 ns/op 0.51400 ns/op 1.20
Object set x1000 48.711 ns/op 48.961 ns/op 0.99
Map set x1000 38.566 ns/op 38.750 ns/op 1.00
Return object 10000 times 0.22990 ns/op 0.23430 ns/op 0.98
Throw Error 10000 times 3.7994 us/op 3.8302 us/op 0.99
fastMsgIdFn sha256 / 200 bytes 3.2200 us/op 3.3130 us/op 0.97
fastMsgIdFn h32 xxhash / 200 bytes 266.00 ns/op 285.00 ns/op 0.93
fastMsgIdFn h64 xxhash / 200 bytes 340.00 ns/op 339.00 ns/op 1.00
fastMsgIdFn sha256 / 1000 bytes 11.046 us/op 11.251 us/op 0.98
fastMsgIdFn h32 xxhash / 1000 bytes 400.00 ns/op 414.00 ns/op 0.97
fastMsgIdFn h64 xxhash / 1000 bytes 402.00 ns/op 413.00 ns/op 0.97
fastMsgIdFn sha256 / 10000 bytes 101.02 us/op 103.33 us/op 0.98
fastMsgIdFn h32 xxhash / 10000 bytes 1.8640 us/op 1.9110 us/op 0.98
fastMsgIdFn h64 xxhash / 10000 bytes 1.2700 us/op 1.2980 us/op 0.98
enrSubnets - fastDeserialize 64 bits 1.2130 us/op 1.2210 us/op 0.99
enrSubnets - ssz BitVector 64 bits 418.00 ns/op 417.00 ns/op 1.00
enrSubnets - fastDeserialize 4 bits 172.00 ns/op 169.00 ns/op 1.02
enrSubnets - ssz BitVector 4 bits 426.00 ns/op 419.00 ns/op 1.02
prioritizePeers score -10:0 att 32-0.1 sync 2-0 99.985 us/op 96.667 us/op 1.03
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 124.49 us/op 132.24 us/op 0.94
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 160.35 us/op 165.05 us/op 0.97
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 276.09 us/op 288.00 us/op 0.96
prioritizePeers score 0:0 att 64-1 sync 4-1 333.30 us/op 335.54 us/op 0.99
array of 16000 items push then shift 1.5770 us/op 1.5907 us/op 0.99
LinkedList of 16000 items push then shift 8.8180 ns/op 8.9420 ns/op 0.99
array of 16000 items push then pop 55.165 ns/op 50.794 ns/op 1.09
LinkedList of 16000 items push then pop 8.4590 ns/op 8.4350 ns/op 1.00
array of 24000 items push then shift 2.3398 us/op 2.3434 us/op 1.00
LinkedList of 24000 items push then shift 8.6520 ns/op 8.5630 ns/op 1.01
array of 24000 items push then pop 91.513 ns/op 68.658 ns/op 1.33
LinkedList of 24000 items push then pop 8.4690 ns/op 8.5060 ns/op 1.00
intersect bitArray bitLen 8 6.6350 ns/op 6.7400 ns/op 0.98
intersect array and set length 8 56.223 ns/op 57.759 ns/op 0.97
intersect bitArray bitLen 128 31.647 ns/op 31.584 ns/op 1.00
intersect array and set length 128 758.41 ns/op 784.92 ns/op 0.97
bitArray.getTrueBitIndexes() bitLen 128 1.6410 us/op 1.4950 us/op 1.10
bitArray.getTrueBitIndexes() bitLen 248 2.7360 us/op 2.5520 us/op 1.07
bitArray.getTrueBitIndexes() bitLen 512 5.2460 us/op 4.8640 us/op 1.08
Buffer.concat 32 items 1.0480 us/op 1.0250 us/op 1.02
Uint8Array.set 32 items 1.9100 us/op 1.8930 us/op 1.01
transfer serialized Status (84 B) 1.8870 us/op 1.8460 us/op 1.02
copy serialized Status (84 B) 1.5640 us/op 1.5110 us/op 1.04
transfer serialized SignedVoluntaryExit (112 B) 1.9510 us/op 1.9650 us/op 0.99
copy serialized SignedVoluntaryExit (112 B) 1.6860 us/op 1.5820 us/op 1.07
transfer serialized ProposerSlashing (416 B) 2.4660 us/op 2.3650 us/op 1.04
copy serialized ProposerSlashing (416 B) 2.2640 us/op 2.2130 us/op 1.02
transfer serialized Attestation (485 B) 3.0120 us/op 2.3860 us/op 1.26
copy serialized Attestation (485 B) 2.2590 us/op 2.1920 us/op 1.03
transfer serialized AttesterSlashing (33232 B) 2.4740 us/op 2.5160 us/op 0.98
copy serialized AttesterSlashing (33232 B) 5.1560 us/op 4.6540 us/op 1.11
transfer serialized Small SignedBeaconBlock (128000 B) 2.9150 us/op 2.5950 us/op 1.12
copy serialized Small SignedBeaconBlock (128000 B) 11.955 us/op 11.326 us/op 1.06
transfer serialized Avg SignedBeaconBlock (200000 B) 3.1100 us/op 2.8410 us/op 1.09
copy serialized Avg SignedBeaconBlock (200000 B) 18.106 us/op 16.421 us/op 1.10
transfer serialized BlobsSidecar (524380 B) 2.9730 us/op 2.6100 us/op 1.14
copy serialized BlobsSidecar (524380 B) 75.694 us/op 112.55 us/op 0.67
transfer serialized Big SignedBeaconBlock (1000000 B) 2.9740 us/op 2.6740 us/op 1.11
copy serialized Big SignedBeaconBlock (1000000 B) 148.05 us/op 143.95 us/op 1.03
pass gossip attestations to forkchoice per slot 2.1030 ms/op 2.0369 ms/op 1.03
forkChoice updateHead vc 100000 bc 64 eq 0 2.0798 ms/op 2.0181 ms/op 1.03
forkChoice updateHead vc 600000 bc 64 eq 0 12.142 ms/op 13.460 ms/op 0.90
forkChoice updateHead vc 1000000 bc 64 eq 0 18.077 ms/op 21.844 ms/op 0.83
forkChoice updateHead vc 600000 bc 320 eq 0 17.452 ms/op 17.198 ms/op 1.01
forkChoice updateHead vc 600000 bc 1200 eq 0 79.545 ms/op 80.755 ms/op 0.99
forkChoice updateHead vc 600000 bc 64 eq 1000 18.293 ms/op 22.175 ms/op 0.82
forkChoice updateHead vc 600000 bc 64 eq 10000 20.281 ms/op 24.339 ms/op 0.83
forkChoice updateHead vc 600000 bc 64 eq 300000 28.161 ms/op 29.180 ms/op 0.97
computeDeltas 2.9580 ms/op 2.9503 ms/op 1.00
computeProposerBoostScoreFromBalances 378.14 us/op 384.86 us/op 0.98
altair processAttestation - 250000 vs - 7PWei normalcase 2.0398 ms/op 2.0761 ms/op 0.98
altair processAttestation - 250000 vs - 7PWei worstcase 3.0740 ms/op 3.1726 ms/op 0.97
altair processAttestation - setStatus - 1/6 committees join 194.14 us/op 238.06 us/op 0.82
altair processAttestation - setStatus - 1/3 committees join 364.85 us/op 416.74 us/op 0.88
altair processAttestation - setStatus - 1/2 committees join 503.55 us/op 593.36 us/op 0.85
altair processAttestation - setStatus - 2/3 committees join 565.45 us/op 754.87 us/op 0.75
altair processAttestation - setStatus - 4/5 committees join 852.45 us/op 991.22 us/op 0.86
altair processAttestation - setStatus - 100% committees join 994.90 us/op 1.1930 ms/op 0.83
altair processBlock - 250000 vs - 7PWei normalcase 8.9823 ms/op 9.2007 ms/op 0.98
altair processBlock - 250000 vs - 7PWei normalcase hashState 16.079 ms/op 16.968 ms/op 0.95
altair processBlock - 250000 vs - 7PWei worstcase 36.907 ms/op 36.412 ms/op 1.01
altair processBlock - 250000 vs - 7PWei worstcase hashState 57.373 ms/op 57.850 ms/op 0.99
phase0 processBlock - 250000 vs - 7PWei normalcase 1.9420 ms/op 1.9233 ms/op 1.01
phase0 processBlock - 250000 vs - 7PWei worstcase 28.080 ms/op 27.850 ms/op 1.01
altair processEth1Data - 250000 vs - 7PWei normalcase 422.82 us/op 487.17 us/op 0.87
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:15 8.2480 us/op 9.1750 us/op 0.90
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:219 53.134 us/op 49.336 us/op 1.08
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:42 18.391 us/op 15.560 us/op 1.18
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:18 9.5360 us/op 7.1280 us/op 1.34
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1020 160.81 us/op 126.64 us/op 1.27
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11777 1.3553 ms/op 1.0489 ms/op 1.29
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.5613 ms/op 1.4673 ms/op 1.06
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.5674 ms/op 1.4084 ms/op 1.11
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.0858 ms/op 2.8806 ms/op 1.07
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.8408 ms/op 2.3301 ms/op 1.22
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.9162 ms/op 4.9232 ms/op 1.00
Tree 40 250000 create 320.30 ms/op 291.91 ms/op 1.10
Tree 40 250000 get(125000) 192.86 ns/op 192.20 ns/op 1.00
Tree 40 250000 set(125000) 848.41 ns/op 840.51 ns/op 1.01
Tree 40 250000 toArray() 16.794 ms/op 17.658 ms/op 0.95
Tree 40 250000 iterate all - toArray() + loop 16.702 ms/op 18.109 ms/op 0.92
Tree 40 250000 iterate all - get(i) 64.472 ms/op 65.648 ms/op 0.98
MutableVector 250000 create 9.4063 ms/op 11.733 ms/op 0.80
MutableVector 250000 get(125000) 6.4150 ns/op 6.4960 ns/op 0.99
MutableVector 250000 set(125000) 247.87 ns/op 252.85 ns/op 0.98
MutableVector 250000 toArray() 2.7273 ms/op 2.7413 ms/op 0.99
MutableVector 250000 iterate all - toArray() + loop 2.8876 ms/op 2.9264 ms/op 0.99
MutableVector 250000 iterate all - get(i) 1.5020 ms/op 1.5347 ms/op 0.98
Array 250000 create 2.5450 ms/op 2.5461 ms/op 1.00
Array 250000 clone - spread 1.2094 ms/op 1.1248 ms/op 1.08
Array 250000 get(125000) 0.58500 ns/op 0.55600 ns/op 1.05
Array 250000 set(125000) 0.66000 ns/op 0.62900 ns/op 1.05
Array 250000 iterate all - loop 81.355 us/op 79.735 us/op 1.02
effectiveBalanceIncrements clone Uint8Array 300000 27.055 us/op 25.705 us/op 1.05
effectiveBalanceIncrements clone MutableVector 300000 366.00 ns/op 335.00 ns/op 1.09
effectiveBalanceIncrements rw all Uint8Array 300000 176.19 us/op 173.48 us/op 1.02
effectiveBalanceIncrements rw all MutableVector 300000 81.507 ms/op 78.684 ms/op 1.04
phase0 afterProcessEpoch - 250000 vs - 7PWei 112.12 ms/op 112.18 ms/op 1.00
phase0 beforeProcessEpoch - 250000 vs - 7PWei 31.340 ms/op 30.511 ms/op 1.03
altair processEpoch - mainnet_e81889 295.68 ms/op 315.03 ms/op 0.94
mainnet_e81889 - altair beforeProcessEpoch 58.074 ms/op 58.395 ms/op 0.99
mainnet_e81889 - altair processJustificationAndFinalization 13.408 us/op 13.324 us/op 1.01
mainnet_e81889 - altair processInactivityUpdates 4.4567 ms/op 5.3442 ms/op 0.83
mainnet_e81889 - altair processRewardsAndPenalties 49.032 ms/op 62.663 ms/op 0.78
mainnet_e81889 - altair processRegistryUpdates 2.1610 us/op 2.3690 us/op 0.91
mainnet_e81889 - altair processSlashings 517.00 ns/op 400.00 ns/op 1.29
mainnet_e81889 - altair processEth1DataReset 555.00 ns/op 507.00 ns/op 1.09
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.2012 ms/op 1.2134 ms/op 0.99
mainnet_e81889 - altair processSlashingsReset 2.4240 us/op 2.7880 us/op 0.87
mainnet_e81889 - altair processRandaoMixesReset 5.2360 us/op 4.9070 us/op 1.07
mainnet_e81889 - altair processHistoricalRootsUpdate 820.00 ns/op 852.00 ns/op 0.96
mainnet_e81889 - altair processParticipationFlagUpdates 1.8200 us/op 1.9910 us/op 0.91
mainnet_e81889 - altair processSyncCommitteeUpdates 577.00 ns/op 543.00 ns/op 1.06
mainnet_e81889 - altair afterProcessEpoch 123.12 ms/op 124.65 ms/op 0.99
capella processEpoch - mainnet_e217614 999.84 ms/op 1.0235 s/op 0.98
mainnet_e217614 - capella beforeProcessEpoch 229.08 ms/op 218.86 ms/op 1.05
mainnet_e217614 - capella processJustificationAndFinalization 14.519 us/op 14.202 us/op 1.02
mainnet_e217614 - capella processInactivityUpdates 15.377 ms/op 19.071 ms/op 0.81
mainnet_e217614 - capella processRewardsAndPenalties 281.23 ms/op 282.90 ms/op 0.99
mainnet_e217614 - capella processRegistryUpdates 19.351 us/op 16.740 us/op 1.16
mainnet_e217614 - capella processSlashings 499.00 ns/op 475.00 ns/op 1.05
mainnet_e217614 - capella processEth1DataReset 461.00 ns/op 452.00 ns/op 1.02
mainnet_e217614 - capella processEffectiveBalanceUpdates 3.9606 ms/op 3.9466 ms/op 1.00
mainnet_e217614 - capella processSlashingsReset 2.1310 us/op 2.1860 us/op 0.97
mainnet_e217614 - capella processRandaoMixesReset 3.8630 us/op 3.6940 us/op 1.05
mainnet_e217614 - capella processHistoricalRootsUpdate 597.00 ns/op 546.00 ns/op 1.09
mainnet_e217614 - capella processParticipationFlagUpdates 2.8370 us/op 1.6230 us/op 1.75
mainnet_e217614 - capella afterProcessEpoch 288.77 ms/op 294.70 ms/op 0.98
phase0 processEpoch - mainnet_e58758 318.50 ms/op 320.02 ms/op 1.00
mainnet_e58758 - phase0 beforeProcessEpoch 109.67 ms/op 114.76 ms/op 0.96
mainnet_e58758 - phase0 processJustificationAndFinalization 14.165 us/op 14.577 us/op 0.97
mainnet_e58758 - phase0 processRewardsAndPenalties 54.329 ms/op 53.374 ms/op 1.02
mainnet_e58758 - phase0 processRegistryUpdates 9.7990 us/op 9.4150 us/op 1.04
mainnet_e58758 - phase0 processSlashings 485.00 ns/op 504.00 ns/op 0.96
mainnet_e58758 - phase0 processEth1DataReset 418.00 ns/op 424.00 ns/op 0.99
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 973.55 us/op 940.48 us/op 1.04
mainnet_e58758 - phase0 processSlashingsReset 2.0040 us/op 2.0910 us/op 0.96
mainnet_e58758 - phase0 processRandaoMixesReset 3.8670 us/op 3.7730 us/op 1.02
mainnet_e58758 - phase0 processHistoricalRootsUpdate 640.00 ns/op 587.00 ns/op 1.09
mainnet_e58758 - phase0 processParticipationRecordUpdates 3.4950 us/op 3.3140 us/op 1.05
mainnet_e58758 - phase0 afterProcessEpoch 98.603 ms/op 100.72 ms/op 0.98
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.9421 ms/op 1.1883 ms/op 1.63
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.4600 ms/op 1.4424 ms/op 1.01
altair processInactivityUpdates - 250000 normalcase 24.211 ms/op 18.413 ms/op 1.31
altair processInactivityUpdates - 250000 worstcase 25.234 ms/op 20.601 ms/op 1.22
phase0 processRegistryUpdates - 250000 normalcase 7.2960 us/op 8.6520 us/op 0.84
phase0 processRegistryUpdates - 250000 badcase_full_deposits 304.14 us/op 307.09 us/op 0.99
phase0 processRegistryUpdates - 250000 worstcase 0.5 118.78 ms/op 123.62 ms/op 0.96
altair processRewardsAndPenalties - 250000 normalcase 72.540 ms/op 57.403 ms/op 1.26
altair processRewardsAndPenalties - 250000 worstcase 70.453 ms/op 56.468 ms/op 1.25
phase0 getAttestationDeltas - 250000 normalcase 7.8650 ms/op 7.5840 ms/op 1.04
phase0 getAttestationDeltas - 250000 worstcase 8.0853 ms/op 7.6661 ms/op 1.05
phase0 processSlashings - 250000 worstcase 2.3575 ms/op 2.1955 ms/op 1.07
altair processSyncCommitteeUpdates - 250000 148.04 ms/op 139.13 ms/op 1.06
BeaconState.hashTreeRoot - No change 275.00 ns/op 278.00 ns/op 0.99
BeaconState.hashTreeRoot - 1 full validator 48.424 us/op 47.548 us/op 1.02
BeaconState.hashTreeRoot - 32 full validator 474.04 us/op 462.29 us/op 1.03
BeaconState.hashTreeRoot - 512 full validator 4.9629 ms/op 4.8522 ms/op 1.02
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 58.916 us/op 57.723 us/op 1.02
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 811.06 us/op 790.89 us/op 1.03
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 10.042 ms/op 9.7362 ms/op 1.03
BeaconState.hashTreeRoot - 1 balances 45.889 us/op 44.944 us/op 1.02
BeaconState.hashTreeRoot - 32 balances 410.97 us/op 400.38 us/op 1.03
BeaconState.hashTreeRoot - 512 balances 3.8110 ms/op 3.6808 ms/op 1.04
BeaconState.hashTreeRoot - 250000 balances 76.451 ms/op 73.866 ms/op 1.03
aggregationBits - 2048 els - zipIndexesInBitList 14.533 us/op 14.156 us/op 1.03
regular array get 100000 times 32.227 us/op 31.925 us/op 1.01
wrappedArray get 100000 times 32.210 us/op 42.530 us/op 0.76
arrayWithProxy get 100000 times 14.606 ms/op 13.905 ms/op 1.05
ssz.Root.equals 200.00 ns/op 202.00 ns/op 0.99
byteArrayEquals 199.00 ns/op 197.00 ns/op 1.01
shuffle list - 16384 els 6.8857 ms/op 6.7637 ms/op 1.02
shuffle list - 250000 els 100.93 ms/op 99.316 ms/op 1.02
processSlot - 1 slots 7.7330 us/op 7.4210 us/op 1.04
processSlot - 32 slots 1.2232 ms/op 1.2950 ms/op 0.94
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 54.400 ms/op 53.001 ms/op 1.03
getCommitteeAssignments - req 1 vs - 250000 vc 2.4733 ms/op 2.5032 ms/op 0.99
getCommitteeAssignments - req 100 vs - 250000 vc 3.6850 ms/op 3.6998 ms/op 1.00
getCommitteeAssignments - req 1000 vs - 250000 vc 4.0468 ms/op 4.0178 ms/op 1.01
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 4.5500 ns/op 4.4500 ns/op 1.02
state getBlockRootAtSlot - 250000 vs - 7PWei 786.23 ns/op 703.29 ns/op 1.12
computeProposers - vc 250000 8.7532 ms/op 8.4577 ms/op 1.03
computeEpochShuffling - vc 250000 103.75 ms/op 101.50 ms/op 1.02
getNextSyncCommittee - vc 250000 141.90 ms/op 140.68 ms/op 1.01
computeSigningRoot for AttestationData 12.918 us/op 12.793 us/op 1.01
hash AttestationData serialized data then Buffer.toString(base64) 2.2365 us/op 2.3185 us/op 0.96
toHexString serialized data 1.0189 us/op 1.0698 us/op 0.95
Buffer.toString(base64) 201.52 ns/op 215.96 ns/op 0.93

by benchmarkbot/action

@nflaig nflaig force-pushed the nflaig/health-syncing-status branch from 1bc571b to 0cbccc2 Compare July 23, 2023 19:37
@@ -66,10 +66,18 @@ export function getNodeApi(
return {data: sync.getSyncStatus()};
},

async getHealth(_req, res) {
async getHealth(options, _req, res) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Uhmm I didn't noticed that request and response objects where being leaked to the API implementation. I see it was introduced by #4994 but why not keep the interaction in the Fastify's response object in packages/api/src/beacon/server/node.ts?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looked at this as well but the only "clean way" to achieve this without too many type hacks is approach used in #3349 but in this case the getHealth type is incorrect, it doesn't actually return a res.response of type number.

If you think passing response object to API implementation is not desired we should update types accordingly and use the other approach to implementing this.

Copy link
Contributor

Choose a reason for hiding this comment

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

@nazarhussain any opinions here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Passing res directly to API handler is a pattern which does not meant it's leaking anything.

We can define a data structure for the implementation of API endpoints, that way we can handle the status code at the server implementation side.

So all implementation handlers should return;

{
   body: unknown;
   status?: number; 
}

And if status is not present then we consider 200 as default.

Copy link
Member Author

Choose a reason for hiding this comment

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

Health API is really the only one right now that needs to modify the status code.

The interface of response is limited to setting the status code

export type GenericResponseObject = {code: (code: number) => void};

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case we can make the response status code structure as optional. e.g.

() => T | {body: T, status; number}

This will require no refactor on API implementation side and minimum change on the server side.

Copy link
Member Author

@nflaig nflaig Jul 24, 2023

Choose a reason for hiding this comment

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

True, that could be a good pattern to give some API implementations more control over the response. Maybe we want to allow to set null explicitly as body as well, that way we can more closely follow the spec for APIs that should not return a json body (see #5790 (comment)).

Proposed type would look like this then

() => T | {body: T | null, status: number}

Copy link
Contributor

Choose a reason for hiding this comment

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

So will you make the change in this PR or separate?

Copy link
Member Author

Choose a reason for hiding this comment

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

Better to do it in this PR, allows to remove the workaround in tests, should reduce overall diff

Copy link
Member Author

@nflaig nflaig Jul 29, 2023

Choose a reason for hiding this comment

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

Request and response objects are no longer passed to API implementation. After looking into updating return type to something like () => T | {body: T | null, status: number} it does not look that clean and requires too many type checks in other place unrelated to health API.

Better to address in a separate PR.

>;
return {
ok: false,
status: err.status,
Copy link
Member Author

Choose a reason for hiding this comment

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

Assuming this was just missed previously as the code here is not really type-safe, didn't find a quick solution to fix that but we should always set status in client response.

}

if (err instanceof TimeoutError) {
return {
ok: false,
status: HttpStatusCode.INTERNAL_SERVER_ERROR,
Copy link
Member Author

Choose a reason for hiding this comment

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

This status code is kinda wrong but 408 would also be wrong as it would indicate a server timeout whereas this error happens due to client timeout.

If server doesn't respond within client timeout we can assume a server issue, setting 500 here is the most accurate. Alternative would be to just throw the error here but this makes the error logs less informative if a timeout happens.

@@ -26,7 +26,7 @@ export function getFetchOptsSerializer<Fn extends (...args: any) => any, ReqType
return {
url: urlFormater(req.params ?? {}),
method: routeDef.method,
query: req.query,
query: Object.keys(req.query ?? {}).length ? req.query : undefined,
Copy link
Member Author

@nflaig nflaig Jul 24, 2023

Choose a reason for hiding this comment

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

Fixes appending '?' to URL if query params are omitted, e.g. /eth/v1/node/health instead of /eth/v1/node/health?.

I would assume most/(all) HTTP servers will ignore this anyways, but it might be misleading if req.url is logged on the server.

wemeetagain
wemeetagain previously approved these changes Jul 24, 2023
Copy link
Member

@wemeetagain wemeetagain left a comment

Choose a reason for hiding this comment

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

lgtm!

@nflaig
Copy link
Member Author

nflaig commented Jul 24, 2023

Would like to get @nazarhussain thoughts on #5790 (comment) first before we merge this. Putting as draft for now. Thanks for all the reviews :)

@nflaig nflaig marked this pull request as draft July 24, 2023 17:19
query: options?.syncingStatus !== undefined ? {syncing_status: options.syncingStatus} : {},
}),
parseReq: ({query}) => [{syncingStatus: query.syncing_status}],
schema: {query: {syncing_status: Schema.Uint}},
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend moving the range check in the schema.

Copy link
Member Author

@nflaig nflaig Jul 24, 2023

Choose a reason for hiding this comment

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

Do you have an example API that does this? Based on current types this is not possible as far as I can see, we only support pre-defined schemas

export enum Schema {

We enforce basic validation through schema but the error response and logging is not ideal in my opinion

JSON API response

Schema validation error

[
  {
    "instancePath": "/syncing_status",
    "schemaPath": "#/properties/syncing_status/minimum",
    "keyword": "minimum",
    "params": {
      "comparison": ">=",
      "limit": 0
    },
    "message": "must be >= 0"
  }
]

API error

{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid syncing status code: 600"
}

Error log

Schema validation error

Eph 0/2 1.126[rest]            error: Req req-o getHealth error  querystring/syncing_status must be >= 0
Error: querystring/syncing_status must be >= 0
    at defaultSchemaErrorFormatter (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/context.js:108:10)
    at wrapValidationError (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/validation.js:228:17)
    at validate (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/validation.js:157:16)
    at preValidationCallback (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/handleRequest.js:92:25)
    at handler (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/handleRequest.js:76:7)
    at handleRequest (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/handleRequest.js:24:5)
    at runPreParsing (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/route.js:569:5)
    at next (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/hooks.js:179:7)
    at handleResolve (/home/nico/projects/ethereum/lodestar/node_modules/fastify/lib/hooks.js:196:5)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

API error

Eph 0/0 5.017[rest]             warn: Req req-9 getHealth failed reason=Invalid syncing status code: 600

In both cases as it is right now, it is better to use API error. Schema validation could be made more flexible but there are not that many values in the spec that enforce things like minimum / maximum.

@nflaig
Copy link
Member Author

nflaig commented Jul 24, 2023

Noticed something else while testing, right now we always return a json body in the response.

> curl -s http://localhost:9596/eth/v1/node/health
{}

In case of success, this is due to default behavior if no returnType is defined

if (returnType) {
return returnType.toJson(data);
} else {
return {};
}

And in case of errors, we return them in response body as well

> curl -s http://localhost:9596/eth/v1/node/health?syncing_status=600 | jq
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "Invalid syncing status code: 600"
}

At least for the error response, I think this is even a good thing as just returning the status code is not that helpful.

@nflaig nflaig marked this pull request as ready for review July 24, 2023 18:42
@nflaig nflaig requested a review from nazarhussain July 24, 2023 18:42
@nflaig nflaig marked this pull request as draft July 24, 2023 20:05
@nflaig
Copy link
Member Author

nflaig commented Jul 29, 2023

#5790 (comment) has been addressed now by updating the handler.

> curl -s http://localhost:9596/eth/v1/node/health

No longer returns a response body. Errors will still return a response body with more error details.


export type ServerApi<T extends Record<string, APIClientHandler>> = {
[K in keyof T]: (
...args: [...args: Parameters<T[K]>, req?: GenericRequestObject, res?: GenericResponseObject]
...args: [...args: Parameters<T[K]>, opts?: GenericOptions]
Copy link
Member Author

@nflaig nflaig Jul 29, 2023

Choose a reason for hiding this comment

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

I removed req / res but added generic opts instead, we have API implementations that pass additional options, like publishBlock. Might not be a bad thing in general to allow this as API implementations are called internally as well.

return this.publishBlock(signedBlockOrContents, {ignoreIfKnown: true});

@nflaig
Copy link
Member Author

nflaig commented Jul 29, 2023

@nazarhussain Thanks for suggesting an implementation on how we could resolve the issue of setting the status code. This is really similar to what I attempted as well locally but it creates too much noise in my opinion just for solving an issue of a single API.

I think if we want to implement a solution that gets rid of all custom route handlers, e.g. getState or getStateProof API

handler: async (req) => {

handler: async (req) => {

To achieve this we would have to give the API implementation the option to return a "raw" response, no modification at all in generic handler, skip calls like returnType.toJson which restricts the API to only return JSON.

return returnType.toJson(data);

I went for solution now which only modifies the health API and does some minor clean up of other things but I really want to keep the diff of this PR smaller.

Another refactoring PR might make sense which addresses more than just the requirements of getHealth API or we could just revisit this when implementing #5128.

@nflaig nflaig marked this pull request as ready for review July 29, 2023 13:41
@nazarhussain
Copy link
Contributor

I don't see the change as noise rather a feature to make the API implementation flexible. It's upto us we want that feature, and either we want that feature in this PR or a separate PR.

I would not suggest to remove the support for custom handlers, it's a flexibility that we may need anytime if not now.

@nflaig
Copy link
Member Author

nflaig commented Jul 31, 2023

I looked at this for like 4 hours, tried different type and different places how to modify the type. Nothing seemed like a good solution to me, for example having to add this const data = "status" in apiRes ? apiRes.response?.data : apiRes.data; in 4-5 other places doesn't seem right to me.

I would like a solution that addresses more than just the status code issue of health API. For this PR, I feel like 2 type casts with comments that explain why it is done seems alright to me (health API is just a bit of an outlier). It also keeps the changes this of this PR isolated to health API, I noticed that forwarding request for example has unwanted side-effects as we override the options some API implementations like publishBlock add.

Also considering that health API is broken at the moment (always returns 200) and there are now tests that ensure it works correctly reduces the risk of a regression due to missing type safety by casting the types.

@nflaig
Copy link
Member Author

nflaig commented Jul 31, 2023

I would not suggest to remove the support for custom handlers

Shouldn't remove support for them but rather make them less needed. Still need to consider the benefit of this but having the API implementation in a single place seems beneficial to me.

@wemeetagain wemeetagain merged commit 3ef8a00 into unstable Aug 1, 2023
11 checks passed
@wemeetagain wemeetagain deleted the nflaig/health-syncing-status branch August 1, 2023 14:54
@wemeetagain
Copy link
Member

🎉 This PR is included in v1.10.0 🎉

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.

Add syncing_status query parameter to health API
4 participants