mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-04-19 03:01:06 -04:00
Compare commits
197 Commits
tests/test
...
defer-cons
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c86e1badc | ||
|
|
c8cbec4269 | ||
|
|
9802242cfe | ||
|
|
e75166cfe7 | ||
|
|
8864484230 | ||
|
|
5bb13408d5 | ||
|
|
99327d7422 | ||
|
|
527863e9de | ||
|
|
de34b4dfae | ||
|
|
ccf61fb91b | ||
|
|
e9fdeee7bb | ||
|
|
9da54ce816 | ||
|
|
ee9fa34b30 | ||
|
|
cf09469ac9 | ||
|
|
f3dfcbab2a | ||
|
|
10cd675793 | ||
|
|
f01575e44c | ||
|
|
129d6e1088 | ||
|
|
883d78221f | ||
|
|
6e4d7fd781 | ||
|
|
14f5e6f414 | ||
|
|
9d084bceb3 | ||
|
|
f79d2efc6e | ||
|
|
9dba7c5319 | ||
|
|
c02c057b7d | ||
|
|
b6ec6a8eec | ||
|
|
3ca8c3ba35 | ||
|
|
1092c7135f | ||
|
|
73033a9d67 | ||
|
|
a7b83c358a | ||
|
|
29a0fd6760 | ||
|
|
209e46bab7 | ||
|
|
108e2806cb | ||
|
|
68c4c36e65 | ||
|
|
67cc68c3bb | ||
|
|
c33f0d04b7 | ||
|
|
f05972a181 | ||
|
|
7352ae03c6 | ||
|
|
4f34624a54 | ||
|
|
4e44fdf55e | ||
|
|
139773aa3a | ||
|
|
6558e947ca | ||
|
|
d8150ac20c | ||
|
|
543746d95d | ||
|
|
8e8c990a04 | ||
|
|
3c9eae6064 | ||
|
|
c0ee666996 | ||
|
|
3e61778d38 | ||
|
|
0bfe736730 | ||
|
|
277797f6f8 | ||
|
|
2898a5e8a2 | ||
|
|
ca8cc65d72 | ||
|
|
445487b4a7 | ||
|
|
86edeef90f | ||
|
|
bfbca75862 | ||
|
|
1f72a1428c | ||
|
|
101dd55710 | ||
|
|
7d797ee4f9 | ||
|
|
45e38d430f | ||
|
|
0a643b177d | ||
|
|
9ea9e1f07c | ||
|
|
8fb4d85bbd | ||
|
|
259f526c8d | ||
|
|
77b5a7a5b3 | ||
|
|
fb9d9d93de | ||
|
|
7781e40abf | ||
|
|
e77724401d | ||
|
|
f43ba7851c | ||
|
|
dfc5bbef7f | ||
|
|
6430e27257 | ||
|
|
b141b5ccd2 | ||
|
|
696a08f3b9 | ||
|
|
65d428db58 | ||
|
|
a6e669d8bc | ||
|
|
605ab1c7ac | ||
|
|
93f7214b32 | ||
|
|
1bffcc84f4 | ||
|
|
7728ad4aa2 | ||
|
|
1362654669 | ||
|
|
3cec3997f8 | ||
|
|
1236519810 | ||
|
|
36052ed1bb | ||
|
|
458d4ebe54 | ||
|
|
8680f3f8bb | ||
|
|
416c49e6d5 | ||
|
|
6605dfbd50 | ||
|
|
84993fdd68 | ||
|
|
1e916418f2 | ||
|
|
8d5d584cf8 | ||
|
|
598509ffa8 | ||
|
|
0fbd643c02 | ||
|
|
0e4f3231d2 | ||
|
|
b17f2752ab | ||
|
|
932e5eb7d8 | ||
|
|
de233438f1 | ||
|
|
e35f6c351a | ||
|
|
4da5ed072c | ||
|
|
dec4b43b3e | ||
|
|
17ea45a011 | ||
|
|
1934afac73 | ||
|
|
d0c9a31657 | ||
|
|
66c70200ee | ||
|
|
928a874e4a | ||
|
|
ed8a3351aa | ||
|
|
5b95d11c5e | ||
|
|
7a4bea0e44 | ||
|
|
e899003973 | ||
|
|
e751a74c64 | ||
|
|
6826e77539 | ||
|
|
15c178ef0c | ||
|
|
e115137591 | ||
|
|
dc62271ebb | ||
|
|
3ec505bc22 | ||
|
|
6be77c0194 | ||
|
|
03fa7042cb | ||
|
|
c620f29aab | ||
|
|
7a652d7ec6 | ||
|
|
b59a830dce | ||
|
|
6999943c3d | ||
|
|
90064edd54 | ||
|
|
393eb1e83c | ||
|
|
d2bcf75c50 | ||
|
|
89d3a6c66f | ||
|
|
bf2485eb71 | ||
|
|
a88f60f1fa | ||
|
|
33f899506f | ||
|
|
f7ead02e6e | ||
|
|
0abf17f6cd | ||
|
|
8307ee1098 | ||
|
|
7e4a039a7f | ||
|
|
d46621eea3 | ||
|
|
8b25ffaa45 | ||
|
|
17f1b78494 | ||
|
|
5874226067 | ||
|
|
544bc3eb45 | ||
|
|
de6d9947d7 | ||
|
|
e5382d95dd | ||
|
|
6d8445d440 | ||
|
|
7b16c34af3 | ||
|
|
91577760b6 | ||
|
|
9ced048510 | ||
|
|
0439151373 | ||
|
|
5f050566f4 | ||
|
|
8a43513f5a | ||
|
|
6c8504ef71 | ||
|
|
e99f86ecc5 | ||
|
|
0c36416677 | ||
|
|
f2db0faea8 | ||
|
|
e31f59e04e | ||
|
|
83a5210549 | ||
|
|
0aa4a08b7b | ||
|
|
9c5d4a1767 | ||
|
|
dd1ede572d | ||
|
|
ec7eb97d41 | ||
|
|
0c893bd1a6 | ||
|
|
9a7cae9e5c | ||
|
|
c114bc57d9 | ||
|
|
a9aea0ba25 | ||
|
|
9f2ade53ff | ||
|
|
5b19916067 | ||
|
|
e4cbb34c2f | ||
|
|
c9a2a0dd86 | ||
|
|
fdd04a5466 | ||
|
|
a199580672 | ||
|
|
b5bdd65f64 | ||
|
|
ce4e653261 | ||
|
|
6f87a0cb95 | ||
|
|
d262c9f927 | ||
|
|
7865bd5f32 | ||
|
|
7f2593e496 | ||
|
|
55e2f0a7d2 | ||
|
|
bf11ac0d64 | ||
|
|
dfb918432f | ||
|
|
a28c6c8145 | ||
|
|
045a3cfe4e | ||
|
|
8ee28394ab | ||
|
|
b31e2ffe51 | ||
|
|
22e77add54 | ||
|
|
7f9983386e | ||
|
|
935acf6b9d | ||
|
|
63b69a63b1 | ||
|
|
cf63d112be | ||
|
|
6c045083a6 | ||
|
|
09d0338aa9 | ||
|
|
eaf3aa3e8e | ||
|
|
9c49bb484c | ||
|
|
bb0f70ad60 | ||
|
|
dc66f8872d | ||
|
|
db2bb5505c | ||
|
|
14f01bbc6c | ||
|
|
c3e74e4a5d | ||
|
|
e7ae6a004b | ||
|
|
862fb2eb4a | ||
|
|
bb80a9c832 | ||
|
|
c1b668a50a | ||
|
|
fab687d96d | ||
|
|
cf94ccbf72 |
@@ -1,25 +1,41 @@
|
||||
version: v1.7.0-alpha.1
|
||||
version: v1.7.0-alpha.4
|
||||
style: full
|
||||
|
||||
specrefs:
|
||||
search_root: ..
|
||||
search_root: .
|
||||
auto_standardize_names: true
|
||||
auto_add_missing_entries: true
|
||||
require_exceptions_have_fork: true
|
||||
|
||||
files:
|
||||
- configs.yml
|
||||
- constants.yml
|
||||
- containers.yml
|
||||
- dataclasses.yml
|
||||
- functions.yml
|
||||
- presets.yml
|
||||
- specrefs/configs.yml
|
||||
- specrefs/constants.yml
|
||||
- specrefs/containers.yml
|
||||
- specrefs/dataclasses.yml
|
||||
- specrefs/functions.yml
|
||||
- specrefs/presets.yml
|
||||
|
||||
exceptions:
|
||||
presets:
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
|
||||
- MAX_PAYLOAD_ATTESTATIONS#gloas
|
||||
- PTC_SIZE#gloas
|
||||
|
||||
constants:
|
||||
# Constants in the KZG library
|
||||
# heze
|
||||
- DOMAIN_INCLUSION_LIST_COMMITTEE#heze
|
||||
# phase0
|
||||
- BASIS_POINTS#phase0
|
||||
- ENDIANNESS#phase0
|
||||
- MAX_CONCURRENT_REQUESTS#phase0
|
||||
- UINT64_MAX#phase0
|
||||
- UINT64_MAX_SQRT#phase0
|
||||
# altair
|
||||
- PARTICIPATION_FLAG_WEIGHTS#altair
|
||||
# bellatrix
|
||||
- SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY#bellatrix
|
||||
# deneb
|
||||
- BLS_MODULUS#deneb
|
||||
- BYTES_PER_COMMITMENT#deneb
|
||||
- BYTES_PER_FIELD_ELEMENT#deneb
|
||||
@@ -33,18 +49,9 @@ exceptions:
|
||||
- PRIMITIVE_ROOT_OF_UNITY#deneb
|
||||
- RANDOM_CHALLENGE_KZG_BATCH_DOMAIN#deneb
|
||||
- RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN#fulu
|
||||
|
||||
# Not implemented
|
||||
- BASIS_POINTS#phase0
|
||||
- ENDIANNESS#phase0
|
||||
- MAX_CONCURRENT_REQUESTS#phase0
|
||||
- PARTICIPATION_FLAG_WEIGHTS#altair
|
||||
- SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY#bellatrix
|
||||
# fulu
|
||||
- UINT256_MAX#fulu
|
||||
- UINT64_MAX#phase0
|
||||
- UINT64_MAX_SQRT#phase0
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- BUILDER_PAYMENT_THRESHOLD_DENOMINATOR#gloas
|
||||
- BUILDER_PAYMENT_THRESHOLD_NUMERATOR#gloas
|
||||
- BUILDER_WITHDRAWAL_PREFIX#gloas
|
||||
@@ -56,69 +63,92 @@ exceptions:
|
||||
- ATTESTATION_TIMELINESS_INDEX#gloas
|
||||
- BUILDER_INDEX_FLAG#gloas
|
||||
- BUILDER_INDEX_SELF_BUILD#gloas
|
||||
- DOMAIN_PROPOSER_PREFERENCES#gloas
|
||||
- NUM_BLOCK_TIMELINESS_DEADLINES#gloas
|
||||
- PTC_TIMELINESS_INDEX#gloas
|
||||
|
||||
configs:
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- AGGREGATE_DUE_BPS_GLOAS#gloas
|
||||
- ATTESTATION_DUE_BPS_GLOAS#gloas
|
||||
- CONTRIBUTION_DUE_BPS_GLOAS#gloas
|
||||
- GLOAS_FORK_EPOCH#gloas
|
||||
- GLOAS_FORK_VERSION#gloas
|
||||
- MAX_REQUEST_PAYLOADS#gloas
|
||||
- PAYLOAD_ATTESTATION_DUE_BPS#gloas
|
||||
- SYNC_MESSAGE_DUE_BPS_GLOAS#gloas
|
||||
- MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas
|
||||
# heze
|
||||
- HEZE_FORK_EPOCH#heze
|
||||
- HEZE_FORK_VERSION#heze
|
||||
- INCLUSION_LIST_SUBMISSION_DUE_BPS#heze
|
||||
- MAX_BYTES_PER_INCLUSION_LIST#heze
|
||||
- MAX_REQUEST_INCLUSION_LIST#heze
|
||||
- PROPOSER_INCLUSION_LIST_CUTOFF_BPS#heze
|
||||
- VIEW_FREEZE_CUTOFF_BPS#heze
|
||||
|
||||
ssz_objects:
|
||||
# Not implemented
|
||||
# phase0
|
||||
- Eth1Block#phase0
|
||||
- MatrixEntry#fulu
|
||||
|
||||
# Not implemented: capella
|
||||
# fulu
|
||||
- PartialDataColumnHeader#fulu
|
||||
- PartialDataColumnPartsMetadata#fulu
|
||||
- PartialDataColumnSidecar#fulu
|
||||
# gloas
|
||||
- PartialDataColumnHeader#gloas
|
||||
# heze
|
||||
- BeaconState#heze
|
||||
- ExecutionPayloadBid#heze
|
||||
- InclusionList#heze
|
||||
- SignedExecutionPayloadBid#heze
|
||||
- SignedInclusionList#heze
|
||||
# capella
|
||||
- LightClientBootstrap#capella
|
||||
- LightClientFinalityUpdate#capella
|
||||
- LightClientOptimisticUpdate#capella
|
||||
- LightClientUpdate#capella
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# fulu
|
||||
- MatrixEntry#fulu
|
||||
# gloas
|
||||
- BeaconBlockBody#gloas
|
||||
- BeaconState#gloas
|
||||
- Builder#gloas
|
||||
- BuilderPendingPayment#gloas
|
||||
- BuilderPendingWithdrawal#gloas
|
||||
- DataColumnSidecar#gloas
|
||||
- ExecutionPayloadEnvelope#gloas
|
||||
- ExecutionPayloadBid#gloas
|
||||
- ExecutionPayloadEnvelope#gloas
|
||||
- ForkChoiceNode#gloas
|
||||
- IndexedPayloadAttestation#gloas
|
||||
- PayloadAttestation#gloas
|
||||
- PayloadAttestationData#gloas
|
||||
- PayloadAttestationMessage#gloas
|
||||
- SignedExecutionPayloadEnvelope#gloas
|
||||
- SignedExecutionPayloadBid#gloas
|
||||
- Builder#gloas
|
||||
- ProposerPreferences#gloas
|
||||
- SignedExecutionPayloadBid#gloas
|
||||
- SignedExecutionPayloadEnvelope#gloas
|
||||
- SignedProposerPreferences#gloas
|
||||
|
||||
dataclasses:
|
||||
# Not implemented
|
||||
- BlobParameters#fulu
|
||||
- ExpectedWithdrawals#capella
|
||||
- ExpectedWithdrawals#electra
|
||||
# phase0
|
||||
- LatestMessage#phase0
|
||||
- LightClientStore#altair
|
||||
- OptimisticStore#bellatrix
|
||||
- Seen#phase0
|
||||
- Store#phase0
|
||||
|
||||
# Not implemented: capella
|
||||
# altair
|
||||
- LightClientStore#altair
|
||||
# bellatrix
|
||||
- OptimisticStore#bellatrix
|
||||
# capella
|
||||
- ExpectedWithdrawals#capella
|
||||
- LightClientStore#capella
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# electra
|
||||
- ExpectedWithdrawals#electra
|
||||
# fulu
|
||||
- BlobParameters#fulu
|
||||
# gloas
|
||||
- ExpectedWithdrawals#gloas
|
||||
- LatestMessage#gloas
|
||||
- Store#gloas
|
||||
# heze
|
||||
- GetInclusionListResponse#heze
|
||||
- InclusionListStore#heze
|
||||
- PayloadAttributes#heze
|
||||
- Store#heze
|
||||
|
||||
functions:
|
||||
# Functions implemented by KZG library for EIP-4844
|
||||
@@ -140,7 +170,6 @@ exceptions:
|
||||
- g1_lincomb#deneb
|
||||
- hash_to_bls_field#deneb
|
||||
- is_power_of_two#deneb
|
||||
- multi_exp#deneb
|
||||
- reverse_bits#deneb
|
||||
- validate_kzg_g1#deneb
|
||||
- verify_blob_kzg_proof#deneb
|
||||
@@ -175,7 +204,23 @@ exceptions:
|
||||
- verify_cell_kzg_proof_batch#fulu
|
||||
- verify_cell_kzg_proof_batch_impl#fulu
|
||||
|
||||
# Not implemented: phase0
|
||||
# phase0
|
||||
- compute_attestation_subnet_prefix_bits#phase0
|
||||
- compute_min_epochs_for_block_requests#phase0
|
||||
- compute_time_at_slot_ms#phase0
|
||||
- is_not_from_future_slot#phase0
|
||||
- is_within_slot_range#phase0
|
||||
- update_proposer_boost_root#phase0
|
||||
- is_proposer_equivocation#phase0
|
||||
- record_block_timeliness#phase0
|
||||
- compute_proposer_score#phase0
|
||||
- get_attestation_score#phase0
|
||||
- validate_attester_slashing_gossip#phase0
|
||||
- validate_beacon_aggregate_and_proof_gossip#phase0
|
||||
- validate_beacon_attestation_gossip#phase0
|
||||
- validate_beacon_block_gossip#phase0
|
||||
- validate_proposer_slashing_gossip#phase0
|
||||
- validate_voluntary_exit_gossip#phase0
|
||||
- calculate_committee_fraction#phase0
|
||||
- compute_fork_version#phase0
|
||||
- compute_pulled_up_tip#phase0
|
||||
@@ -221,8 +266,7 @@ exceptions:
|
||||
- validate_on_attestation#phase0
|
||||
- validate_target_epoch_against_current_time#phase0
|
||||
- xor#phase0
|
||||
|
||||
# Not implemented: altair
|
||||
# altair
|
||||
- compute_merkle_proof#altair
|
||||
- compute_sync_committee_period_at_slot#altair
|
||||
- get_contribution_and_proof#altair
|
||||
@@ -244,27 +288,30 @@ exceptions:
|
||||
- process_sync_committee_contributions#altair
|
||||
- set_or_append_list#altair
|
||||
- validate_light_client_update#altair
|
||||
|
||||
# Not implemented: bellatrix
|
||||
# bellatrix
|
||||
- get_execution_payload#bellatrix
|
||||
- is_merge_transition_block#bellatrix
|
||||
- is_optimistic_candidate_block#bellatrix
|
||||
- latest_verified_ancestor#bellatrix
|
||||
- prepare_execution_payload#bellatrix
|
||||
|
||||
# Not implemented: capella
|
||||
# capella
|
||||
- apply_withdrawals#capella
|
||||
- get_balance_after_withdrawals#capella
|
||||
- get_lc_execution_root#capella
|
||||
- get_validators_sweep_withdrawals#capella
|
||||
- is_valid_light_client_header#capella
|
||||
- prepare_execution_payload#capella
|
||||
- process_epoch#capella
|
||||
- update_next_withdrawal_index#capella
|
||||
- update_next_withdrawal_validator_index#capella
|
||||
- upgrade_lc_bootstrap_to_capella#capella
|
||||
- upgrade_lc_finality_update_to_capella#capella
|
||||
- upgrade_lc_header_to_capella#capella
|
||||
- upgrade_lc_optimistic_update_to_capella#capella
|
||||
- upgrade_lc_store_to_capella#capella
|
||||
- upgrade_lc_update_to_capella#capella
|
||||
|
||||
# Not implemented: deneb
|
||||
# deneb
|
||||
- compute_max_request_blob_sidecars#deneb
|
||||
- get_lc_execution_root#deneb
|
||||
- is_valid_light_client_header#deneb
|
||||
- prepare_execution_payload#deneb
|
||||
@@ -274,33 +321,43 @@ exceptions:
|
||||
- upgrade_lc_optimistic_update_to_deneb#deneb
|
||||
- upgrade_lc_store_to_deneb#deneb
|
||||
- upgrade_lc_update_to_deneb#deneb
|
||||
|
||||
# Not implemented: electra
|
||||
# electra
|
||||
- compute_max_request_blob_sidecars#electra
|
||||
- compute_weak_subjectivity_period#electra
|
||||
- current_sync_committee_gindex_at_slot#electra
|
||||
- finalized_root_gindex_at_slot#electra
|
||||
- get_eth1_vote#electra
|
||||
- get_lc_execution_root#electra
|
||||
- get_pending_partial_withdrawals#electra
|
||||
- get_validators_sweep_withdrawals#electra
|
||||
- is_compounding_withdrawal_credential#electra
|
||||
- is_eligible_for_partial_withdrawals#electra
|
||||
- is_within_weak_subjectivity_period#electra
|
||||
- next_sync_committee_gindex_at_slot#electra
|
||||
- normalize_merkle_branch#electra
|
||||
- prepare_execution_payload#electra
|
||||
- update_pending_partial_withdrawals#electra
|
||||
- upgrade_lc_bootstrap_to_electra#electra
|
||||
- upgrade_lc_finality_update_to_electra#electra
|
||||
- upgrade_lc_header_to_electra#electra
|
||||
- upgrade_lc_optimistic_update_to_electra#electra
|
||||
- upgrade_lc_store_to_electra#electra
|
||||
- upgrade_lc_update_to_electra#electra
|
||||
|
||||
# Not implemented: fulu
|
||||
# fulu
|
||||
- compute_max_request_data_column_sidecars#fulu
|
||||
- compute_matrix#fulu
|
||||
- verify_partial_data_column_header_inclusion_proof#fulu
|
||||
- verify_partial_data_column_sidecar_kzg_proofs#fulu
|
||||
- get_blob_parameters#fulu
|
||||
- get_data_column_sidecars_from_block#fulu
|
||||
- get_data_column_sidecars_from_column_sidecar#fulu
|
||||
- recover_matrix#fulu
|
||||
|
||||
# Not implemented: gloas (future fork)
|
||||
# gloas
|
||||
- compute_ptc#gloas
|
||||
- initialize_ptc_window#gloas
|
||||
- is_payload_data_available#gloas
|
||||
- is_pending_validator#gloas
|
||||
- process_ptc_window#gloas
|
||||
- compute_balance_weighted_acceptance#gloas
|
||||
- compute_balance_weighted_selection#gloas
|
||||
- compute_fork_version#gloas
|
||||
@@ -368,51 +425,65 @@ exceptions:
|
||||
- verify_execution_payload_bid_signature#gloas
|
||||
- add_builder_to_registry#gloas
|
||||
- apply_deposit_for_builder#gloas
|
||||
- apply_withdrawals#capella
|
||||
- apply_withdrawals#gloas
|
||||
- can_builder_cover_bid#gloas
|
||||
- compute_proposer_score#phase0
|
||||
- convert_builder_index_to_validator_index#gloas
|
||||
- convert_validator_index_to_builder_index#gloas
|
||||
- get_attestation_score#gloas
|
||||
- get_attestation_score#phase0
|
||||
- get_balance_after_withdrawals#capella
|
||||
- get_builder_from_deposit#gloas
|
||||
- get_builder_withdrawals#gloas
|
||||
- get_builders_sweep_withdrawals#gloas
|
||||
- get_index_for_new_builder#gloas
|
||||
- get_pending_balance_to_withdraw_for_builder#gloas
|
||||
- get_pending_partial_withdrawals#electra
|
||||
- get_proposer_preferences_signature#gloas
|
||||
- get_upcoming_proposal_slots#gloas
|
||||
- get_validators_sweep_withdrawals#capella
|
||||
- get_validators_sweep_withdrawals#electra
|
||||
- initiate_builder_exit#gloas
|
||||
- is_active_builder#gloas
|
||||
- is_builder_index#gloas
|
||||
- is_data_available#gloas
|
||||
- is_eligible_for_partial_withdrawals#electra
|
||||
- is_head_late#gloas
|
||||
- is_head_weak#gloas
|
||||
- is_parent_strong#gloas
|
||||
- is_proposer_equivocation#phase0
|
||||
- is_valid_proposal_slot#gloas
|
||||
- onboard_builders_from_pending_deposits#gloas
|
||||
- process_deposit_request#gloas
|
||||
- process_voluntary_exit#gloas
|
||||
- record_block_timeliness#gloas
|
||||
- record_block_timeliness#phase0
|
||||
- verify_data_column_sidecar_kzg_proofs#gloas
|
||||
- should_apply_proposer_boost#gloas
|
||||
- update_builder_pending_withdrawals#gloas
|
||||
- update_next_withdrawal_builder_index#gloas
|
||||
- update_next_withdrawal_index#capella
|
||||
- update_next_withdrawal_validator_index#capella
|
||||
- update_payload_expected_withdrawals#gloas
|
||||
- update_pending_partial_withdrawals#electra
|
||||
- update_proposer_boost_root#gloas
|
||||
- update_proposer_boost_root#phase0
|
||||
# heze
|
||||
- compute_fork_version#heze
|
||||
- get_forkchoice_store#heze
|
||||
- get_inclusion_list_bits#heze
|
||||
- get_inclusion_list_committee_assignment#heze
|
||||
- get_inclusion_list_committee#heze
|
||||
- get_inclusion_list_signature#heze
|
||||
- get_inclusion_list_store#heze
|
||||
- get_inclusion_list_submission_due_ms#heze
|
||||
- get_inclusion_list_transactions#heze
|
||||
- get_proposer_inclusion_list_cutoff_ms#heze
|
||||
- get_view_freeze_cutoff_ms#heze
|
||||
- is_inclusion_list_bits_inclusive#heze
|
||||
- is_payload_inclusion_list_satisfied#heze
|
||||
- is_valid_inclusion_list_signature#heze
|
||||
- on_execution_payload#heze
|
||||
- on_inclusion_list#heze
|
||||
- prepare_execution_payload#heze
|
||||
- process_inclusion_list#heze
|
||||
- record_payload_inclusion_list_satisfaction#heze
|
||||
- should_extend_payload#heze
|
||||
- upgrade_to_heze#heze
|
||||
|
||||
presets:
|
||||
# gloas
|
||||
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
|
||||
- BUILDER_REGISTRY_LIMIT#gloas
|
||||
- MAX_BUILDERS_PER_WITHDRAWALS_SWEEP#gloas
|
||||
- MAX_PAYLOAD_ATTESTATIONS#gloas
|
||||
- PTC_SIZE#gloas
|
||||
# heze
|
||||
- INCLUSION_LIST_COMMITTEE_SIZE#heze
|
||||
8
.github/workflows/check-specrefs.yml
vendored
8
.github/workflows/check-specrefs.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
- name: Check version consistency
|
||||
run: |
|
||||
WORKSPACE_VERSION=$(grep 'consensus_spec_version = ' WORKSPACE | sed 's/.*"\(.*\)"/\1/')
|
||||
ETHSPECIFY_VERSION=$(grep '^version:' specrefs/.ethspecify.yml | sed 's/version: //')
|
||||
ETHSPECIFY_VERSION=$(grep '^version:' .ethspecify.yml | sed 's/version: //')
|
||||
if [ "$WORKSPACE_VERSION" != "$ETHSPECIFY_VERSION" ]; then
|
||||
echo "Version mismatch between WORKSPACE and ethspecify"
|
||||
echo " WORKSPACE: $WORKSPACE_VERSION"
|
||||
echo " specrefs/.ethspecify.yml: $ETHSPECIFY_VERSION"
|
||||
echo " .ethspecify.yml: $ETHSPECIFY_VERSION"
|
||||
exit 1
|
||||
else
|
||||
echo "Versions match: $WORKSPACE_VERSION"
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
run: python3 -mpip install ethspecify
|
||||
|
||||
- name: Update spec references
|
||||
run: ethspecify process --path=specrefs
|
||||
run: ethspecify
|
||||
|
||||
- name: Check for differences
|
||||
run: |
|
||||
@@ -40,4 +40,4 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Check spec references
|
||||
run: ethspecify check --path=specrefs
|
||||
run: ethspecify check
|
||||
|
||||
16
.github/workflows/clang-format.yml
vendored
16
.github/workflows/clang-format.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: Protobuf Format
|
||||
|
||||
on:
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
@@ -12,10 +12,14 @@ jobs:
|
||||
clang-format-checking:
|
||||
runs-on: ubuntu-4
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# Is this step failing for you?
|
||||
- uses: actions/checkout@v6
|
||||
# Is this step failing for you?
|
||||
# Run: clang-format -i proto/**/*.proto
|
||||
# See: https://clang.llvm.org/docs/ClangFormat.html
|
||||
- uses: RafikFarhad/clang-format-github-action@v3
|
||||
with:
|
||||
sources: "proto/**/*.proto"
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y clang-format
|
||||
- name: Check protobuf formatting
|
||||
run: |
|
||||
clang-format --style=LLVM --dry-run --Werror proto/**/*.proto
|
||||
|
||||
67
.github/workflows/sbom-export.yaml
vendored
Normal file
67
.github/workflows/sbom-export.yaml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
name: SBOM Export & Centralize
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
schedule:
|
||||
- cron: '50 21 * * 2'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
generate-and-upload:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Source Code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for recent changes
|
||||
id: check
|
||||
run: |
|
||||
if [ -z "$(git log --since='7 days ago' --oneline | head -1)" ]; then
|
||||
echo "No commits in the last 7 days, skipping SBOM generation."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Generate CycloneDX SBOM via cdxgen
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
--user "$(id -u):$(id -g)" \
|
||||
-v /tmp:/tmp \
|
||||
-v "${{ github.workspace }}:/app:rw" \
|
||||
-e FETCH_LICENSE=true \
|
||||
-e GITHUB_TOKEN \
|
||||
ghcr.io/cdxgen/cdxgen:v12.1.1 \
|
||||
-r /app \
|
||||
-o /app/sbom.cdx.json \
|
||||
--no-install-deps \
|
||||
--spec-version 1.6
|
||||
|
||||
if [ ! -s sbom.cdx.json ]; then
|
||||
echo "::error::cdxgen SBOM generation failed or returned empty."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SBOM generated successfully:"
|
||||
ls -lh sbom.cdx.json
|
||||
|
||||
- name: Upload SBOM to Dependency Track
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
env:
|
||||
DT_API_KEY: ${{ secrets.DEPENDENCY_TRACK_API_KEY }}
|
||||
DT_URL: ${{ secrets.DEPENDENCY_TRACK_URL }}
|
||||
run: |
|
||||
REPO_NAME=${GITHUB_REPOSITORY##*/}
|
||||
|
||||
curl -sf -X POST "${DT_URL}/api/v1/bom" \
|
||||
-H "X-Api-Key: ${DT_API_KEY}" \
|
||||
-F "autoCreate=true" \
|
||||
-F "projectName=${REPO_NAME}" \
|
||||
-F "projectVersion=${{ github.ref_name }}" \
|
||||
-F "bom=@sbom.cdx.json"
|
||||
|
||||
echo "SBOM uploaded to Dependency Track for ${REPO_NAME}@${{ github.ref_name }}"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -44,6 +44,3 @@ tmp
|
||||
|
||||
# spectest coverage reports
|
||||
report.txt
|
||||
|
||||
# execution client data
|
||||
execution/
|
||||
|
||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -4,6 +4,116 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
|
||||
|
||||
## [v7.1.3](https://github.com/prysmaticlabs/prysm/compare/v7.1.2...v7.1.3) - 2026-03-18
|
||||
|
||||
This release brings extensive Gloas (next fork) groundwork, a major logging infrastructure overhaul, and numerous performance optimizations across the beacon chain. A security update to go-ethereum v1.16.8 is also included.
|
||||
|
||||
Release highlights:
|
||||
|
||||
- **Gloas fork preparation**: Builder registry, bid processing, payload attestation, proposer slashing, slot processing, block API endpoints, and duty timing intervals are all wired up.
|
||||
- **Logging revamp**: New ephemeral debug logfile (24h retention, enabled by default), per-package loggers with CI enforcement, per-hook verbosity control (`--log.vmodule`), and a version banner at startup.
|
||||
- **Performance**: Forkchoice updates moved to background, post-Electra attestation data cached per slot, parallel data column cache warmup, reduced heap allocations in SSZ marshaling and `MixInLength`, and proposer preprocessing behind a feature flag.
|
||||
- **Validator client**: gRPC fallback now matches the REST API implementation — both connect only to fully synced nodes. The gRPC health endpoint returns an error on syncing/optimistic status.
|
||||
- **Security**: go-ethereum updated to v1.16.8; fixed an authentication bypass on `/v2/validator/*` endpoints.
|
||||
- **State storage**: Initial support for the `hdiff` state-diff feature — migration-to-cold and DB initialization are now available behind feature flags.
|
||||
|
||||
There are no known security issues in this release. Operators can update at their convenience.
|
||||
|
||||
### Added
|
||||
|
||||
- Use the head state to validate attestations for the previous epoch if head is compatible with the target checkpoint. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16109)
|
||||
- Added separate logrus hooks for handling the formatting and output of terminal logs vs log-file logs, instead of the. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16102)
|
||||
- Batch publish data columns for faster data propogation. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16183)
|
||||
- `--disable-get-blobs-v2` flag. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16155)
|
||||
- Update spectests to v1.7.0-alpha.0. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16219)
|
||||
- Added basic Gloas builder support (`Builder` message and `BeaconStateGloas` `builders`/`next_withdrawal_builder_index` fields). [[PR]](https://github.com/prysmaticlabs/prysm/pull/16164)
|
||||
- Added an ephemeral debug logfile that for beacon and validator nodes that captures debug-level logs for 24 hours. It. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16108)
|
||||
- Add a feature flag to pass spectests with low validator count. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16231)
|
||||
- Add feature flag `--enable-proposer-preprocessing` to process the block and verify signatures before proposing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15920)
|
||||
- Add `ProofByFieldIndex` to generalize merkle proof generation for `BeaconState`. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15443)
|
||||
- Update spectests to v1.7.0-alpha-1. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16246)
|
||||
- Add feature flag to use hashtree instead of gohashtre. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16216)
|
||||
- Migrate to cold with the hdiff feature. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16049)
|
||||
- Adding basic fulu fork transition support for mainnet and minimal e2e tests (multi scenario is not included). [[PR]](https://github.com/prysmaticlabs/prysm/pull/15640)
|
||||
- `commitment_count_in_gossip_processed_blocks` gauge metric to track the number of blob KZG commitments in processed beacon blocks. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16254)
|
||||
- Add Gloas latest execution bid processing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15638)
|
||||
- Added shell completion support for `beacon-chain` and `validator` CLI tools. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16245)
|
||||
- add pending payments processing and quorum threshold, plus spectests and state hooks (rotate/append). [[PR]](https://github.com/prysmaticlabs/prysm/pull/15655)
|
||||
- Add slot processing with execution payload availability updates. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15730)
|
||||
- Implement modified proposer slashing for gloas. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16212)
|
||||
- Added missing beacon config in fulu so that the presets don't go missing in /eth/v1/config/spec beacon api. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16170)
|
||||
- Close opened file in data_column.go. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16274)
|
||||
- Flag `--log.vmodule` to set per-package verbosity levels for logging. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16272)
|
||||
- Added a version log at startup to display the version of the build. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16283)
|
||||
- gloas block return support for /eth/v2/beacon/blocks/{block_id} and /eth/v1/beacon/blocks/{block_id}/root endpoints. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16278)
|
||||
- Add Gloas process payload attestation. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15650)
|
||||
- Initialize db with state-diff feature flag. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16203)
|
||||
- Gloas-specific timing intervals for validator attestation, aggregation, and sync duties. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16291)
|
||||
- Added new proofCollector type to ssz-query. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16177)
|
||||
- Added README for maintaining specrefs. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16302)
|
||||
- The ability to download the nightly reference tests from a specific day. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16298)
|
||||
- Set beacon node options after reading the config file. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16320)
|
||||
- Implement finalization-based eviction for `CheckpointStateCache`. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16458)
|
||||
|
||||
### Changed
|
||||
|
||||
- Performance improvement in ProcessConsolidationRequests: Use more performance HasPendingBalanceToWithdraw instead of PendingBalanceToWithdraw as no need to calculate full total pending balance. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16189)
|
||||
- Extend `httperror` analyzer to more functions. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16186)
|
||||
- Do not check block signature on state transition. [[PR]](https://github.com/prysmaticlabs/prysm/pull/14820)
|
||||
- Notify the engine about forkchoice updates in the background. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16149)
|
||||
- Use a separate context when updating the slot cache. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16209)
|
||||
- Data column sidecars cache warmup: Process in parallel all sidecars for a given epoch. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16207)
|
||||
- Use lookahead to validate data column sidecar proposer index. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16202)
|
||||
- Summarize DEBUG log corresponding to incoming via gossip data column sidecar. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16210)
|
||||
- Added a log.go file for every important package with a logger variable containing a `package` field set to the package. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16059)
|
||||
- Added a CI check to ensure every important package has a log.go file with the correct `package` field. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16059)
|
||||
- Changed the log formatter to use this `package` field instead of the previous `prefix` field. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16059)
|
||||
- Replaced `time.Sleep` with `require.Eventually` polling in tests to fix flaky behavior caused by race conditions between goroutines and assertions. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16217)
|
||||
- changed IsHealthy check to IsReady for validator client's interpretation from /eth/v1/node/health, 206 will now return false as the node is syncing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16167)
|
||||
- Performance improvement in state (MarshalSSZTo): use copy() instead of byte-by-byte loop which isn't required. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16222)
|
||||
- Moved verbosity settings to be configurable per hook, rather than just globally. This allows us to control the. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16106)
|
||||
- updated go ethereum to 1.16.7. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15640)
|
||||
- Use dependent root and target root to verify data column proposer index. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16250)
|
||||
- post electra we now call attestation data once per slot and use a cache for subsequent requests. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16236)
|
||||
- Avoid unnessary heap allocation while calling MixInLength. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16251)
|
||||
- Log commitments instead of indices in missingCommitError. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16258)
|
||||
- Added some defensive checks to prevent overflows in block batch requests. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16227)
|
||||
- gRPC health endpoint will now return an error on syncing or optimistic status showing that it's unavailable. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16294)
|
||||
- Sample PTC per committee to reduce allocations. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16293)
|
||||
- gRPC fallback now matches rest api implementation and will also check and connect to only synced nodes. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16215)
|
||||
- Improved node fallback logs. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16316)
|
||||
- Improved integrations with ethspecify so specrefs can be used throughout the codebase. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16304)
|
||||
- Fixed the logging issue described in #16314. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16322)
|
||||
|
||||
### Removed
|
||||
|
||||
- removed github.com/MariusVanDerWijden/FuzzyVM and github.com/MariusVanDerWijden/tx-fuzz due to lack of support post 1.16.7, only used in e2e for transaction fuzzing. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15640)
|
||||
- Remove unused `delay` parameter from `fetchOriginDataColumnSidecars` function. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16262)
|
||||
- Batching of KZG verification for incoming via gossip data column sidecars. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16240)
|
||||
- `--disable-get-blobs-v2` flag from help. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16265)
|
||||
- gRPC resolver for load balancing, the new implementation matches rest api's so we should remove the resolver so it's handled the same way for consistency. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16215)
|
||||
|
||||
### Fixed
|
||||
|
||||
- avoid panic when fork schedule is empty [#16175](https://github.com/OffchainLabs/prysm/pull/16175). [[PR]](https://github.com/prysmaticlabs/prysm/pull/16175)
|
||||
- Fix validation logic for `--backfill-oldest-slot`, which was rejecting slots newer than 1056767. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16173)
|
||||
- Don't call trace.WithMaxExportBatchSize(trace.DefaultMaxExportBatchSize) twice. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16211)
|
||||
- When adding the `--[semi-]supernode` flag, update the ealiest available slot accordingly. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16230)
|
||||
- fixed broken and old links to actual. [[PR]](https://github.com/prysmaticlabs/prysm/pull/15856)
|
||||
- stop SlotIntervalTicker goroutine leaks [#16241](https://github.com/OffchainLabs/prysm/pull/16241). [[PR]](https://github.com/prysmaticlabs/prysm/pull/16241)
|
||||
- Fix `prysmctl testnet generate-genesis` to use the timestamp from `--geth-genesis-json-in` when `--genesis-time` is not explicitly provided. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16239)
|
||||
- Prevent authentication bypass on direct `/v2/validator/*` endpoints by enforcing auth checks for non-public routes. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16226)
|
||||
- Fixed a typo: AggregrateDueBPS -> AggregateDueBPS. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16194)
|
||||
- Fixed a bug in `hack/check-logs.sh` where untracked files were ignored. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16287)
|
||||
- Fix hashtree release builds. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16288)
|
||||
- Fix Bazel build failure on macOS x86_64 (darwin_amd64) (adds missing assembly stub to hashtree patch). [[PR]](https://github.com/prysmaticlabs/prysm/pull/16281)
|
||||
- a potential race condition when switching hosts quickly and reconnecting to same host on an old connection. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16316)
|
||||
- Fixed a bug where `cmd/beacon-chain/execution` was being ignored by `hack/gen-logs.sh` due to a `.gitignore` rule. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16328)
|
||||
|
||||
### Security
|
||||
|
||||
- Update go-ethereum to v1.16.8. [[PR]](https://github.com/prysmaticlabs/prysm/pull/16252)
|
||||
|
||||
## [v7.1.2](https://github.com/prysmaticlabs/prysm/compare/v7.1.1...v7.1.2) - 2026-01-07
|
||||
|
||||
Happy new year! This patch release is very small. The main improvement is better management of pending attestation aggregation via [PR 16153](https://github.com/OffchainLabs/prysm/pull/16153).
|
||||
@@ -4046,4 +4156,4 @@ There are no security updates in this release.
|
||||
|
||||
# Older than v2.0.0
|
||||
|
||||
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases
|
||||
For changelog history for releases older than v2.0.0, please refer to https://github.com/prysmaticlabs/prysm/releases
|
||||
10
WORKSPACE
10
WORKSPACE
@@ -273,16 +273,16 @@ filegroup(
|
||||
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
|
||||
)
|
||||
|
||||
consensus_spec_version = "v1.7.0-alpha.1"
|
||||
consensus_spec_version = "v1.7.0-alpha.4"
|
||||
|
||||
load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests")
|
||||
|
||||
consensus_spec_tests(
|
||||
name = "consensus_spec_tests",
|
||||
flavors = {
|
||||
"general": "sha256-j5R3jA7Oo4OSDMTvpMuD+8RomaCByeFSwtfkq6fL0Zg=",
|
||||
"minimal": "sha256-tdTqByoyswOS4r6OxFmo70y2BP7w1TgEok+gf4cbxB0=",
|
||||
"mainnet": "sha256-5gB4dt6SnSDKzdBc06VedId3NkgvSYyv9n9FRxWKwYI=",
|
||||
"general": "sha256-kNJxuhCtW4RbuS9nb4U6JXHlPgTSg6G3hWeHFVB9gZ4=",
|
||||
"minimal": "sha256-U1tCkXxtdI6mkEdk80i8z9LU2hAyf7Ztz5SBYo5oMzo=",
|
||||
"mainnet": "sha256-Ga8VDOcNhTTdXDj8tSyBVYrwya9f1HO94ehJ5vv91r4=",
|
||||
},
|
||||
version = consensus_spec_version,
|
||||
)
|
||||
@@ -298,7 +298,7 @@ filegroup(
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
integrity = "sha256-J+43DrK1pF658kTXTwMS6zGf4KDjvas++m8w2a8swpg=",
|
||||
integrity = "sha256-XHu5K/65mue+5po63L9yGTFjGfU1RGj4S56dmcHc2Rs=",
|
||||
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
|
||||
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
|
||||
)
|
||||
|
||||
19
api/fallback/BUILD.bazel
Normal file
19
api/fallback/BUILD.bazel
Normal file
@@ -0,0 +1,19 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"fallback.go",
|
||||
"log.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/api/fallback",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["@com_github_sirupsen_logrus//:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["fallback_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//testing/assert:go_default_library"],
|
||||
)
|
||||
66
api/fallback/fallback.go
Normal file
66
api/fallback/fallback.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// HostProvider is the subset of connection-provider methods that EnsureReady
|
||||
// needs. Both grpc.GrpcConnectionProvider and rest.RestConnectionProvider
|
||||
// satisfy this interface.
|
||||
type HostProvider interface {
|
||||
Hosts() []string
|
||||
CurrentHost() string
|
||||
SwitchHost(index int) error
|
||||
}
|
||||
|
||||
// ReadyChecker can report whether the current endpoint is ready.
|
||||
// iface.NodeClient satisfies this implicitly.
|
||||
type ReadyChecker interface {
|
||||
IsReady(ctx context.Context) bool
|
||||
}
|
||||
|
||||
// EnsureReady iterates through the configured hosts and returns true as soon as
|
||||
// one responds as ready. It starts from the provider's current host and wraps
|
||||
// around using modular arithmetic, performing failover when a host is not ready.
|
||||
func EnsureReady(ctx context.Context, provider HostProvider, checker ReadyChecker) bool {
|
||||
hosts := provider.Hosts()
|
||||
numHosts := len(hosts)
|
||||
startingHost := provider.CurrentHost()
|
||||
var attemptedHosts []string
|
||||
|
||||
// Find current index
|
||||
currentIdx := 0
|
||||
for i, h := range hosts {
|
||||
if h == startingHost {
|
||||
currentIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := range numHosts {
|
||||
if checker.IsReady(ctx) {
|
||||
if len(attemptedHosts) > 0 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"previous": startingHost,
|
||||
"current": provider.CurrentHost(),
|
||||
"tried": attemptedHosts,
|
||||
}).Warn("Switched to responsive beacon node")
|
||||
}
|
||||
return true
|
||||
}
|
||||
attemptedHosts = append(attemptedHosts, provider.CurrentHost())
|
||||
|
||||
// Try next host if not the last iteration
|
||||
if i < numHosts-1 {
|
||||
nextIdx := (currentIdx + i + 1) % numHosts
|
||||
if err := provider.SwitchHost(nextIdx); err != nil {
|
||||
log.WithError(err).Error("Failed to switch host")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.WithField("tried", attemptedHosts).Warn("No responsive beacon node found")
|
||||
return false
|
||||
}
|
||||
94
api/fallback/fallback_test.go
Normal file
94
api/fallback/fallback_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package fallback
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
)
|
||||
|
||||
// mockHostProvider is a minimal HostProvider for unit tests.
|
||||
type mockHostProvider struct {
|
||||
hosts []string
|
||||
hostIndex int
|
||||
}
|
||||
|
||||
func (m *mockHostProvider) Hosts() []string { return m.hosts }
|
||||
func (m *mockHostProvider) CurrentHost() string {
|
||||
return m.hosts[m.hostIndex%len(m.hosts)]
|
||||
}
|
||||
func (m *mockHostProvider) SwitchHost(index int) error { m.hostIndex = index; return nil }
|
||||
|
||||
// mockReadyChecker records per-call IsReady results in sequence.
|
||||
type mockReadyChecker struct {
|
||||
results []bool
|
||||
idx int
|
||||
}
|
||||
|
||||
func (m *mockReadyChecker) IsReady(_ context.Context) bool {
|
||||
if m.idx >= len(m.results) {
|
||||
return false
|
||||
}
|
||||
r := m.results[m.idx]
|
||||
m.idx++
|
||||
return r
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostReady(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostNotReady(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_SingleHostError(t *testing.T) {
|
||||
provider := &mockHostProvider{hosts: []string{"http://host1:3500"}, hostIndex: 0}
|
||||
checker := &mockReadyChecker{results: []bool{false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsFirstReady(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsFailoverToSecond(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{false, true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 1, provider.hostIndex)
|
||||
}
|
||||
|
||||
func TestEnsureReady_MultipleHostsNoneReady(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host1:3500", "http://host2:3500", "http://host3:3500"},
|
||||
hostIndex: 0,
|
||||
}
|
||||
checker := &mockReadyChecker{results: []bool{false, false, false}}
|
||||
assert.Equal(t, false, EnsureReady(t.Context(), provider, checker))
|
||||
}
|
||||
|
||||
func TestEnsureReady_WrapAroundFromNonZeroIndex(t *testing.T) {
|
||||
provider := &mockHostProvider{
|
||||
hosts: []string{"http://host0:3500", "http://host1:3500", "http://host2:3500"},
|
||||
hostIndex: 1,
|
||||
}
|
||||
// host1 (start) fails, host2 fails, host0 succeeds
|
||||
checker := &mockReadyChecker{results: []bool{false, false, true}}
|
||||
assert.Equal(t, true, EnsureReady(t.Context(), provider, checker))
|
||||
assert.Equal(t, 0, provider.hostIndex)
|
||||
}
|
||||
9
api/fallback/log.go
Normal file
9
api/fallback/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Code generated by hack/gen-logs.sh; DO NOT EDIT.
|
||||
// This file is created and regenerated automatically. Anything added here might get removed.
|
||||
package fallback
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// The prefix for logs from this package will be the text after the last slash in the package path.
|
||||
// If you wish to change this, you should add your desired name in the runtime/logging/logrus-prefixed-formatter/prefix-replacement.go file.
|
||||
var log = logrus.WithField("package", "api/fallback")
|
||||
@@ -25,6 +25,11 @@ type GrpcConnectionProvider interface {
|
||||
// SwitchHost switches to the endpoint at the given index.
|
||||
// The new connection is created lazily on next CurrentConn() call.
|
||||
SwitchHost(index int) error
|
||||
// ConnectionCounter returns a monotonically increasing counter that increments
|
||||
// each time SwitchHost changes the active endpoint. This allows consumers to
|
||||
// detect connection changes even when the host string returns to a previous value
|
||||
// (e.g., host0 → host1 → host0).
|
||||
ConnectionCounter() uint64
|
||||
// Close closes the current connection.
|
||||
Close()
|
||||
}
|
||||
@@ -38,6 +43,7 @@ type grpcConnectionProvider struct {
|
||||
// Current connection state (protected by mutex)
|
||||
currentIndex uint64
|
||||
conn *grpc.ClientConn
|
||||
connCounter uint64
|
||||
|
||||
mu sync.Mutex
|
||||
closed bool
|
||||
@@ -138,6 +144,7 @@ func (p *grpcConnectionProvider) SwitchHost(index int) error {
|
||||
|
||||
p.conn = nil // Clear immediately - new connection created lazily
|
||||
p.currentIndex = uint64(index)
|
||||
p.connCounter++
|
||||
|
||||
// Close old connection asynchronously to avoid blocking the caller
|
||||
if oldConn != nil {
|
||||
@@ -155,6 +162,12 @@ func (p *grpcConnectionProvider) SwitchHost(index int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) ConnectionCounter() uint64 {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.connCounter
|
||||
}
|
||||
|
||||
func (p *grpcConnectionProvider) Close() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
@@ -4,17 +4,24 @@ import "google.golang.org/grpc"
|
||||
|
||||
// MockGrpcProvider implements GrpcConnectionProvider for testing.
|
||||
type MockGrpcProvider struct {
|
||||
MockConn *grpc.ClientConn
|
||||
MockHosts []string
|
||||
MockConn *grpc.ClientConn
|
||||
MockHosts []string
|
||||
CurrentIndex int
|
||||
ConnCounter uint64
|
||||
}
|
||||
|
||||
func (m *MockGrpcProvider) CurrentConn() *grpc.ClientConn { return m.MockConn }
|
||||
func (m *MockGrpcProvider) CurrentHost() string {
|
||||
if len(m.MockHosts) > 0 {
|
||||
return m.MockHosts[0]
|
||||
return m.MockHosts[m.CurrentIndex]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
func (m *MockGrpcProvider) Hosts() []string { return m.MockHosts }
|
||||
func (m *MockGrpcProvider) SwitchHost(int) error { return nil }
|
||||
func (m *MockGrpcProvider) Close() {}
|
||||
func (m *MockGrpcProvider) Hosts() []string { return m.MockHosts }
|
||||
func (m *MockGrpcProvider) SwitchHost(idx int) error {
|
||||
m.CurrentIndex = idx
|
||||
m.ConnCounter++
|
||||
return nil
|
||||
}
|
||||
func (m *MockGrpcProvider) ConnectionCounter() uint64 { return m.ConnCounter }
|
||||
func (m *MockGrpcProvider) Close() {}
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
// MockRestProvider implements RestConnectionProvider for testing.
|
||||
type MockRestProvider struct {
|
||||
MockClient *http.Client
|
||||
MockHandler RestHandler
|
||||
MockHandler Handler
|
||||
MockHosts []string
|
||||
HostIndex int
|
||||
}
|
||||
|
||||
func (m *MockRestProvider) HttpClient() *http.Client { return m.MockClient }
|
||||
func (m *MockRestProvider) RestHandler() RestHandler { return m.MockHandler }
|
||||
func (m *MockRestProvider) Handler() Handler { return m.MockHandler }
|
||||
func (m *MockRestProvider) CurrentHost() string {
|
||||
if len(m.MockHosts) > 0 {
|
||||
return m.MockHosts[m.HostIndex%len(m.MockHosts)]
|
||||
@@ -25,25 +25,22 @@ func (m *MockRestProvider) CurrentHost() string {
|
||||
func (m *MockRestProvider) Hosts() []string { return m.MockHosts }
|
||||
func (m *MockRestProvider) SwitchHost(index int) error { m.HostIndex = index; return nil }
|
||||
|
||||
// MockRestHandler implements RestHandler for testing.
|
||||
type MockRestHandler struct {
|
||||
MockHost string
|
||||
MockClient *http.Client
|
||||
// MockHandler implements Handler for testing.
|
||||
type MockHandler struct {
|
||||
MockHost string
|
||||
}
|
||||
|
||||
func (m *MockRestHandler) Get(_ context.Context, _ string, _ any) error { return nil }
|
||||
func (m *MockRestHandler) GetStatusCode(_ context.Context, _ string) (int, error) {
|
||||
func (m *MockHandler) Get(_ context.Context, _ string, _ any) error { return nil }
|
||||
func (m *MockHandler) GetStatusCode(_ context.Context, _ string) (int, error) {
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
func (m *MockRestHandler) GetSSZ(_ context.Context, _ string) ([]byte, http.Header, error) {
|
||||
func (m *MockHandler) GetSSZ(_ context.Context, _ string) ([]byte, http.Header, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (m *MockRestHandler) Post(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer, _ any) error {
|
||||
func (m *MockHandler) Post(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer, _ any) error {
|
||||
return nil
|
||||
}
|
||||
func (m *MockRestHandler) PostSSZ(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer) ([]byte, http.Header, error) {
|
||||
func (m *MockHandler) PostSSZ(_ context.Context, _ string, _ map[string]string, _ *bytes.Buffer) ([]byte, http.Header, error) {
|
||||
return nil, nil, nil
|
||||
}
|
||||
func (m *MockRestHandler) HttpClient() *http.Client { return m.MockClient }
|
||||
func (m *MockRestHandler) Host() string { return m.MockHost }
|
||||
func (m *MockRestHandler) SwitchHost(host string) { m.MockHost = host }
|
||||
func (m *MockHandler) Host() string { return m.MockHost }
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
type RestConnectionProvider interface {
|
||||
// HttpClient returns the configured HTTP client with headers, timeout, and optional tracing.
|
||||
HttpClient() *http.Client
|
||||
// RestHandler returns the REST handler for making API requests.
|
||||
RestHandler() RestHandler
|
||||
// Handler returns the REST handler for making API requests.
|
||||
Handler() Handler
|
||||
// CurrentHost returns the current REST API endpoint URL.
|
||||
CurrentHost() string
|
||||
// Hosts returns all configured REST API endpoint URLs.
|
||||
@@ -54,7 +54,7 @@ func WithTracing() RestConnectionProviderOption {
|
||||
type restConnectionProvider struct {
|
||||
endpoints []string
|
||||
httpClient *http.Client
|
||||
restHandler RestHandler
|
||||
restHandler *handler
|
||||
currentIndex atomic.Uint64
|
||||
timeout time.Duration
|
||||
headers map[string][]string
|
||||
@@ -96,7 +96,7 @@ func NewRestConnectionProvider(endpoint string, opts ...RestConnectionProviderOp
|
||||
}
|
||||
|
||||
// Create the REST handler with the HTTP client and initial host
|
||||
p.restHandler = newRestHandler(*p.httpClient, endpoints[0])
|
||||
p.restHandler = newHandler(*p.httpClient, endpoints[0])
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"endpoints": endpoints,
|
||||
@@ -124,7 +124,7 @@ func (p *restConnectionProvider) HttpClient() *http.Client {
|
||||
return p.httpClient
|
||||
}
|
||||
|
||||
func (p *restConnectionProvider) RestHandler() RestHandler {
|
||||
func (p *restConnectionProvider) Handler() Handler {
|
||||
return p.restHandler
|
||||
}
|
||||
|
||||
|
||||
@@ -21,32 +21,35 @@ import (
|
||||
|
||||
type reqOption func(*http.Request)
|
||||
|
||||
// RestHandler defines the interface for making REST API requests.
|
||||
type RestHandler interface {
|
||||
// Handler defines the interface for making REST API requests.
|
||||
type Handler interface {
|
||||
Get(ctx context.Context, endpoint string, resp any) error
|
||||
GetStatusCode(ctx context.Context, endpoint string) (int, error)
|
||||
GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error)
|
||||
Post(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer, resp any) error
|
||||
PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error)
|
||||
HttpClient() *http.Client
|
||||
Host() string
|
||||
SwitchHost(host string)
|
||||
}
|
||||
|
||||
type restHandler struct {
|
||||
type handler struct {
|
||||
client http.Client
|
||||
host string
|
||||
reqOverrides []reqOption
|
||||
}
|
||||
|
||||
// newRestHandler returns a RestHandler (internal use)
|
||||
func newRestHandler(client http.Client, host string) RestHandler {
|
||||
return NewRestHandler(client, host)
|
||||
// newHandler returns a *handler for internal use within the rest package.
|
||||
func newHandler(client http.Client, host string) *handler {
|
||||
rh := &handler{
|
||||
client: client,
|
||||
host: host,
|
||||
}
|
||||
rh.appendAcceptOverride()
|
||||
return rh
|
||||
}
|
||||
|
||||
// NewRestHandler returns a RestHandler
|
||||
func NewRestHandler(client http.Client, host string) RestHandler {
|
||||
rh := &restHandler{
|
||||
// NewHandler returns a Handler
|
||||
func NewHandler(client http.Client, host string) Handler {
|
||||
rh := &handler{
|
||||
client: client,
|
||||
host: host,
|
||||
}
|
||||
@@ -57,7 +60,7 @@ func NewRestHandler(client http.Client, host string) RestHandler {
|
||||
// appendAcceptOverride enables the Accept header to be customized at runtime via an environment variable.
|
||||
// This is specified as an env var because it is a niche option that prysm may use for performance testing or debugging
|
||||
// bug which users are unlikely to need. Using an env var keeps the set of user-facing flags cleaner.
|
||||
func (c *restHandler) appendAcceptOverride() {
|
||||
func (c *handler) appendAcceptOverride() {
|
||||
if accept := os.Getenv(params.EnvNameOverrideAccept); accept != "" {
|
||||
c.reqOverrides = append(c.reqOverrides, func(req *http.Request) {
|
||||
req.Header.Set("Accept", accept)
|
||||
@@ -66,18 +69,18 @@ func (c *restHandler) appendAcceptOverride() {
|
||||
}
|
||||
|
||||
// HttpClient returns the underlying HTTP client of the handler
|
||||
func (c *restHandler) HttpClient() *http.Client {
|
||||
func (c *handler) HttpClient() *http.Client {
|
||||
return &c.client
|
||||
}
|
||||
|
||||
// Host returns the underlying HTTP host
|
||||
func (c *restHandler) Host() string {
|
||||
func (c *handler) Host() string {
|
||||
return c.host
|
||||
}
|
||||
|
||||
// Get sends a GET request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *restHandler) Get(ctx context.Context, endpoint string, resp any) error {
|
||||
func (c *handler) Get(ctx context.Context, endpoint string, resp any) error {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -100,7 +103,7 @@ func (c *restHandler) Get(ctx context.Context, endpoint string, resp any) error
|
||||
// GetStatusCode sends a GET request and returns only the HTTP status code.
|
||||
// This is useful for endpoints like /eth/v1/node/health that communicate status via HTTP codes
|
||||
// (200 = ready, 206 = syncing, 503 = unavailable) rather than response bodies.
|
||||
func (c *restHandler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
|
||||
func (c *handler) GetStatusCode(ctx context.Context, endpoint string) (int, error) {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -119,7 +122,7 @@ func (c *restHandler) GetStatusCode(ctx context.Context, endpoint string) (int,
|
||||
return httpResp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (c *restHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
func (c *handler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, error) {
|
||||
url := c.host + endpoint
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
@@ -174,7 +177,7 @@ func (c *restHandler) GetSSZ(ctx context.Context, endpoint string) ([]byte, http
|
||||
|
||||
// Post sends a POST request and decodes the response body as a JSON object into the passed in object.
|
||||
// If an HTTP error is returned, the body is decoded as a DefaultJsonError JSON object and returned as the first return value.
|
||||
func (c *restHandler) Post(
|
||||
func (c *handler) Post(
|
||||
ctx context.Context,
|
||||
apiEndpoint string,
|
||||
headers map[string]string,
|
||||
@@ -210,7 +213,7 @@ func (c *restHandler) Post(
|
||||
}
|
||||
|
||||
// PostSSZ sends a POST request and prefers an SSZ (application/octet-stream) response body.
|
||||
func (c *restHandler) PostSSZ(
|
||||
func (c *handler) PostSSZ(
|
||||
ctx context.Context,
|
||||
apiEndpoint string,
|
||||
headers map[string]string,
|
||||
@@ -311,6 +314,6 @@ func decodeResp(httpResp *http.Response, resp any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *restHandler) SwitchHost(host string) {
|
||||
func (c *handler) SwitchHost(host string) {
|
||||
c.host = host
|
||||
}
|
||||
|
||||
@@ -92,7 +92,11 @@ func AcceptEncodingHeaderHandler() Middleware {
|
||||
return
|
||||
}
|
||||
|
||||
gz := gzip.NewWriter(w)
|
||||
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed)
|
||||
if err != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
gzipRW := &gzipResponseWriter{gz: gz, ResponseWriter: w}
|
||||
defer func() {
|
||||
if !gzipRW.zip {
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"conversions_blob.go",
|
||||
"conversions_block.go",
|
||||
"conversions_block_execution.go",
|
||||
"conversions_gloas.go",
|
||||
"conversions_lightclient.go",
|
||||
"conversions_state.go",
|
||||
"endpoints_beacon.go",
|
||||
@@ -57,10 +58,13 @@ go_test(
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//testing/assert:go_default_library",
|
||||
"//testing/require:go_default_library",
|
||||
"//testing/util:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common/hexutil:go_default_library",
|
||||
],
|
||||
|
||||
@@ -509,17 +509,17 @@ func (s *SignedBlindedBeaconBlockFulu) SigString() string {
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type ExecutionPayloadBid struct {
|
||||
ParentBlockHash string `json:"parent_block_hash"`
|
||||
ParentBlockRoot string `json:"parent_block_root"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
Slot string `json:"slot"`
|
||||
Value string `json:"value"`
|
||||
ExecutionPayment string `json:"execution_payment"`
|
||||
BlobKzgCommitmentsRoot string `json:"blob_kzg_commitments_root"`
|
||||
ParentBlockHash string `json:"parent_block_hash"`
|
||||
ParentBlockRoot string `json:"parent_block_root"`
|
||||
BlockHash string `json:"block_hash"`
|
||||
PrevRandao string `json:"prev_randao"`
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
GasLimit string `json:"gas_limit"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
Slot string `json:"slot"`
|
||||
Value string `json:"value"`
|
||||
ExecutionPayment string `json:"execution_payment"`
|
||||
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
|
||||
}
|
||||
|
||||
type SignedExecutionPayloadBid struct {
|
||||
@@ -540,6 +540,12 @@ type PayloadAttestation struct {
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type PayloadAttestationMessage struct {
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Data *PayloadAttestationData `json:"data"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
type BeaconBlockBodyGloas struct {
|
||||
RandaoReveal string `json:"randao_reveal"`
|
||||
Eth1Data *Eth1Data `json:"eth1_data"`
|
||||
@@ -577,3 +583,17 @@ func (s *SignedBeaconBlockGloas) MessageRawJson() ([]byte, error) {
|
||||
func (s *SignedBeaconBlockGloas) SigString() string {
|
||||
return s.Signature
|
||||
}
|
||||
|
||||
type ExecutionPayloadEnvelope struct {
|
||||
Payload *ExecutionPayloadDeneb `json:"payload"`
|
||||
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
BeaconBlockRoot string `json:"beacon_block_root"`
|
||||
Slot string `json:"slot"`
|
||||
StateRoot string `json:"state_root"`
|
||||
}
|
||||
|
||||
type SignedExecutionPayloadEnvelope struct {
|
||||
Message *ExecutionPayloadEnvelope `json:"message"`
|
||||
Signature string `json:"signature"`
|
||||
}
|
||||
|
||||
@@ -2939,18 +2939,22 @@ func SignedExecutionPayloadBidFromConsensus(b *eth.SignedExecutionPayloadBid) *S
|
||||
}
|
||||
|
||||
func ExecutionPayloadBidFromConsensus(b *eth.ExecutionPayloadBid) *ExecutionPayloadBid {
|
||||
blobKzgCommitments := make([]string, len(b.BlobKzgCommitments))
|
||||
for i := range b.BlobKzgCommitments {
|
||||
blobKzgCommitments[i] = hexutil.Encode(b.BlobKzgCommitments[i])
|
||||
}
|
||||
return &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(b.BlockHash),
|
||||
PrevRandao: hexutil.Encode(b.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(b.FeeRecipient),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
|
||||
Slot: fmt.Sprintf("%d", b.Slot),
|
||||
Value: fmt.Sprintf("%d", b.Value),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
|
||||
BlobKzgCommitmentsRoot: hexutil.Encode(b.BlobKzgCommitmentsRoot),
|
||||
ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(b.BlockHash),
|
||||
PrevRandao: hexutil.Encode(b.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(b.FeeRecipient),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
|
||||
Slot: fmt.Sprintf("%d", b.Slot),
|
||||
Value: fmt.Sprintf("%d", b.Value),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2962,6 +2966,14 @@ func PayloadAttestationFromConsensus(pa *eth.PayloadAttestation) *PayloadAttesta
|
||||
}
|
||||
}
|
||||
|
||||
func PayloadAttestationMessageFromConsensus(m *eth.PayloadAttestationMessage) *PayloadAttestationMessage {
|
||||
return &PayloadAttestationMessage{
|
||||
ValidatorIndex: fmt.Sprintf("%d", m.ValidatorIndex),
|
||||
Data: PayloadAttestationDataFromConsensus(m.Data),
|
||||
Signature: hexutil.Encode(m.Signature),
|
||||
}
|
||||
}
|
||||
|
||||
func PayloadAttestationDataFromConsensus(d *eth.PayloadAttestationData) *PayloadAttestationData {
|
||||
return &PayloadAttestationData{
|
||||
BeaconBlockRoot: hexutil.Encode(d.BeaconBlockRoot),
|
||||
@@ -3187,22 +3199,30 @@ func (b *ExecutionPayloadBid) ToConsensus() (*eth.ExecutionPayloadBid, error) {
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "ExecutionPayment")
|
||||
}
|
||||
blobKzgCommitmentsRoot, err := bytesutil.DecodeHexWithLength(b.BlobKzgCommitmentsRoot, fieldparams.RootLength)
|
||||
err = slice.VerifyMaxLength(b.BlobKzgCommitments, fieldparams.MaxBlobCommitmentsPerBlock)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, "BlobKzgCommitmentsRoot")
|
||||
return nil, server.NewDecodeError(err, "BlobKzgCommitments")
|
||||
}
|
||||
blobKzgCommitments := make([][]byte, len(b.BlobKzgCommitments))
|
||||
for i, commitment := range b.BlobKzgCommitments {
|
||||
kzg, err := bytesutil.DecodeHexWithLength(commitment, fieldparams.BLSPubkeyLength)
|
||||
if err != nil {
|
||||
return nil, server.NewDecodeError(err, fmt.Sprintf("BlobKzgCommitments[%d]", i))
|
||||
}
|
||||
blobKzgCommitments[i] = kzg
|
||||
}
|
||||
return ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: parentBlockHash,
|
||||
ParentBlockRoot: parentBlockRoot,
|
||||
BlockHash: blockHash,
|
||||
PrevRandao: prevRandao,
|
||||
FeeRecipient: feeRecipient,
|
||||
GasLimit: gasLimit,
|
||||
BuilderIndex: primitives.BuilderIndex(builderIndex),
|
||||
Slot: primitives.Slot(slot),
|
||||
Value: primitives.Gwei(value),
|
||||
ExecutionPayment: primitives.Gwei(executionPayment),
|
||||
BlobKzgCommitmentsRoot: blobKzgCommitmentsRoot,
|
||||
ParentBlockHash: parentBlockHash,
|
||||
ParentBlockRoot: parentBlockRoot,
|
||||
BlockHash: blockHash,
|
||||
PrevRandao: prevRandao,
|
||||
FeeRecipient: feeRecipient,
|
||||
GasLimit: gasLimit,
|
||||
BuilderIndex: primitives.BuilderIndex(builderIndex),
|
||||
Slot: primitives.Slot(slot),
|
||||
Value: primitives.Gwei(value),
|
||||
ExecutionPayment: primitives.Gwei(executionPayment),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -3263,3 +3283,26 @@ func (d *PayloadAttestationData) ToConsensus() (*eth.PayloadAttestationData, err
|
||||
BlobDataAvailable: d.BlobDataAvailable,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SignedExecutionPayloadEnvelopeFromConsensus converts a proto envelope to the API struct.
|
||||
func SignedExecutionPayloadEnvelopeFromConsensus(e *eth.SignedExecutionPayloadEnvelope) (*SignedExecutionPayloadEnvelope, error) {
|
||||
payload, err := ExecutionPayloadDenebFromConsensus(e.Message.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var requests *ExecutionRequests
|
||||
if e.Message.ExecutionRequests != nil {
|
||||
requests = ExecutionRequestsFromConsensus(e.Message.ExecutionRequests)
|
||||
}
|
||||
return &SignedExecutionPayloadEnvelope{
|
||||
Message: &ExecutionPayloadEnvelope{
|
||||
Payload: payload,
|
||||
ExecutionRequests: requests,
|
||||
BuilderIndex: fmt.Sprintf("%d", e.Message.BuilderIndex),
|
||||
BeaconBlockRoot: hexutil.Encode(e.Message.BeaconBlockRoot),
|
||||
Slot: fmt.Sprintf("%d", e.Message.Slot),
|
||||
StateRoot: hexutil.Encode(e.Message.StateRoot),
|
||||
},
|
||||
Signature: hexutil.Encode(e.Signature),
|
||||
}, nil
|
||||
}
|
||||
|
||||
89
api/server/structs/conversions_gloas.go
Normal file
89
api/server/structs/conversions_gloas.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
func ROExecutionPayloadBidFromConsensus(b interfaces.ROExecutionPayloadBid) *ExecutionPayloadBid {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pbh := b.ParentBlockHash()
|
||||
pbr := b.ParentBlockRoot()
|
||||
bh := b.BlockHash()
|
||||
pr := b.PrevRandao()
|
||||
fr := b.FeeRecipient()
|
||||
commitments := b.BlobKzgCommitments()
|
||||
blobKzgCommitments := make([]string, 0, len(commitments))
|
||||
for _, commitment := range commitments {
|
||||
blobKzgCommitments = append(blobKzgCommitments, hexutil.Encode(commitment))
|
||||
}
|
||||
return &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(pbh[:]),
|
||||
ParentBlockRoot: hexutil.Encode(pbr[:]),
|
||||
BlockHash: hexutil.Encode(bh[:]),
|
||||
PrevRandao: hexutil.Encode(pr[:]),
|
||||
FeeRecipient: hexutil.Encode(fr[:]),
|
||||
GasLimit: fmt.Sprintf("%d", b.GasLimit()),
|
||||
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex()),
|
||||
Slot: fmt.Sprintf("%d", b.Slot()),
|
||||
Value: fmt.Sprintf("%d", b.Value()),
|
||||
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment()),
|
||||
BlobKzgCommitments: blobKzgCommitments,
|
||||
}
|
||||
}
|
||||
|
||||
func BuildersFromConsensus(builders []*ethpb.Builder) []*Builder {
|
||||
newBuilders := make([]*Builder, len(builders))
|
||||
for i, b := range builders {
|
||||
newBuilders[i] = BuilderFromConsensus(b)
|
||||
}
|
||||
return newBuilders
|
||||
}
|
||||
|
||||
func BuilderFromConsensus(b *ethpb.Builder) *Builder {
|
||||
return &Builder{
|
||||
Pubkey: hexutil.Encode(b.Pubkey),
|
||||
Version: hexutil.Encode(b.Version),
|
||||
ExecutionAddress: hexutil.Encode(b.ExecutionAddress),
|
||||
Balance: fmt.Sprintf("%d", b.Balance),
|
||||
DepositEpoch: fmt.Sprintf("%d", b.DepositEpoch),
|
||||
WithdrawableEpoch: fmt.Sprintf("%d", b.WithdrawableEpoch),
|
||||
}
|
||||
}
|
||||
|
||||
func BuilderPendingPaymentsFromConsensus(payments []*ethpb.BuilderPendingPayment) []*BuilderPendingPayment {
|
||||
newPayments := make([]*BuilderPendingPayment, len(payments))
|
||||
for i, p := range payments {
|
||||
newPayments[i] = BuilderPendingPaymentFromConsensus(p)
|
||||
}
|
||||
return newPayments
|
||||
}
|
||||
|
||||
func BuilderPendingPaymentFromConsensus(p *ethpb.BuilderPendingPayment) *BuilderPendingPayment {
|
||||
return &BuilderPendingPayment{
|
||||
Weight: fmt.Sprintf("%d", p.Weight),
|
||||
Withdrawal: BuilderPendingWithdrawalFromConsensus(p.Withdrawal),
|
||||
}
|
||||
}
|
||||
|
||||
func BuilderPendingWithdrawalsFromConsensus(withdrawals []*ethpb.BuilderPendingWithdrawal) []*BuilderPendingWithdrawal {
|
||||
newWithdrawals := make([]*BuilderPendingWithdrawal, len(withdrawals))
|
||||
for i, w := range withdrawals {
|
||||
newWithdrawals[i] = BuilderPendingWithdrawalFromConsensus(w)
|
||||
}
|
||||
return newWithdrawals
|
||||
}
|
||||
|
||||
func BuilderPendingWithdrawalFromConsensus(w *ethpb.BuilderPendingWithdrawal) *BuilderPendingWithdrawal {
|
||||
return &BuilderPendingWithdrawal{
|
||||
FeeRecipient: hexutil.Encode(w.FeeRecipient),
|
||||
Amount: fmt.Sprintf("%d", w.Amount),
|
||||
BuilderIndex: fmt.Sprintf("%d", w.BuilderIndex),
|
||||
}
|
||||
}
|
||||
@@ -972,3 +972,223 @@ func BeaconStateFuluFromConsensus(st beaconState.BeaconState) (*BeaconStateFulu,
|
||||
ProposerLookahead: lookahead,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Gloas
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func BeaconStateGloasFromConsensus(st beaconState.BeaconState) (*BeaconStateGloas, error) {
|
||||
srcBr := st.BlockRoots()
|
||||
br := make([]string, len(srcBr))
|
||||
for i, r := range srcBr {
|
||||
br[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcSr := st.StateRoots()
|
||||
sr := make([]string, len(srcSr))
|
||||
for i, r := range srcSr {
|
||||
sr[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcHr := st.HistoricalRoots()
|
||||
hr := make([]string, len(srcHr))
|
||||
for i, r := range srcHr {
|
||||
hr[i] = hexutil.Encode(r)
|
||||
}
|
||||
srcVotes := st.Eth1DataVotes()
|
||||
votes := make([]*Eth1Data, len(srcVotes))
|
||||
for i, e := range srcVotes {
|
||||
votes[i] = Eth1DataFromConsensus(e)
|
||||
}
|
||||
srcVals := st.Validators()
|
||||
vals := make([]*Validator, len(srcVals))
|
||||
for i, v := range srcVals {
|
||||
vals[i] = ValidatorFromConsensus(v)
|
||||
}
|
||||
srcBals := st.Balances()
|
||||
bals := make([]string, len(srcBals))
|
||||
for i, b := range srcBals {
|
||||
bals[i] = fmt.Sprintf("%d", b)
|
||||
}
|
||||
srcRm := st.RandaoMixes()
|
||||
rm := make([]string, len(srcRm))
|
||||
for i, m := range srcRm {
|
||||
rm[i] = hexutil.Encode(m)
|
||||
}
|
||||
srcSlashings := st.Slashings()
|
||||
slashings := make([]string, len(srcSlashings))
|
||||
for i, s := range srcSlashings {
|
||||
slashings[i] = fmt.Sprintf("%d", s)
|
||||
}
|
||||
srcPrevPart, err := st.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prevPart := make([]string, len(srcPrevPart))
|
||||
for i, p := range srcPrevPart {
|
||||
prevPart[i] = fmt.Sprintf("%d", p)
|
||||
}
|
||||
srcCurrPart, err := st.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currPart := make([]string, len(srcCurrPart))
|
||||
for i, p := range srcCurrPart {
|
||||
currPart[i] = fmt.Sprintf("%d", p)
|
||||
}
|
||||
srcIs, err := st.InactivityScores()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
is := make([]string, len(srcIs))
|
||||
for i, s := range srcIs {
|
||||
is[i] = fmt.Sprintf("%d", s)
|
||||
}
|
||||
currSc, err := st.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextSc, err := st.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcHs, err := st.HistoricalSummaries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hs := make([]*HistoricalSummary, len(srcHs))
|
||||
for i, s := range srcHs {
|
||||
hs[i] = HistoricalSummaryFromConsensus(s)
|
||||
}
|
||||
nwi, err := st.NextWithdrawalIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nwvi, err := st.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
drsi, err := st.DepositRequestsStartIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbtc, err := st.DepositBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ebtc, err := st.ExitBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eee, err := st.EarliestExitEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cbtc, err := st.ConsolidationBalanceToConsume()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ece, err := st.EarliestConsolidationEpoch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pbd, err := st.PendingDeposits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ppw, err := st.PendingPartialWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := st.PendingConsolidations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srcLookahead, err := st.ProposerLookahead()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lookahead := make([]string, len(srcLookahead))
|
||||
for i, v := range srcLookahead {
|
||||
lookahead[i] = fmt.Sprintf("%d", uint64(v))
|
||||
}
|
||||
// Gloas-specific fields
|
||||
lepb, err := st.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
builders, err := st.Builders()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nwbi, err := st.NextWithdrawalBuilderIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
epa, err := st.ExecutionPayloadAvailabilityVector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bpp, err := st.BuilderPendingPayments()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bpw, err := st.BuilderPendingWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lbh, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pew, err := st.PayloadExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BeaconStateGloas{
|
||||
GenesisTime: fmt.Sprintf("%d", st.GenesisTime().Unix()),
|
||||
GenesisValidatorsRoot: hexutil.Encode(st.GenesisValidatorsRoot()),
|
||||
Slot: fmt.Sprintf("%d", st.Slot()),
|
||||
Fork: ForkFromConsensus(st.Fork()),
|
||||
LatestBlockHeader: BeaconBlockHeaderFromConsensus(st.LatestBlockHeader()),
|
||||
BlockRoots: br,
|
||||
StateRoots: sr,
|
||||
HistoricalRoots: hr,
|
||||
Eth1Data: Eth1DataFromConsensus(st.Eth1Data()),
|
||||
Eth1DataVotes: votes,
|
||||
Eth1DepositIndex: fmt.Sprintf("%d", st.Eth1DepositIndex()),
|
||||
Validators: vals,
|
||||
Balances: bals,
|
||||
RandaoMixes: rm,
|
||||
Slashings: slashings,
|
||||
PreviousEpochParticipation: prevPart,
|
||||
CurrentEpochParticipation: currPart,
|
||||
JustificationBits: hexutil.Encode(st.JustificationBits()),
|
||||
PreviousJustifiedCheckpoint: CheckpointFromConsensus(st.PreviousJustifiedCheckpoint()),
|
||||
CurrentJustifiedCheckpoint: CheckpointFromConsensus(st.CurrentJustifiedCheckpoint()),
|
||||
FinalizedCheckpoint: CheckpointFromConsensus(st.FinalizedCheckpoint()),
|
||||
InactivityScores: is,
|
||||
CurrentSyncCommittee: SyncCommitteeFromConsensus(currSc),
|
||||
NextSyncCommittee: SyncCommitteeFromConsensus(nextSc),
|
||||
NextWithdrawalIndex: fmt.Sprintf("%d", nwi),
|
||||
NextWithdrawalValidatorIndex: fmt.Sprintf("%d", nwvi),
|
||||
HistoricalSummaries: hs,
|
||||
DepositRequestsStartIndex: fmt.Sprintf("%d", drsi),
|
||||
DepositBalanceToConsume: fmt.Sprintf("%d", dbtc),
|
||||
ExitBalanceToConsume: fmt.Sprintf("%d", ebtc),
|
||||
EarliestExitEpoch: fmt.Sprintf("%d", eee),
|
||||
ConsolidationBalanceToConsume: fmt.Sprintf("%d", cbtc),
|
||||
EarliestConsolidationEpoch: fmt.Sprintf("%d", ece),
|
||||
PendingDeposits: PendingDepositsFromConsensus(pbd),
|
||||
PendingPartialWithdrawals: PendingPartialWithdrawalsFromConsensus(ppw),
|
||||
PendingConsolidations: PendingConsolidationsFromConsensus(pc),
|
||||
ProposerLookahead: lookahead,
|
||||
LatestExecutionPayloadBid: ROExecutionPayloadBidFromConsensus(lepb),
|
||||
Builders: BuildersFromConsensus(builders),
|
||||
NextWithdrawalBuilderIndex: fmt.Sprintf("%d", nwbi),
|
||||
ExecutionPayloadAvailability: hexutil.Encode(epa),
|
||||
BuilderPendingPayments: BuilderPendingPaymentsFromConsensus(bpp),
|
||||
BuilderPendingWithdrawals: BuilderPendingWithdrawalsFromConsensus(bpw),
|
||||
LatestBlockHash: hexutil.Encode(lbh[:]),
|
||||
PayloadExpectedWithdrawals: WithdrawalsFromConsensus(pew),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
)
|
||||
|
||||
@@ -355,3 +360,214 @@ func TestIndexedAttestation_ToConsensus(t *testing.T) {
|
||||
_, err := a.ToConsensus()
|
||||
require.ErrorContains(t, errNilValue.Error(), err)
|
||||
}
|
||||
|
||||
func TestROExecutionPayloadBidFromConsensus(t *testing.T) {
|
||||
t.Run("empty blobkzg commitments", func(t *testing.T) {
|
||||
bid := ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||
GasLimit: 100,
|
||||
BuilderIndex: 7,
|
||||
Slot: 9,
|
||||
Value: 11,
|
||||
ExecutionPayment: 22,
|
||||
BlobKzgCommitments: [][]byte{},
|
||||
}
|
||||
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||
require.NoError(t, err)
|
||||
|
||||
got := ROExecutionPayloadBidFromConsensus(roBid)
|
||||
want := &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(bid.BlockHash),
|
||||
PrevRandao: hexutil.Encode(bid.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
|
||||
GasLimit: "100",
|
||||
BuilderIndex: "7",
|
||||
Slot: "9",
|
||||
Value: "11",
|
||||
ExecutionPayment: "22",
|
||||
BlobKzgCommitments: []string{},
|
||||
}
|
||||
assert.DeepEqual(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
bid := ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x01}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x02}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||
GasLimit: 100,
|
||||
BuilderIndex: 7,
|
||||
Slot: 9,
|
||||
Value: 11,
|
||||
ExecutionPayment: 22,
|
||||
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x06}, 48)},
|
||||
}
|
||||
roBid, err := blocks.WrappedROExecutionPayloadBid(bid)
|
||||
require.NoError(t, err)
|
||||
|
||||
var bkcs []string
|
||||
for _, commitment := range roBid.BlobKzgCommitments() {
|
||||
bkcs = append(bkcs, hexutil.Encode(commitment))
|
||||
}
|
||||
|
||||
got := ROExecutionPayloadBidFromConsensus(roBid)
|
||||
want := &ExecutionPayloadBid{
|
||||
ParentBlockHash: hexutil.Encode(bid.ParentBlockHash),
|
||||
ParentBlockRoot: hexutil.Encode(bid.ParentBlockRoot),
|
||||
BlockHash: hexutil.Encode(bid.BlockHash),
|
||||
PrevRandao: hexutil.Encode(bid.PrevRandao),
|
||||
FeeRecipient: hexutil.Encode(bid.FeeRecipient),
|
||||
GasLimit: "100",
|
||||
BuilderIndex: "7",
|
||||
Slot: "9",
|
||||
Value: "11",
|
||||
ExecutionPayment: "22",
|
||||
BlobKzgCommitments: bkcs,
|
||||
}
|
||||
assert.DeepEqual(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuilderConversionsFromConsensus(t *testing.T) {
|
||||
builder := ð.Builder{
|
||||
Pubkey: bytes.Repeat([]byte{0xAA}, 48),
|
||||
Version: bytes.Repeat([]byte{0x01}, 4),
|
||||
ExecutionAddress: bytes.Repeat([]byte{0xBB}, 20),
|
||||
Balance: 42,
|
||||
DepositEpoch: 3,
|
||||
WithdrawableEpoch: 4,
|
||||
}
|
||||
wantBuilder := &Builder{
|
||||
Pubkey: hexutil.Encode(builder.Pubkey),
|
||||
Version: hexutil.Encode(builder.Version),
|
||||
ExecutionAddress: hexutil.Encode(builder.ExecutionAddress),
|
||||
Balance: "42",
|
||||
DepositEpoch: "3",
|
||||
WithdrawableEpoch: "4",
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, wantBuilder, BuilderFromConsensus(builder))
|
||||
assert.DeepEqual(t, []*Builder{wantBuilder}, BuildersFromConsensus([]*eth.Builder{builder}))
|
||||
}
|
||||
|
||||
func TestBuilderPendingPaymentConversionsFromConsensus(t *testing.T) {
|
||||
withdrawal := ð.BuilderPendingWithdrawal{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x10}, 20),
|
||||
Amount: 15,
|
||||
BuilderIndex: 2,
|
||||
}
|
||||
payment := ð.BuilderPendingPayment{
|
||||
Weight: 5,
|
||||
Withdrawal: withdrawal,
|
||||
}
|
||||
wantWithdrawal := &BuilderPendingWithdrawal{
|
||||
FeeRecipient: hexutil.Encode(withdrawal.FeeRecipient),
|
||||
Amount: "15",
|
||||
BuilderIndex: "2",
|
||||
}
|
||||
wantPayment := &BuilderPendingPayment{
|
||||
Weight: "5",
|
||||
Withdrawal: wantWithdrawal,
|
||||
}
|
||||
|
||||
assert.DeepEqual(t, wantPayment, BuilderPendingPaymentFromConsensus(payment))
|
||||
assert.DeepEqual(t, []*BuilderPendingPayment{wantPayment}, BuilderPendingPaymentsFromConsensus([]*eth.BuilderPendingPayment{payment}))
|
||||
assert.DeepEqual(t, wantWithdrawal, BuilderPendingWithdrawalFromConsensus(withdrawal))
|
||||
assert.DeepEqual(t, []*BuilderPendingWithdrawal{wantWithdrawal}, BuilderPendingWithdrawalsFromConsensus([]*eth.BuilderPendingWithdrawal{withdrawal}))
|
||||
}
|
||||
|
||||
func TestBeaconStateGloasFromConsensus(t *testing.T) {
|
||||
st, err := util.NewBeaconStateGloas(func(state *eth.BeaconStateGloas) error {
|
||||
state.GenesisTime = 123
|
||||
state.GenesisValidatorsRoot = bytes.Repeat([]byte{0x10}, 32)
|
||||
state.Slot = 5
|
||||
state.ProposerLookahead = []primitives.ValidatorIndex{1, 2}
|
||||
state.LatestExecutionPayloadBid = ð.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32),
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x12}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x13}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x14}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x15}, 20),
|
||||
GasLimit: 64,
|
||||
BuilderIndex: 3,
|
||||
Slot: 5,
|
||||
Value: 99,
|
||||
ExecutionPayment: 7,
|
||||
BlobKzgCommitments: [][]byte{bytes.Repeat([]byte{0x16}, 48)},
|
||||
}
|
||||
state.Builders = []*eth.Builder{
|
||||
{
|
||||
Pubkey: bytes.Repeat([]byte{0x20}, 48),
|
||||
Version: bytes.Repeat([]byte{0x21}, 4),
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x22}, 20),
|
||||
Balance: 88,
|
||||
DepositEpoch: 1,
|
||||
WithdrawableEpoch: 2,
|
||||
},
|
||||
}
|
||||
state.NextWithdrawalBuilderIndex = 9
|
||||
state.ExecutionPayloadAvailability = []byte{0x01, 0x02}
|
||||
state.BuilderPendingPayments = []*eth.BuilderPendingPayment{
|
||||
{
|
||||
Weight: 3,
|
||||
Withdrawal: ð.BuilderPendingWithdrawal{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x23}, 20),
|
||||
Amount: 4,
|
||||
BuilderIndex: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
state.BuilderPendingWithdrawals = []*eth.BuilderPendingWithdrawal{
|
||||
{
|
||||
FeeRecipient: bytes.Repeat([]byte{0x24}, 20),
|
||||
Amount: 6,
|
||||
BuilderIndex: 7,
|
||||
},
|
||||
}
|
||||
state.LatestBlockHash = bytes.Repeat([]byte{0x25}, 32)
|
||||
state.PayloadExpectedWithdrawals = []*enginev1.Withdrawal{
|
||||
{Index: 1, ValidatorIndex: 2, Address: bytes.Repeat([]byte{0x26}, 20), Amount: 10},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := BeaconStateGloasFromConsensus(st)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "123", got.GenesisTime)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x10}, 32)), got.GenesisValidatorsRoot)
|
||||
require.Equal(t, "5", got.Slot)
|
||||
require.DeepEqual(t, []string{"1", "2"}, got.ProposerLookahead)
|
||||
require.Equal(t, "9", got.NextWithdrawalBuilderIndex)
|
||||
require.Equal(t, hexutil.Encode([]byte{0x01, 0x02}), got.ExecutionPayloadAvailability)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x25}, 32)), got.LatestBlockHash)
|
||||
|
||||
require.NotNil(t, got.LatestExecutionPayloadBid)
|
||||
require.Equal(t, "64", got.LatestExecutionPayloadBid.GasLimit)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x11}, 32)), got.LatestExecutionPayloadBid.ParentBlockHash)
|
||||
|
||||
require.NotNil(t, got.Builders)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x20}, 48)), got.Builders[0].Pubkey)
|
||||
require.Equal(t, "88", got.Builders[0].Balance)
|
||||
|
||||
require.Equal(t, "3", got.BuilderPendingPayments[0].Weight)
|
||||
require.Equal(t, "4", got.BuilderPendingPayments[0].Withdrawal.Amount)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x23}, 20)), got.BuilderPendingPayments[0].Withdrawal.FeeRecipient)
|
||||
|
||||
require.Equal(t, "6", got.BuilderPendingWithdrawals[0].Amount)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x24}, 20)), got.BuilderPendingWithdrawals[0].FeeRecipient)
|
||||
|
||||
require.Equal(t, "1", got.PayloadExpectedWithdrawals[0].WithdrawalIndex)
|
||||
require.Equal(t, "2", got.PayloadExpectedWithdrawals[0].ValidatorIndex)
|
||||
require.Equal(t, hexutil.Encode(bytes.Repeat([]byte{0x26}, 20)), got.PayloadExpectedWithdrawals[0].ExecutionAddress)
|
||||
require.Equal(t, "10", got.PayloadExpectedWithdrawals[0].Amount)
|
||||
}
|
||||
|
||||
@@ -285,6 +285,13 @@ type GetBlobsResponse struct {
|
||||
Data []string `json:"data"` //blobs
|
||||
}
|
||||
|
||||
type GetExecutionPayloadEnvelopeResponse struct {
|
||||
Version string `json:"version"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Finalized bool `json:"finalized"`
|
||||
Data *SignedExecutionPayloadEnvelope `json:"data"`
|
||||
}
|
||||
|
||||
type SSZQueryRequest struct {
|
||||
Query string `json:"query"`
|
||||
IncludeProof bool `json:"include_proof,omitempty"`
|
||||
|
||||
@@ -53,8 +53,8 @@ type ChainReorgEvent struct {
|
||||
Slot string `json:"slot"`
|
||||
Depth string `json:"depth"`
|
||||
OldHeadBlock string `json:"old_head_block"`
|
||||
NewHeadBlock string `json:"old_head_state"`
|
||||
OldHeadState string `json:"new_head_block"`
|
||||
NewHeadBlock string `json:"new_head_block"`
|
||||
OldHeadState string `json:"old_head_state"`
|
||||
NewHeadState string `json:"new_head_state"`
|
||||
Epoch string `json:"epoch"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
@@ -112,3 +112,8 @@ type LightClientOptimisticUpdateEvent struct {
|
||||
Version string `json:"version"`
|
||||
Data *LightClientOptimisticUpdate `json:"data"`
|
||||
}
|
||||
|
||||
type PayloadEvent struct {
|
||||
Slot string `json:"slot"`
|
||||
BlockRoot string `json:"block_root"`
|
||||
}
|
||||
|
||||
@@ -63,6 +63,19 @@ type PeerCount struct {
|
||||
Connected string `json:"connected"`
|
||||
Disconnecting string `json:"disconnecting"`
|
||||
}
|
||||
type GetVersionV2Response struct {
|
||||
Data *VersionV2 `json:"data"`
|
||||
}
|
||||
type VersionV2 struct {
|
||||
BeaconNode *ClientVersionV1 `json:"beacon_node"`
|
||||
ExecutionClient *ClientVersionV1 `json:"execution_client,omitempty"`
|
||||
}
|
||||
type ClientVersionV1 struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Commit string `json:"commit"`
|
||||
}
|
||||
|
||||
type GetVersionResponse struct {
|
||||
Data *Version `json:"data"`
|
||||
|
||||
@@ -74,6 +74,18 @@ type SyncCommitteeDuty struct {
|
||||
ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"`
|
||||
}
|
||||
|
||||
type GetPTCDutiesResponse struct {
|
||||
DependentRoot string `json:"dependent_root"`
|
||||
ExecutionOptimistic bool `json:"execution_optimistic"`
|
||||
Data []*PTCDuty `json:"data"`
|
||||
}
|
||||
|
||||
type PTCDuty struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
ValidatorIndex string `json:"validator_index"`
|
||||
Slot string `json:"slot"`
|
||||
}
|
||||
|
||||
// ProduceBlockV3Response is a wrapper json object for the returned block from the ProduceBlockV3 endpoint
|
||||
type ProduceBlockV3Response struct {
|
||||
Version string `json:"version"`
|
||||
|
||||
@@ -262,3 +262,23 @@ type PendingConsolidation struct {
|
||||
SourceIndex string `json:"source_index"`
|
||||
TargetIndex string `json:"target_index"`
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
Pubkey string `json:"pubkey"`
|
||||
Version string `json:"version"`
|
||||
ExecutionAddress string `json:"execution_address"`
|
||||
Balance string `json:"balance"`
|
||||
DepositEpoch string `json:"deposit_epoch"`
|
||||
WithdrawableEpoch string `json:"withdrawable_epoch"`
|
||||
}
|
||||
|
||||
type BuilderPendingPayment struct {
|
||||
Weight string `json:"weight"`
|
||||
Withdrawal *BuilderPendingWithdrawal `json:"withdrawal"`
|
||||
}
|
||||
|
||||
type BuilderPendingWithdrawal struct {
|
||||
FeeRecipient string `json:"fee_recipient"`
|
||||
Amount string `json:"amount"`
|
||||
BuilderIndex string `json:"builder_index"`
|
||||
}
|
||||
|
||||
@@ -221,3 +221,51 @@ type BeaconStateFulu struct {
|
||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||
}
|
||||
|
||||
type BeaconStateGloas struct {
|
||||
GenesisTime string `json:"genesis_time"`
|
||||
GenesisValidatorsRoot string `json:"genesis_validators_root"`
|
||||
Slot string `json:"slot"`
|
||||
Fork *Fork `json:"fork"`
|
||||
LatestBlockHeader *BeaconBlockHeader `json:"latest_block_header"`
|
||||
BlockRoots []string `json:"block_roots"`
|
||||
StateRoots []string `json:"state_roots"`
|
||||
HistoricalRoots []string `json:"historical_roots"`
|
||||
Eth1Data *Eth1Data `json:"eth1_data"`
|
||||
Eth1DataVotes []*Eth1Data `json:"eth1_data_votes"`
|
||||
Eth1DepositIndex string `json:"eth1_deposit_index"`
|
||||
Validators []*Validator `json:"validators"`
|
||||
Balances []string `json:"balances"`
|
||||
RandaoMixes []string `json:"randao_mixes"`
|
||||
Slashings []string `json:"slashings"`
|
||||
PreviousEpochParticipation []string `json:"previous_epoch_participation"`
|
||||
CurrentEpochParticipation []string `json:"current_epoch_participation"`
|
||||
JustificationBits string `json:"justification_bits"`
|
||||
PreviousJustifiedCheckpoint *Checkpoint `json:"previous_justified_checkpoint"`
|
||||
CurrentJustifiedCheckpoint *Checkpoint `json:"current_justified_checkpoint"`
|
||||
FinalizedCheckpoint *Checkpoint `json:"finalized_checkpoint"`
|
||||
InactivityScores []string `json:"inactivity_scores"`
|
||||
CurrentSyncCommittee *SyncCommittee `json:"current_sync_committee"`
|
||||
NextSyncCommittee *SyncCommittee `json:"next_sync_committee"`
|
||||
NextWithdrawalIndex string `json:"next_withdrawal_index"`
|
||||
NextWithdrawalValidatorIndex string `json:"next_withdrawal_validator_index"`
|
||||
HistoricalSummaries []*HistoricalSummary `json:"historical_summaries"`
|
||||
DepositRequestsStartIndex string `json:"deposit_requests_start_index"`
|
||||
DepositBalanceToConsume string `json:"deposit_balance_to_consume"`
|
||||
ExitBalanceToConsume string `json:"exit_balance_to_consume"`
|
||||
EarliestExitEpoch string `json:"earliest_exit_epoch"`
|
||||
ConsolidationBalanceToConsume string `json:"consolidation_balance_to_consume"`
|
||||
EarliestConsolidationEpoch string `json:"earliest_consolidation_epoch"`
|
||||
PendingDeposits []*PendingDeposit `json:"pending_deposits"`
|
||||
PendingPartialWithdrawals []*PendingPartialWithdrawal `json:"pending_partial_withdrawals"`
|
||||
PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"`
|
||||
ProposerLookahead []string `json:"proposer_lookahead"`
|
||||
LatestExecutionPayloadBid *ExecutionPayloadBid `json:"latest_execution_payload_bid"`
|
||||
Builders []*Builder `json:"builders"`
|
||||
NextWithdrawalBuilderIndex string `json:"next_withdrawal_builder_index"`
|
||||
ExecutionPayloadAvailability string `json:"execution_payload_availability"`
|
||||
BuilderPendingPayments []*BuilderPendingPayment `json:"builder_pending_payments"`
|
||||
BuilderPendingWithdrawals []*BuilderPendingWithdrawal `json:"builder_pending_withdrawals"`
|
||||
LatestBlockHash string `json:"latest_block_hash"`
|
||||
PayloadExpectedWithdrawals []*Withdrawal `json:"payload_expected_withdrawals"`
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ go_library(
|
||||
"error.go",
|
||||
"execution_engine.go",
|
||||
"forkchoice_update_execution.go",
|
||||
"gloas.go",
|
||||
"head.go",
|
||||
"head_sync_committee_info.go",
|
||||
"init_sync_process_block.go",
|
||||
@@ -27,6 +28,8 @@ go_library(
|
||||
"receive_blob.go",
|
||||
"receive_block.go",
|
||||
"receive_data_column.go",
|
||||
"receive_execution_payload_envelope.go",
|
||||
"receive_payload_attestation_message.go",
|
||||
"service.go",
|
||||
"setup_forkchoice.go",
|
||||
"tracked_proposer.go",
|
||||
@@ -50,6 +53,7 @@ go_library(
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/feed:go_default_library",
|
||||
"//beacon-chain/core/feed/state:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/peerdas:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
@@ -85,6 +89,7 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//io/logs:go_default_library",
|
||||
"//math:go_default_library",
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
@@ -118,6 +123,7 @@ go_test(
|
||||
"error_test.go",
|
||||
"execution_engine_test.go",
|
||||
"forkchoice_update_execution_test.go",
|
||||
"gloas_test.go",
|
||||
"head_sync_committee_info_test.go",
|
||||
"head_test.go",
|
||||
"init_sync_process_block_test.go",
|
||||
@@ -130,10 +136,12 @@ go_test(
|
||||
"process_block_test.go",
|
||||
"receive_attestation_test.go",
|
||||
"receive_block_test.go",
|
||||
"receive_payload_attestation_message_test.go",
|
||||
"service_norace_test.go",
|
||||
"service_test.go",
|
||||
"setup_forkchoice_test.go",
|
||||
"setup_test.go",
|
||||
"tracked_proposer_test.go",
|
||||
"weak_subjectivity_checks_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
@@ -178,6 +186,7 @@ go_test(
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/payload-attribute:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//container/trie:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -39,19 +40,26 @@ type ChainInfoFetcher interface {
|
||||
// of locking forkchoice
|
||||
type ForkchoiceFetcher interface {
|
||||
Ancestor(context.Context, []byte, primitives.Slot) ([]byte, error)
|
||||
BlockHash(root [32]byte) ([32]byte, error)
|
||||
CachedHeadRoot() [32]byte
|
||||
GetProposerHead() [32]byte
|
||||
SetForkChoiceGenesisTime(time.Time)
|
||||
UpdateHead(context.Context, primitives.Slot)
|
||||
HighestReceivedBlockSlot() primitives.Slot
|
||||
HighestReceivedBlockRoot() [32]byte
|
||||
HasFullNode([32]byte) bool
|
||||
PayloadContentLookup([32]byte) ([32]byte, bool)
|
||||
ReceivedBlocksLastEpoch() (uint64, error)
|
||||
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
|
||||
InsertPayload(interfaces.ROExecutionPayloadEnvelope) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
|
||||
DependentRoot(primitives.Epoch) ([32]byte, error)
|
||||
CanonicalNodeAtSlot(primitives.Slot) ([32]byte, bool)
|
||||
ShouldIgnoreData(parentRoot [32]byte, dataSlot primitives.Slot) bool
|
||||
}
|
||||
|
||||
// TimeFetcher retrieves the Ethereum consensus data that's related to time.
|
||||
@@ -113,6 +121,7 @@ type FinalizationFetcher interface {
|
||||
FinalizedBlockHash() [32]byte
|
||||
InForkchoice([32]byte) bool
|
||||
IsFinalized(ctx context.Context, blockRoot [32]byte) bool
|
||||
ParentPayloadReady(interfaces.ReadOnlyBeaconBlock) bool
|
||||
}
|
||||
|
||||
// OptimisticModeFetcher retrieves information about optimistic status of the node.
|
||||
@@ -402,6 +411,32 @@ func (s *Service) InForkchoice(root [32]byte) bool {
|
||||
return s.cfg.ForkChoiceStore.HasNode(root)
|
||||
}
|
||||
|
||||
// ParentPayloadReady returns true if the block's parent payload is available
|
||||
// in forkchoice. For pre-Gloas blocks or blocks building on empty, this always
|
||||
// returns true. For blocks building on full, it checks that the full node
|
||||
// exists.
|
||||
func (s *Service) ParentPayloadReady(blk interfaces.ReadOnlyBeaconBlock) bool {
|
||||
if blk.Version() < version.Gloas {
|
||||
return true
|
||||
}
|
||||
parentRoot := blk.ParentRoot()
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
blockHash, err := s.cfg.ForkChoiceStore.BlockHash(parentRoot)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bid, err := blk.Body().SignedExecutionPayloadBid()
|
||||
if err != nil || bid == nil || bid.Message == nil {
|
||||
return false
|
||||
}
|
||||
parentBlockHash := [32]byte(bid.Message.ParentBlockHash)
|
||||
if parentBlockHash != blockHash {
|
||||
return true // builds on empty, no full node needed
|
||||
}
|
||||
return s.cfg.ForkChoiceStore.HasFullNode(parentRoot)
|
||||
}
|
||||
|
||||
// IsOptimisticForRoot takes the root as argument instead of the current head
|
||||
// and returns true if it is optimistic.
|
||||
func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error) {
|
||||
@@ -564,3 +599,26 @@ func (s *Service) inRegularSync() bool {
|
||||
func (s *Service) validating() bool {
|
||||
return s.cfg.TrackedValidatorsCache.Validating()
|
||||
}
|
||||
|
||||
// ShouldIgnoreData returns true if the data for the given parent root and slot should be ignored.
|
||||
func (s *Service) ShouldIgnoreData(parentRoot [32]byte, dataSlot primitives.Slot) bool {
|
||||
currentEpoch := slots.ToEpoch(s.CurrentSlot())
|
||||
if slots.ToEpoch(dataSlot) < currentEpoch {
|
||||
return false
|
||||
}
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
parentSlot, err := s.cfg.ForkChoiceStore.Slot(parentRoot)
|
||||
if err != nil {
|
||||
// This should not happen. The caller should have already checked the parent is in forkchoice.
|
||||
return false
|
||||
}
|
||||
j := s.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
if j == nil {
|
||||
return false
|
||||
}
|
||||
if slots.ToEpoch(parentSlot) >= j.Epoch {
|
||||
return false
|
||||
}
|
||||
return s.cfg.ForkChoiceStore.IsCanonical(parentRoot)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/forkchoice"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
@@ -41,6 +42,34 @@ func (s *Service) HighestReceivedBlockSlot() primitives.Slot {
|
||||
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlot()
|
||||
}
|
||||
|
||||
// HighestReceivedBlockRoot returns the corresponding value from forkchoice
|
||||
func (s *Service) HighestReceivedBlockRoot() [32]byte {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.HighestReceivedBlockRoot()
|
||||
}
|
||||
|
||||
// BlockHash returns the execution payload block hash for the given beacon block root from forkchoice.
|
||||
func (s *Service) BlockHash(root [32]byte) ([32]byte, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.BlockHash(root)
|
||||
}
|
||||
|
||||
// HasFullNode returns the corresponding value from forkchoice
|
||||
func (s *Service) HasFullNode(root [32]byte) bool {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.HasFullNode(root)
|
||||
}
|
||||
|
||||
// PayloadContentLookup returns the preferred payload-content lookup key from forkchoice.
|
||||
func (s *Service) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.PayloadContentLookup(root)
|
||||
}
|
||||
|
||||
// ReceivedBlocksLastEpoch returns the corresponding value from forkchoice
|
||||
func (s *Service) ReceivedBlocksLastEpoch() (uint64, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
@@ -55,6 +84,13 @@ func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, block co
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
|
||||
// InsertPayload is a wrapper for payload insertion which is self locked
|
||||
func (s *Service) InsertPayload(pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertPayload(pe)
|
||||
}
|
||||
|
||||
// ForkChoiceDump returns the corresponding value from forkchoice
|
||||
func (s *Service) ForkChoiceDump(ctx context.Context) (*forkchoice.Dump, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
@@ -121,6 +157,13 @@ func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byt
|
||||
if st.Version() < version.Bellatrix {
|
||||
return nil, nil
|
||||
}
|
||||
if st.Version() >= version.Gloas {
|
||||
h, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest block hash")
|
||||
}
|
||||
return bytesutil.SafeCopyBytes(h[:]), nil
|
||||
}
|
||||
header, err := st.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get latest execution payload header")
|
||||
@@ -128,6 +171,13 @@ func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byt
|
||||
return bytesutil.SafeCopyBytes(header.BlockHash()), nil
|
||||
}
|
||||
|
||||
// CanonicalNodeAtSlot wraps the corresponding method in forkchoice
|
||||
func (s *Service) CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, bool) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
defer s.cfg.ForkChoiceStore.RUnlock()
|
||||
return s.cfg.ForkChoiceStore.CanonicalNodeAtSlot(slot)
|
||||
}
|
||||
|
||||
// DependentRoot wraps the corresponding method in forkchoice
|
||||
func (s *Service) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -620,6 +621,188 @@ func TestService_IsFinalized(t *testing.T) {
|
||||
require.Equal(t, false, c.IsFinalized(ctx, [32]byte{'c'}))
|
||||
}
|
||||
|
||||
func TestParentPayloadReady(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, tr := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
fcs := tr.fcs
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentBlockHash := [32]byte{10}
|
||||
zeroHash := params.BeaconConfig().ZeroHash
|
||||
|
||||
// Insert parent node into forkchoice.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, zeroHash, parentBlockHash, zeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
t.Run("pre-Gloas always true", func(t *testing.T) {
|
||||
blk := util.HydrateSignedBeaconBlockDeneb(ðpb.SignedBeaconBlockDeneb{
|
||||
Block: ðpb.BeaconBlockDeneb{ParentRoot: parentRoot[:]},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("parent not in forkchoice", func(t *testing.T) {
|
||||
unknownParent := [32]byte{99}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: unknownParent[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on empty", func(t *testing.T) {
|
||||
differentHash := [32]byte{99}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: differentHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on full without payload", func(t *testing.T) {
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
|
||||
t.Run("builds on full with payload", func(t *testing.T) {
|
||||
pe, err := blocks.WrappedROExecutionPayloadEnvelope(ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: parentRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertPayload(pe))
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: []byte{20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, service.ParentPayloadReady(wsb.Block()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_ShouldIgnoreData(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
fcs := tr.fcs
|
||||
|
||||
zeroHash := params.BeaconConfig().ZeroHash
|
||||
currentSlot := service.CurrentSlot()
|
||||
currentEpoch := slots.ToEpoch(currentSlot)
|
||||
slotsPerEpoch := params.BeaconConfig().SlotsPerEpoch
|
||||
|
||||
// Build a chain in forkchoice:
|
||||
// genesis (slot 0) -> nodeA (slot 1, epoch 0) -> nodeB (slot slotsPerEpoch, epoch 1) -> nodeC (slot 2*slotsPerEpoch, epoch 2)
|
||||
nodeARoot := [32]byte{1}
|
||||
nodeBRoot := [32]byte{2}
|
||||
nodeCRoot := [32]byte{3}
|
||||
nodeASlot := primitives.Slot(1)
|
||||
nodeBSlot := primitives.Slot(slotsPerEpoch) // epoch 1
|
||||
nodeCSlot := primitives.Slot(2 * slotsPerEpoch) // epoch 2
|
||||
|
||||
stA, robA, err := prepareForkchoiceState(ctx, nodeASlot, nodeARoot, zeroHash, [32]byte{10}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, stA, robA))
|
||||
|
||||
stB, robB, err := prepareForkchoiceState(ctx, nodeBSlot, nodeBRoot, nodeARoot, [32]byte{11}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, stB, robB))
|
||||
|
||||
stC, robC, err := prepareForkchoiceState(ctx, nodeCSlot, nodeCRoot, nodeBRoot, [32]byte{12}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, stC, robC))
|
||||
|
||||
// Set justified checkpoint to nodeB (epoch 1).
|
||||
fcs.SetBalancesByRooter(func(_ context.Context, _ [32]byte) ([]uint64, error) { return []uint64{}, nil })
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: 1, Root: nodeBRoot}))
|
||||
|
||||
t.Run("past epoch data is not ignored", func(t *testing.T) {
|
||||
pastSlot := primitives.Slot((currentEpoch - 1) * primitives.Epoch(slotsPerEpoch))
|
||||
require.Equal(t, false, service.ShouldIgnoreData(nodeARoot, pastSlot))
|
||||
})
|
||||
|
||||
t.Run("parent not in forkchoice", func(t *testing.T) {
|
||||
unknownRoot := [32]byte{99}
|
||||
require.Equal(t, false, service.ShouldIgnoreData(unknownRoot, currentSlot))
|
||||
})
|
||||
|
||||
t.Run("parent epoch at or after justified", func(t *testing.T) {
|
||||
// nodeB is at epoch 1, justified is epoch 1 => parentEpoch >= justified => false
|
||||
require.Equal(t, false, service.ShouldIgnoreData(nodeBRoot, currentSlot))
|
||||
})
|
||||
|
||||
t.Run("canonical parent before justified is ignored", func(t *testing.T) {
|
||||
// nodeA is at epoch 0 < justified epoch 1, and is canonical => true
|
||||
require.Equal(t, true, service.ShouldIgnoreData(nodeARoot, currentSlot))
|
||||
})
|
||||
|
||||
t.Run("non-canonical parent before justified is not ignored", func(t *testing.T) {
|
||||
// Insert a fork: nodeD at slot 2 (epoch 0) branching from nodeA, not on the canonical chain.
|
||||
nodeDRoot := [32]byte{4}
|
||||
stD, robD, err := prepareForkchoiceState(ctx, 2, nodeDRoot, nodeARoot, [32]byte{13}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]}, ðpb.Checkpoint{Epoch: 0, Root: zeroHash[:]})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, stD, robD))
|
||||
|
||||
// nodeD is at epoch 0 < justified epoch 1, but not canonical => false
|
||||
require.Equal(t, false, service.ShouldIgnoreData(nodeDRoot, currentSlot))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_hashForGenesisRoot(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
ctx := t.Context()
|
||||
@@ -637,3 +820,23 @@ func Test_hashForGenesisRoot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, [32]byte{}, [32]byte(genRoot))
|
||||
}
|
||||
|
||||
func Test_hashForGenesisRoot_Gloas(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
ctx := t.Context()
|
||||
c := setupBeaconChain(t, beaconDB)
|
||||
|
||||
expectedHash := [32]byte{1, 2, 3, 4, 5}
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
LatestBlockHash: expectedHash[:],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
genesis.StoreDuringTest(t, genesis.GenesisData{State: st})
|
||||
|
||||
genesisRoot := [32]byte{0xaa}
|
||||
require.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, genesisRoot))
|
||||
|
||||
genHash, err := c.hashForGenesisBlock(ctx, genesisRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedHash, [32]byte(genHash))
|
||||
}
|
||||
|
||||
@@ -101,11 +101,16 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
|
||||
if len(lastValidHash) == 0 {
|
||||
lastValidHash = defaultLatestValidHash
|
||||
}
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, headRoot, headBlk.ParentRoot(), bytesutil.ToBytes32(lastValidHash))
|
||||
// this call has guaranteed to have the `headRoot` with its payload in forkchoice.
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, headRoot, headBlk.ParentRoot(), bytesutil.ToBytes32(headPayload.ParentHash()), bytesutil.ToBytes32(lastValidHash))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not set head root to invalid")
|
||||
return nil, nil
|
||||
}
|
||||
// TODO: Gloas, we should not include the head root in this call
|
||||
if len(invalidRoots) == 0 || invalidRoots[0] != headRoot {
|
||||
invalidRoots = append([][32]byte{headRoot}, invalidRoots...)
|
||||
}
|
||||
if err := s.removeInvalidBlockAndState(ctx, invalidRoots); err != nil {
|
||||
log.WithError(err).Error("Could not remove invalid block and state")
|
||||
return nil, nil
|
||||
@@ -228,6 +233,9 @@ func (s *Service) notifyNewPayload(ctx context.Context, stVersion int, header in
|
||||
if stVersion < version.Bellatrix {
|
||||
return true, nil
|
||||
}
|
||||
if blk.Version() >= version.Gloas {
|
||||
return false, nil
|
||||
}
|
||||
body := blk.Block().Body()
|
||||
enabled, err := blocks.IsExecutionEnabledUsingHeader(header, body)
|
||||
if err != nil {
|
||||
@@ -263,7 +271,7 @@ func (s *Service) notifyNewPayload(ctx context.Context, stVersion int, header in
|
||||
}
|
||||
}
|
||||
|
||||
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests)
|
||||
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests, blk.Block().Slot())
|
||||
if err == nil {
|
||||
newPayloadValidNodeCount.Inc()
|
||||
return true, nil
|
||||
@@ -290,10 +298,10 @@ func (s *Service) notifyNewPayload(ctx context.Context, stVersion int, header in
|
||||
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
|
||||
}
|
||||
|
||||
// reportInvalidBlock deals with the event that an invalid block was detected by the execution layer
|
||||
func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, lvh [32]byte) error {
|
||||
// pruneInvalidBlock deals with the event that an invalid block was detected by the execution layer
|
||||
func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, parentHash [32]byte, lvh [32]byte) error {
|
||||
newPayloadInvalidNodeCount.Inc()
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, root, parentRoot, lvh)
|
||||
invalidRoots, err := s.cfg.ForkChoiceStore.SetOptimisticToInvalid(ctx, root, parentRoot, parentHash, lvh)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -313,7 +321,7 @@ func (s *Service) pruneInvalidBlock(ctx context.Context, root, parentRoot, lvh [
|
||||
|
||||
// getPayloadAttributes returns the payload attributes for the given state and slot.
|
||||
// The attribute is required to initiate a payload build process in the context of an `engine_forkchoiceUpdated` call.
|
||||
func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot primitives.Slot, headRoot []byte) payloadattribute.Attributer {
|
||||
func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, slot primitives.Slot, headRoot, accessRoot []byte) payloadattribute.Attributer {
|
||||
emptyAttri := payloadattribute.EmptyWithVersion(st.Version())
|
||||
|
||||
// If it is an epoch boundary then process slots to get the right
|
||||
@@ -335,7 +343,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
// right proposer index pre-Fulu, either way we need to copy the state to process it.
|
||||
st = st.Copy()
|
||||
var err error
|
||||
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, headRoot, slot)
|
||||
st, err = transition.ProcessSlotsUsingNextSlotCache(ctx, st, accessRoot, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not process slots to get payload attribute")
|
||||
return emptyAttri
|
||||
@@ -363,66 +371,91 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
}
|
||||
|
||||
v := st.Version()
|
||||
|
||||
if v >= version.Deneb {
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV3{
|
||||
Timestamp: uint64(t.Unix()),
|
||||
PrevRandao: prevRando,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: headRoot,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
return attr
|
||||
switch {
|
||||
case v >= version.Gloas:
|
||||
return payloadAttributesGloas(st, uint64(t.Unix()), prevRando, val.FeeRecipient[:], headRoot)
|
||||
case v >= version.Deneb:
|
||||
return payloadAttributesDeneb(st, uint64(t.Unix()), prevRando, val.FeeRecipient[:], headRoot)
|
||||
case v >= version.Capella:
|
||||
return payloadAttributesCapella(st, uint64(t.Unix()), prevRando, val.FeeRecipient[:])
|
||||
case v >= version.Bellatrix:
|
||||
return payloadAttributesBellatrix(uint64(t.Unix()), prevRando, val.FeeRecipient[:])
|
||||
default:
|
||||
log.WithField("version", version.String(v)).Error("Could not get payload attribute due to unknown state version")
|
||||
return payloadattribute.EmptyWithVersion(v)
|
||||
}
|
||||
}
|
||||
|
||||
if v >= version.Capella {
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV2{
|
||||
Timestamp: uint64(t.Unix()),
|
||||
PrevRandao: prevRando,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
Withdrawals: withdrawals,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
return attr
|
||||
func payloadAttributesGloas(st state.BeaconState, timestamp uint64, prevRandao, feeRecipient, parentBeaconBlockRoot []byte) payloadattribute.Attributer {
|
||||
withdrawals, err := st.WithdrawalsForPayload()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload withdrawals to get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
|
||||
if v >= version.Bellatrix {
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributes{
|
||||
Timestamp: uint64(t.Unix()),
|
||||
PrevRandao: prevRando,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
return attr
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV3{
|
||||
Timestamp: timestamp,
|
||||
PrevRandao: prevRandao,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: parentBeaconBlockRoot,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
log.WithField("version", version.String(st.Version())).Error("Could not get payload attribute due to unknown state version")
|
||||
return emptyAttri
|
||||
func payloadAttributesDeneb(st state.BeaconState, timestamp uint64, prevRandao, feeRecipient, parentBeaconBlockRoot []byte) payloadattribute.Attributer {
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV3{
|
||||
Timestamp: timestamp,
|
||||
PrevRandao: prevRandao,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: parentBeaconBlockRoot,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
func payloadAttributesCapella(st state.BeaconState, timestamp uint64, prevRandao, feeRecipient []byte) payloadattribute.Attributer {
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV2{
|
||||
Timestamp: timestamp,
|
||||
PrevRandao: prevRandao,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
Withdrawals: withdrawals,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(st.Version())
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
func payloadAttributesBellatrix(timestamp uint64, prevRandao, feeRecipient []byte) payloadattribute.Attributer {
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributes{
|
||||
Timestamp: timestamp,
|
||||
PrevRandao: prevRandao,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return payloadattribute.EmptyWithVersion(version.Bellatrix)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// removeInvalidBlockAndState removes the invalid block, blob and its corresponding state from the cache and DB.
|
||||
|
||||
@@ -429,9 +429,9 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
|
||||
// Insert Attestations to D, F and G so that they have higher weight than D
|
||||
// Ensure G is head
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, 1)
|
||||
fcs.ProcessAttestation(ctx, []uint64{0}, brd, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{1}, brf, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.ProcessAttestation(ctx, []uint64{2}, brg, params.BeaconConfig().SlotsPerEpoch, true)
|
||||
fcs.SetBalancesByRooter(service.cfg.StateGen.ActiveNonSlashedBalancesByRoot)
|
||||
jc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: bra}
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, jc))
|
||||
@@ -465,9 +465,9 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, brd, headRoot)
|
||||
|
||||
// Ensure F and G where removed but their parent E wasn't
|
||||
require.Equal(t, false, fcs.HasNode(brf))
|
||||
require.Equal(t, false, fcs.HasNode(brg))
|
||||
// Ensure F and G's full nodes were removed but their empty (consensus) nodes remain, as does E
|
||||
require.Equal(t, true, fcs.HasNode(brf))
|
||||
require.Equal(t, true, fcs.HasNode(brg))
|
||||
require.Equal(t, true, fcs.HasNode(bre))
|
||||
}
|
||||
|
||||
@@ -703,14 +703,13 @@ func Test_reportInvalidBlock(t *testing.T) {
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
|
||||
require.NoError(t, fcs.SetOptimisticToValid(ctx, [32]byte{'A'}))
|
||||
err = service.pruneInvalidBlock(ctx, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'a'})
|
||||
err = service.pruneInvalidBlock(ctx, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'c'}, [32]byte{'a'})
|
||||
require.Equal(t, IsInvalidBlock(err), true)
|
||||
require.Equal(t, InvalidBlockLVH(err), [32]byte{'a'})
|
||||
invalidRoots := InvalidAncestorRoots(err)
|
||||
require.Equal(t, 3, len(invalidRoots))
|
||||
require.Equal(t, 2, len(invalidRoots))
|
||||
require.Equal(t, [32]byte{'D'}, invalidRoots[0])
|
||||
require.Equal(t, [32]byte{'C'}, invalidRoots[1])
|
||||
require.Equal(t, [32]byte{'B'}, invalidRoots[2])
|
||||
}
|
||||
|
||||
func Test_GetPayloadAttribute(t *testing.T) {
|
||||
@@ -718,14 +717,14 @@ func Test_GetPayloadAttribute(t *testing.T) {
|
||||
ctx := tr.ctx
|
||||
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
|
||||
require.Equal(t, true, attr.IsEmpty())
|
||||
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
|
||||
// Cache hit, advance state, no fee recipient
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
|
||||
@@ -733,7 +732,7 @@ func Test_GetPayloadAttribute(t *testing.T) {
|
||||
suggestedAddr := common.HexToAddress("123")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
||||
}
|
||||
@@ -748,7 +747,7 @@ func Test_GetPayloadAttribute_PrepareAllPayloads(t *testing.T) {
|
||||
ctx := tr.ctx
|
||||
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
}
|
||||
@@ -758,14 +757,14 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
|
||||
ctx := tr.ctx
|
||||
|
||||
st, _ := util.DeterministicGenesisStateCapella(t, 1)
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{})
|
||||
attr := service.getPayloadAttribute(ctx, st, 0, []byte{}, []byte{})
|
||||
require.Equal(t, true, attr.IsEmpty())
|
||||
|
||||
// Cache hit, advance state, no fee recipient
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
a, err := attr.Withdrawals()
|
||||
@@ -776,7 +775,7 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
|
||||
suggestedAddr := common.HexToAddress("123")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
||||
a, err = attr.Withdrawals()
|
||||
@@ -785,7 +784,7 @@ func Test_GetPayloadAttributeV2(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_GetPayloadAttributeV3(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
testCases := []struct {
|
||||
name string
|
||||
st bstate.BeaconState
|
||||
}{
|
||||
@@ -810,14 +809,14 @@ func Test_GetPayloadAttributeV3(t *testing.T) {
|
||||
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
ctx := tr.ctx
|
||||
|
||||
attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{})
|
||||
attr := service.getPayloadAttribute(ctx, test.st, 0, []byte{}, []byte{})
|
||||
require.Equal(t, true, attr.IsEmpty())
|
||||
|
||||
// Cache hit, advance state, no fee recipient
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(attr.SuggestedFeeRecipient()).String())
|
||||
a, err := attr.Withdrawals()
|
||||
@@ -828,7 +827,7 @@ func Test_GetPayloadAttributeV3(t *testing.T) {
|
||||
suggestedAddr := common.HexToAddress("123")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(suggestedAddr), Index: 0})
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:])
|
||||
attr = service.getPayloadAttribute(ctx, test.st, slot, params.BeaconConfig().ZeroHash[:], params.BeaconConfig().ZeroHash[:])
|
||||
require.Equal(t, false, attr.IsEmpty())
|
||||
require.Equal(t, suggestedAddr, common.BytesToAddress(attr.SuggestedFeeRecipient()))
|
||||
a, err = attr.Withdrawals()
|
||||
|
||||
@@ -18,19 +18,21 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (s *Service) isNewHead(r [32]byte) bool {
|
||||
func (s *Service) isNewHead(r [32]byte, full bool) bool {
|
||||
s.headLock.RLock()
|
||||
defer s.headLock.RUnlock()
|
||||
|
||||
currentHeadRoot := s.originBlockRoot
|
||||
currentFull := false
|
||||
if s.head != nil {
|
||||
currentHeadRoot = s.headRoot()
|
||||
currentFull = s.head.full
|
||||
}
|
||||
|
||||
return r != currentHeadRoot || r == [32]byte{}
|
||||
return r != currentHeadRoot || full != currentFull || r == [32]byte{}
|
||||
}
|
||||
|
||||
func (s *Service) getStateAndBlock(ctx context.Context, r [32]byte) (state.BeaconState, interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
func (s *Service) getStateAndBlock(ctx context.Context, r, h [32]byte) (state.BeaconState, interfaces.ReadOnlySignedBeaconBlock, error) {
|
||||
if !s.hasBlockInInitSyncOrDB(ctx, r) {
|
||||
return nil, nil, errors.New("block does not exist")
|
||||
}
|
||||
@@ -38,7 +40,7 @@ func (s *Service) getStateAndBlock(ctx context.Context, r [32]byte) (state.Beaco
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
headState, err := s.cfg.StateGen.StateByRoot(ctx, r)
|
||||
headState, err := s.cfg.StateGen.StateByRoot(ctx, h)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -56,7 +58,7 @@ type fcuConfig struct {
|
||||
// sendFCU handles the logic to notify the engine of a forckhoice update
|
||||
// when processing an incoming block during regular sync. It
|
||||
// always updates the shuffling caches and handles epoch transitions .
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
func (s *Service) sendFCU(cfg *postBlockProcessConfig) {
|
||||
if cfg.postState.Version() < version.Fulu {
|
||||
// update the caches to compute the right proposer index
|
||||
// this function is called under a forkchoice lock which we need to release.
|
||||
@@ -64,12 +66,13 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
s.updateCachesPostBlockProcessing(cfg)
|
||||
s.ForkChoicer().Lock()
|
||||
}
|
||||
if err := s.getFCUArgs(cfg, fcuArgs); err != nil {
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get forkchoice update argument")
|
||||
return
|
||||
}
|
||||
// If head has not been updated and attributes are nil, we can skip the FCU.
|
||||
if !s.isNewHead(cfg.headRoot) && (fcuArgs.attributes == nil || fcuArgs.attributes.IsEmpty()) {
|
||||
if !s.isNewHead(cfg.headRoot, false) && (fcuArgs.attributes == nil || fcuArgs.attributes.IsEmpty()) {
|
||||
return
|
||||
}
|
||||
// If we are proposing and we aim to reorg the block, we have already sent FCU with attributes on lateBlockTasks
|
||||
@@ -80,7 +83,7 @@ func (s *Service) sendFCU(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
go s.forkchoiceUpdateWithExecution(cfg.ctx, fcuArgs)
|
||||
}
|
||||
|
||||
if s.isNewHead(fcuArgs.headRoot) {
|
||||
if s.isNewHead(fcuArgs.headRoot, false) {
|
||||
if err := s.saveHead(cfg.ctx, fcuArgs.headRoot, fcuArgs.headBlock, fcuArgs.headState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
|
||||
@@ -19,23 +19,42 @@ import (
|
||||
func TestService_isNewHead(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
service := setupBeaconChain(t, beaconDB)
|
||||
require.Equal(t, true, service.isNewHead([32]byte{}))
|
||||
|
||||
// Zero root is always a new head
|
||||
require.Equal(t, true, service.isNewHead([32]byte{}, false))
|
||||
require.Equal(t, true, service.isNewHead([32]byte{}, true))
|
||||
|
||||
// Different root is a new head
|
||||
service.head = &head{root: [32]byte{1}}
|
||||
require.Equal(t, true, service.isNewHead([32]byte{2}))
|
||||
require.Equal(t, false, service.isNewHead([32]byte{1}))
|
||||
require.Equal(t, true, service.isNewHead([32]byte{2}, false))
|
||||
|
||||
// Same root and same full status is not a new head
|
||||
require.Equal(t, false, service.isNewHead([32]byte{1}, false))
|
||||
|
||||
// Same root but different full status is a new head
|
||||
require.Equal(t, true, service.isNewHead([32]byte{1}, true))
|
||||
|
||||
// Same root and both full is not a new head
|
||||
service.head = &head{root: [32]byte{1}, full: true}
|
||||
require.Equal(t, false, service.isNewHead([32]byte{1}, true))
|
||||
|
||||
// Same root, head is full but incoming is not full, is a new head
|
||||
require.Equal(t, true, service.isNewHead([32]byte{1}, false))
|
||||
|
||||
// Nil head should use origin root
|
||||
service.head = nil
|
||||
service.originBlockRoot = [32]byte{3}
|
||||
require.Equal(t, true, service.isNewHead([32]byte{2}))
|
||||
require.Equal(t, false, service.isNewHead([32]byte{3}))
|
||||
require.Equal(t, true, service.isNewHead([32]byte{2}, false))
|
||||
require.Equal(t, false, service.isNewHead([32]byte{3}, false))
|
||||
|
||||
// Nil head with full=true is always a new head (originBlockRoot has full=false)
|
||||
require.Equal(t, true, service.isNewHead([32]byte{3}, true))
|
||||
}
|
||||
|
||||
func TestService_getHeadStateAndBlock(t *testing.T) {
|
||||
beaconDB := testDB.SetupDB(t)
|
||||
service := setupBeaconChain(t, beaconDB)
|
||||
_, _, err := service.getStateAndBlock(t.Context(), [32]byte{})
|
||||
_, _, err := service.getStateAndBlock(t.Context(), [32]byte{}, [32]byte{})
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
blk, err := blocks.NewSignedBeaconBlock(util.HydrateSignedBeaconBlock(ðpb.SignedBeaconBlock{Signature: []byte{1}}))
|
||||
|
||||
213
beacon-chain/blockchain/gloas.go
Normal file
213
beacon-chain/blockchain/gloas.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
consensus_blocks "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (s *Service) waitUntilEpoch(target primitives.Epoch, secondsPerSlot uint64) error {
|
||||
if slots.ToEpoch(s.CurrentSlot()) >= target {
|
||||
return nil
|
||||
}
|
||||
ticker := slots.NewSlotTicker(s.genesisTime, secondsPerSlot)
|
||||
defer ticker.Done()
|
||||
for {
|
||||
select {
|
||||
case slot := <-ticker.C():
|
||||
if slots.ToEpoch(slot) >= target {
|
||||
return nil
|
||||
}
|
||||
case <-s.ctx.Done():
|
||||
return s.ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getLookupParentRoot returns the root that serves as key to generate the parent state for the passed beacon block.
|
||||
// if it is based on empty or it is pre-Gloas, it is the parent root of the block, otherwise if it is based on full it is
|
||||
// the parent hash.
|
||||
// The caller of this function should not hold a lock on forkchoice.
|
||||
func (s *Service) getLookupParentRoot(b consensus_blocks.ROBlock) ([32]byte, error) {
|
||||
bl := b.Block()
|
||||
parentRoot := bl.ParentRoot()
|
||||
if b.Version() < version.Gloas {
|
||||
return parentRoot, nil
|
||||
}
|
||||
parentSlot, err := s.cfg.ForkChoiceStore.Slot(parentRoot)
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get slot for parent root")
|
||||
}
|
||||
|
||||
if slots.ToEpoch(parentSlot) < params.BeaconConfig().GloasForkEpoch {
|
||||
return parentRoot, nil
|
||||
}
|
||||
blockHash, err := s.cfg.ForkChoiceStore.BlockHash(parentRoot)
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get block hash for parent root")
|
||||
}
|
||||
bid, err := bl.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "failed to get signed execution payload bid from block body")
|
||||
}
|
||||
if bid == nil || bid.Message == nil || len(bid.Message.ParentBlockHash) != 32 {
|
||||
return [32]byte{}, errors.New("invalid signed execution payload bid message")
|
||||
}
|
||||
parentHash := [32]byte(bid.Message.ParentBlockHash)
|
||||
if blockHash == parentHash {
|
||||
return parentHash, nil
|
||||
}
|
||||
return parentRoot, nil
|
||||
}
|
||||
|
||||
func (s *Service) runLatePayloadTasks() {
|
||||
if err := s.waitForSync(); err != nil {
|
||||
log.WithError(err).Error("Failed to wait for initial sync")
|
||||
return
|
||||
}
|
||||
cfg := params.BeaconConfig()
|
||||
if cfg.GloasForkEpoch == math.MaxUint64 {
|
||||
return
|
||||
}
|
||||
if err := s.waitUntilEpoch(cfg.GloasForkEpoch, cfg.SecondsPerSlot); err != nil {
|
||||
return
|
||||
}
|
||||
offset := cfg.SlotComponentDuration(cfg.PayloadAttestationDueBPS)
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, offset, cfg.SecondsPerSlot)
|
||||
defer ticker.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
s.latePayloadTasks(s.ctx)
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting late payload tasks routine")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) checkIfProposing(st state.ReadOnlyBeaconState, slot primitives.Slot) (cache.TrackedValidator, bool) {
|
||||
e := slots.ToEpoch(slot)
|
||||
stateEpoch := slots.ToEpoch(st.Slot())
|
||||
fuluAndNextEpoch := st.Version() >= version.Fulu && e == stateEpoch+1
|
||||
if e == stateEpoch || fuluAndNextEpoch {
|
||||
return s.trackedProposer(st, slot)
|
||||
}
|
||||
return cache.TrackedValidator{}, false
|
||||
}
|
||||
|
||||
// This is a Gloas version of getPayloadAttribute that avoids all the clutter that was originally due to the proposer Index.
|
||||
// It is guaranteed to be called for the current slot + 1 and the head state to have been advanced to at least the current epoch.
|
||||
func (s *Service) getPayloadAttributeGloas(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot, headRoot, accessRoot []byte) payloadattribute.Attributer {
|
||||
emptyAttri := payloadattribute.EmptyWithVersion(st.Version())
|
||||
val, proposing := s.checkIfProposing(st, slot)
|
||||
if !proposing {
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
st, err := transition.ProcessSlotsIfNeeded(ctx, st, accessRoot, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not process slots to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
// Get previous randao.
|
||||
prevRando, err := helpers.RandaoMix(st, time.CurrentEpoch(st))
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get randao mix to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
// Get timestamp.
|
||||
t, err := slots.StartTime(s.genesisTime, slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get timestamp to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
withdrawals, err := st.WithdrawalsForPayload()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload withdrawals to get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
|
||||
attr, err := payloadattribute.New(&enginev1.PayloadAttributesV3{
|
||||
Timestamp: uint64(t.Unix()),
|
||||
PrevRandao: prevRando,
|
||||
SuggestedFeeRecipient: val.FeeRecipient[:],
|
||||
Withdrawals: withdrawals,
|
||||
ParentBeaconBlockRoot: headRoot,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get payload attribute")
|
||||
return emptyAttri
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// latePayloadTasks updates the NSC and epoch boundary caches when there is no payload in the current slot (and there is a block)
|
||||
// The case where the block was also missing would have been dealt by lateBlockTasks already.
|
||||
// We call FCU only if we are proposing next slot, as the execution head is assumed to not have changed.
|
||||
func (s *Service) latePayloadTasks(ctx context.Context) {
|
||||
currentSlot := s.CurrentSlot()
|
||||
if currentSlot != s.HeadSlot() {
|
||||
// We must've already sent a FCU and updated the caches in lateBlockTaks.
|
||||
return
|
||||
}
|
||||
r, err := s.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get head root")
|
||||
return
|
||||
}
|
||||
hr := [32]byte(r)
|
||||
if s.payloadBeingSynced.isSyncing(hr) {
|
||||
return
|
||||
}
|
||||
if s.HasFullNode(hr) {
|
||||
return
|
||||
}
|
||||
st, err := s.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get head state")
|
||||
return
|
||||
}
|
||||
if !s.inRegularSync() {
|
||||
return
|
||||
}
|
||||
attr := s.getPayloadAttributeGloas(ctx, st, currentSlot+1, r, r)
|
||||
if attr == nil || attr.IsEmpty() {
|
||||
return
|
||||
}
|
||||
beaconLatePayloadTaskTriggeredTotal.Inc()
|
||||
// Head is the empty block.
|
||||
bh, err := st.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get latest block hash to notify engine")
|
||||
return
|
||||
}
|
||||
pid, err := s.notifyForkchoiceUpdateGloas(ctx, bh, attr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not notify forkchoice update")
|
||||
return
|
||||
}
|
||||
if pid == nil {
|
||||
log.Warn("Received nil payload ID from forkchoice update.")
|
||||
return
|
||||
}
|
||||
var pId [8]byte
|
||||
copy(pId[:], pid[:])
|
||||
s.cfg.PayloadIDCache.Set(currentSlot+1, hr, pId)
|
||||
}
|
||||
957
beacon-chain/blockchain/gloas_test.go
Normal file
957
beacon-chain/blockchain/gloas_test.go
Normal file
@@ -0,0 +1,957 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
|
||||
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
logTest "github.com/sirupsen/logrus/hooks/test"
|
||||
)
|
||||
|
||||
func prepareGloasForkchoiceState(
|
||||
_ context.Context,
|
||||
slot primitives.Slot,
|
||||
blockRoot [32]byte,
|
||||
parentRoot [32]byte,
|
||||
blockHash [32]byte,
|
||||
parentBlockHash [32]byte,
|
||||
justifiedEpoch primitives.Epoch,
|
||||
finalizedEpoch primitives.Epoch,
|
||||
) (state.BeaconState, blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
|
||||
justifiedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: justifiedEpoch,
|
||||
}
|
||||
|
||||
finalizedCheckpoint := ðpb.Checkpoint{
|
||||
Epoch: finalizedEpoch,
|
||||
}
|
||||
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: justifiedCheckpoint,
|
||||
FinalizedCheckpoint: finalizedCheckpoint,
|
||||
LatestBlockHeader: blockHeader,
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
func testGloasState(t *testing.T, slot primitives.Slot, parentRoot [32]byte, blockHash [32]byte) (*ethpb.BeaconStateGloas, *ethpb.SignedBeaconBlockGloas) {
|
||||
t.Helper()
|
||||
builderPendingPayments := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range builderPendingPayments {
|
||||
builderPendingPayments[i] = ðpb.BuilderPendingPayment{
|
||||
Withdrawal: ðpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)},
|
||||
}
|
||||
}
|
||||
base := ðpb.BeaconStateGloas{
|
||||
Slot: slot,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
BlockRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot),
|
||||
StateRoots: make([][]byte, params.BeaconConfig().SlotsPerHistoricalRoot),
|
||||
Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
StateRoot: make([]byte, 32),
|
||||
BodyRoot: make([]byte, 32),
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: [][]byte{make([]byte, 48)},
|
||||
},
|
||||
Builders: make([]*ethpb.Builder, 0),
|
||||
BuilderPendingPayments: builderPendingPayments,
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
|
||||
}
|
||||
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{SignedExecutionPayloadBid: bid},
|
||||
},
|
||||
})
|
||||
return base, blk
|
||||
}
|
||||
|
||||
func testSignedEnvelope(t *testing.T, blockRoot [32]byte, slot primitives.Slot, blockHash []byte) *ethpb.SignedExecutionPayloadEnvelope {
|
||||
t.Helper()
|
||||
return ðpb.SignedExecutionPayloadEnvelope{
|
||||
Message: ðpb.ExecutionPayloadEnvelope{
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, 32),
|
||||
ReceiptsRoot: make([]byte, 32),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, 32),
|
||||
BaseFeePerGas: make([]byte, 32),
|
||||
BlockHash: blockHash,
|
||||
Transactions: [][]byte{},
|
||||
Withdrawals: []*enginev1.Withdrawal{},
|
||||
},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
BuilderIndex: 0,
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: slot,
|
||||
StateRoot: make([]byte, 32),
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
}
|
||||
|
||||
func setupGloasService(t *testing.T, engineClient *mockExecution.EngineClient) (*Service, *testServiceRequirements) {
|
||||
t.Helper()
|
||||
return minimalTestService(t,
|
||||
WithPayloadIDCache(cache.NewPayloadIDCache()),
|
||||
WithExecutionEngineCaller(engineClient),
|
||||
)
|
||||
}
|
||||
|
||||
func insertGloasBlock(t *testing.T, s *Service, base *ethpb.BeaconStateGloas, blk *ethpb.SignedBeaconBlockGloas, blockRoot [32]byte) {
|
||||
t.Helper()
|
||||
ctx := t.Context()
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.cfg.BeaconDB.SaveBlock(ctx, signed))
|
||||
require.NoError(t, s.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: blockRoot[:], Slot: blk.Block.Slot}))
|
||||
require.NoError(t, s.cfg.StateGen.SaveState(ctx, blockRoot, st))
|
||||
require.NoError(t, s.InsertNode(ctx, st, roblock))
|
||||
}
|
||||
|
||||
func TestGetPayloadEnvelopePrestate_UnknownRoot(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
unknownRoot := bytesutil.ToBytes32([]byte("unknown"))
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: unknownRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
_, err = s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
require.ErrorContains(t, "not found in forkchoice", err)
|
||||
}
|
||||
|
||||
func TestGetPayloadEnvelopePrestate_OK(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{},
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
st, err := s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, primitives.Slot(1), st.Slot())
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Valid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValid)
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Syncing(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrNewPayload: execution.ErrAcceptedSyncingPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, isValid)
|
||||
}
|
||||
|
||||
func TestNotifyNewEnvelope_Invalid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrNewPayload: execution.ErrInvalidPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = s.notifyNewEnvelope(ctx, st, envelope)
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Valid(t *testing.T) {
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
attr := payloadattribute.EmptyWithVersion(version.Gloas)
|
||||
|
||||
retPid, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, attr)
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, pid, retPid)
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Syncing(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrForkchoiceUpdated: execution.ErrAcceptedSyncingPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_Invalid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{
|
||||
ErrForkchoiceUpdated: execution.ErrInvalidPayloadStatus,
|
||||
})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
func TestNotifyForkchoiceUpdateGloas_NilAttributes(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
_, err := s.notifyForkchoiceUpdateGloas(ctx, blockHash, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSavePostPayload(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
protoEnv := testSignedEnvelope(t, blockRoot, 1, blockHash[:])
|
||||
signed, err := blocks.WrappedROSignedExecutionPayloadEnvelope(protoEnv)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.savePostPayload(ctx, signed, st))
|
||||
|
||||
// Verify the envelope was saved in the DB.
|
||||
require.Equal(t, true, s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, blockRoot))
|
||||
}
|
||||
|
||||
func TestValidateExecutionOnEnvelope_Valid(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, parentRoot, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:], ParentHash: make([]byte, 32)},
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
isValid, err := s.validateExecutionOnEnvelope(ctx, st, envelope)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, isValid)
|
||||
}
|
||||
|
||||
func TestPostPayloadHeadUpdate_NotHead(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
root := bytesutil.ToBytes32([]byte("root1"))
|
||||
headRoot := bytesutil.ToBytes32([]byte("different"))
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: root[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:]},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.postPayloadHeadUpdate(ctx, envelope, st, root, headRoot[:]))
|
||||
}
|
||||
|
||||
func TestPostPayloadHeadUpdate_SetsHeadFull(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
root := bytesutil.ToBytes32([]byte("root1"))
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
base, blk := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.head = &head{root: root, block: signed, state: st, slot: 1}
|
||||
require.Equal(t, false, s.head.full)
|
||||
|
||||
env := ðpb.ExecutionPayloadEnvelope{
|
||||
BeaconBlockRoot: root[:],
|
||||
Payload: &enginev1.ExecutionPayloadDeneb{BlockHash: blockHash[:], ParentHash: make([]byte, 32)},
|
||||
Slot: 1,
|
||||
}
|
||||
envelope, err := blocks.WrappedROExecutionPayloadEnvelope(env)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, s.postPayloadHeadUpdate(ctx, envelope, st, root, root[:]))
|
||||
|
||||
s.headLock.RLock()
|
||||
require.Equal(t, true, s.head.full)
|
||||
s.headLock.RUnlock()
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_PreGloas(t *testing.T) {
|
||||
service, _ := minimalTestService(t)
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
blk := util.HydrateSignedBeaconBlockDeneb(ðpb.SignedBeaconBlockDeneb{
|
||||
Block: ðpb.BeaconBlockDeneb{
|
||||
ParentRoot: parentRoot[:],
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasBuildsOnEmpty(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentBlockHash := [32]byte{20}
|
||||
parentNodeBlockHash := [32]byte{99} // Different from parentBlockHash => builds on empty
|
||||
|
||||
// Insert a Gloas node for the parent so BlockHash works.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
blockHash := [32]byte{10}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// parentBlockHash != parentNodeBlockHash, so it builds on empty => returns parentRoot
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasBuildsOnFull(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 0
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentNodeBlockHash := [32]byte{10}
|
||||
|
||||
// Insert a Gloas node for the parent so BlockHash works.
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(ctx, 1, parentRoot, params.BeaconConfig().ZeroHash, parentNodeBlockHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
// Set parentBlockHash in the bid to match the parent's blockHash.
|
||||
blockHash := [32]byte{20}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// parentBlockHash == parentNodeBlockHash, so it builds on full => returns parentBlockHash
|
||||
require.Equal(t, parentNodeBlockHash, got)
|
||||
}
|
||||
|
||||
func TestGetLookupParentRoot_GloasParentPreForkEpoch(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 2
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, req := minimalTestService(t)
|
||||
ctx := t.Context()
|
||||
|
||||
parentRoot := [32]byte{1}
|
||||
parentNodeBlockHash := [32]byte{10}
|
||||
parentSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
|
||||
require.NoError(t, err)
|
||||
parentSlot = parentSlot - 1
|
||||
|
||||
st, parentROBlock, err := prepareGloasForkchoiceState(
|
||||
ctx,
|
||||
parentSlot,
|
||||
parentRoot,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
parentNodeBlockHash,
|
||||
params.BeaconConfig().ZeroHash,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, req.fcs.InsertNode(ctx, st, parentROBlock))
|
||||
|
||||
blockHash := [32]byte{20}
|
||||
bid := util.HydrateSignedExecutionPayloadBid(ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
BlockHash: blockHash[:],
|
||||
ParentBlockHash: parentNodeBlockHash[:],
|
||||
},
|
||||
})
|
||||
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: parentSlot + 1,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
SignedExecutionPayloadBid: bid,
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := service.getLookupParentRoot(roblock)
|
||||
require.NoError(t, err)
|
||||
// Parent slot is pre-fork, so always return parentRoot.
|
||||
require.Equal(t, parentRoot, got)
|
||||
}
|
||||
|
||||
func TestLatePayloadTasks_ReturnsEarlyWhenBlockLate(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
service, tr := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
headRoot := bytesutil.ToBytes32([]byte("headroot"))
|
||||
service.head = &head{
|
||||
root: headRoot,
|
||||
state: st,
|
||||
slot: 1,
|
||||
}
|
||||
// Set genesis time so CurrentSlot > HeadSlot.
|
||||
service.SetGenesisTime(time.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second))
|
||||
|
||||
service.latePayloadTasks(tr.ctx)
|
||||
require.LogsDoNotContain(t, logHook, "Could not notify forkchoice update")
|
||||
// No payload ID should have been cached.
|
||||
_, has := service.cfg.PayloadIDCache.PayloadID(service.CurrentSlot()+1, headRoot)
|
||||
require.Equal(t, false, has)
|
||||
}
|
||||
|
||||
func TestLatePayloadTasks_SendsFCU(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
PrepareAllPayloads: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
base, blk := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
headRoot := bytesutil.ToBytes32([]byte("headroot"))
|
||||
service.head = &head{
|
||||
root: headRoot,
|
||||
block: signed,
|
||||
state: st,
|
||||
slot: 1,
|
||||
}
|
||||
// CurrentSlot == HeadSlot == 1: place genesis 1.5 slots ago so we're solidly in slot 1.
|
||||
service.SetGenesisTime(time.Now().Add(-3 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second / 2))
|
||||
service.SetForkChoiceGenesisTime(service.genesisTime)
|
||||
|
||||
service.latePayloadTasks(tr.ctx)
|
||||
require.LogsDoNotContain(t, logHook, "Could not notify forkchoice update")
|
||||
require.LogsDoNotContain(t, logHook, "Could not get")
|
||||
// Payload ID should have been cached.
|
||||
cachedPid, has := service.cfg.PayloadIDCache.PayloadID(service.CurrentSlot()+1, headRoot)
|
||||
require.Equal(t, true, has)
|
||||
require.Equal(t, primitives.PayloadID(pid[:]), cachedPid)
|
||||
}
|
||||
|
||||
func TestLateBlockTasks_GloasFCU(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
PrepareAllPayloads: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
headRoot := bytesutil.ToBytes32([]byte("headroot"))
|
||||
service.head = &head{
|
||||
root: headRoot,
|
||||
state: st,
|
||||
slot: 1,
|
||||
}
|
||||
|
||||
// Set genesis time so CurrentSlot > HeadSlot, triggering late block logic.
|
||||
service.SetGenesisTime(time.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second))
|
||||
service.SetForkChoiceGenesisTime(service.genesisTime)
|
||||
|
||||
service.lateBlockTasks(tr.ctx)
|
||||
require.LogsDoNotContain(t, logHook, "could not perform late block tasks")
|
||||
|
||||
// Payload ID should have been cached by the Gloas FCU path.
|
||||
cachedPid, has := service.cfg.PayloadIDCache.PayloadID(service.CurrentSlot()+1, headRoot)
|
||||
require.Equal(t, true, has)
|
||||
require.Equal(t, primitives.PayloadID(pid[:]), cachedPid)
|
||||
}
|
||||
|
||||
// TestSaveHead_GloasForkBoundary_PreforkBidForcesEmptyHead verifies that saveHead does not
|
||||
// treat the head as "full" when the latest execution payload bid was issued in a pre-fork epoch.
|
||||
// This guards against the Fulu->Gloas upgrade-seeded bid (bid.BlockHash == latestBlockHash,
|
||||
// bid.Slot == 0) causing a spurious full=true head before any real Gloas bid has been processed.
|
||||
func TestSaveHead_GloasForkBoundary_PreforkBidForcesEmptyHead(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 1
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
// Create a Gloas state where IsParentBlockFull()==true (bid.BlockHash == LatestBlockHash)
|
||||
// but bid.Slot is 0 (epoch 0, pre-fork). This mimics the upgrade-seeded state.
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
// bid.Slot defaults to 0, which is before GloasForkEpoch=1.
|
||||
|
||||
// Set a valid initial head so saveHead's headBlock() call does not panic.
|
||||
// We do NOT insert the old block into forkchoice because insertGloasBlock
|
||||
// would claim the tree root slot; the target block (parentRoot=ZeroHash) must
|
||||
// be the first node inserted so it can become the tree root.
|
||||
oldBlk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{})
|
||||
oldSigned, err2 := blocks.NewSignedBeaconBlock(oldBlk)
|
||||
require.NoError(t, err2)
|
||||
oldSt, err2 := state_native.InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 0,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
|
||||
Eth1Data: ðpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{BlockHash: make([]byte, 32), ParentBlockHash: make([]byte, 32), ParentBlockRoot: make([]byte, 32), PrevRandao: make([]byte, 32), FeeRecipient: make([]byte, 20), BlobKzgCommitments: [][]byte{make([]byte, 48)}},
|
||||
BuilderPendingPayments: func() []*ethpb.BuilderPendingPayment {
|
||||
pp := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range pp {
|
||||
pp[i] = ðpb.BuilderPendingPayment{Withdrawal: ðpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)}}
|
||||
}
|
||||
return pp
|
||||
}(),
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
oldRoot := bytesutil.ToBytes32([]byte("oldroot1"))
|
||||
service.head = &head{root: oldRoot, block: oldSigned, state: oldSt, slot: 0}
|
||||
|
||||
insertGloasBlock(t, service, base, blk, blockRoot)
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify precondition: IsParentBlockFull() is true.
|
||||
full, err := st.IsParentBlockFull()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, full, "precondition: IsParentBlockFull must be true")
|
||||
|
||||
// Verify guard precondition: bid.Slot is pre-fork.
|
||||
bid, err := st.LatestExecutionPayloadBid()
|
||||
require.NoError(t, err)
|
||||
isPrefork := slots.ToEpoch(bid.Slot()) < params.BeaconConfig().GloasForkEpoch
|
||||
require.Equal(t, true, isPrefork, "precondition: bid.Slot must be pre-fork")
|
||||
|
||||
ssigned, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
// saveHead should NOT mark the head as full because bid.Slot < GloasForkEpoch.
|
||||
require.NoError(t, service.saveHead(ctx, blockRoot, ssigned, st))
|
||||
|
||||
service.headLock.RLock()
|
||||
headFull := service.head.full
|
||||
service.headLock.RUnlock()
|
||||
require.Equal(t, false, headFull, "head must not be full for upgrade-seeded bid")
|
||||
}
|
||||
|
||||
// TestSaveHead_GloasForkBoundary_PostforkBidSetsFullHead verifies that saveHead correctly
|
||||
// marks the head as full when the latest bid is from a post-fork epoch.
|
||||
func TestSaveHead_GloasForkBoundary_PostforkBidSetsFullHead(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 1
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
service, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
forkSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
|
||||
require.NoError(t, err)
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
// Set a valid initial head so saveHead's headBlock() call does not panic.
|
||||
// Do NOT use insertGloasBlock for the old block — the target block must be
|
||||
// the first node inserted so it can claim the tree root (parentRoot=ZeroHash).
|
||||
oldBlk2 := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{})
|
||||
oldSigned2, err2 := blocks.NewSignedBeaconBlock(oldBlk2)
|
||||
require.NoError(t, err2)
|
||||
oldSt2, err2 := state_native.InitializeFromProtoUnsafeGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 0,
|
||||
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
LatestBlockHeader: ðpb.BeaconBlockHeader{ParentRoot: make([]byte, 32), StateRoot: make([]byte, 32), BodyRoot: make([]byte, 32)},
|
||||
Eth1Data: ðpb.Eth1Data{DepositRoot: make([]byte, 32), BlockHash: make([]byte, 32)},
|
||||
LatestExecutionPayloadBid: ðpb.ExecutionPayloadBid{BlockHash: make([]byte, 32), ParentBlockHash: make([]byte, 32), ParentBlockRoot: make([]byte, 32), PrevRandao: make([]byte, 32), FeeRecipient: make([]byte, 20), BlobKzgCommitments: [][]byte{make([]byte, 48)}},
|
||||
BuilderPendingPayments: func() []*ethpb.BuilderPendingPayment {
|
||||
pp := make([]*ethpb.BuilderPendingPayment, 64)
|
||||
for i := range pp {
|
||||
pp[i] = ðpb.BuilderPendingPayment{Withdrawal: ðpb.BuilderPendingWithdrawal{FeeRecipient: make([]byte, 20)}}
|
||||
}
|
||||
return pp
|
||||
}(),
|
||||
ExecutionPayloadAvailability: make([]byte, 1024),
|
||||
LatestBlockHash: make([]byte, 32),
|
||||
PayloadExpectedWithdrawals: make([]*enginev1.Withdrawal, 0),
|
||||
ProposerLookahead: make([]primitives.ValidatorIndex, 64),
|
||||
})
|
||||
require.NoError(t, err2)
|
||||
oldRoot2 := bytesutil.ToBytes32([]byte("oldroot2"))
|
||||
service.head = &head{root: oldRoot2, block: oldSigned2, state: oldSt2, slot: 0}
|
||||
|
||||
base, blk := testGloasState(t, forkSlot+1, parentRoot, blockHash)
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
// Set bid.Slot to a post-fork epoch slot.
|
||||
base.LatestExecutionPayloadBid.Slot = forkSlot + 1
|
||||
|
||||
insertGloasBlock(t, service, base, blk, blockRoot)
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify preconditions.
|
||||
full, err := st.IsParentBlockFull()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, full, "precondition: IsParentBlockFull must be true")
|
||||
|
||||
bid, err := st.LatestExecutionPayloadBid()
|
||||
require.NoError(t, err)
|
||||
isPostfork := slots.ToEpoch(bid.Slot()) >= params.BeaconConfig().GloasForkEpoch
|
||||
require.Equal(t, true, isPostfork, "precondition: bid.Slot must be post-fork")
|
||||
|
||||
ssigned, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
// saveHead SHOULD mark the head as full because bid.Slot >= GloasForkEpoch.
|
||||
require.NoError(t, service.saveHead(ctx, blockRoot, ssigned, st))
|
||||
|
||||
service.headLock.RLock()
|
||||
headFull := service.head.full
|
||||
service.headLock.RUnlock()
|
||||
require.Equal(t, true, headFull, "head must be full for real post-fork bid")
|
||||
}
|
||||
|
||||
// TestLateBlockTasks_GloasForkBoundary_PreforkBidUsesHeadRoot verifies that lateBlockTasks
|
||||
// uses headRoot (not LatestBlockHash) as the accessRoot when the bid is pre-fork epoch.
|
||||
// Without this guard, the upgrade-seeded bid would cause lateBlockTasks to use the wrong
|
||||
// access root for the next-slot cache.
|
||||
func TestLateBlockTasks_GloasForkBoundary_PreforkBidUsesHeadRoot(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
resetCfg := features.InitWithReset(&features.Flags{
|
||||
PrepareAllPayloads: true,
|
||||
})
|
||||
defer resetCfg()
|
||||
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.GloasForkEpoch = 1
|
||||
cfg.InitializeForkSchedule()
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
pid := &enginev1.PayloadIDBytes{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
service, tr := setupGloasService(t, &mockExecution.EngineClient{PayloadIDBytes: pid})
|
||||
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
base, _ := testGloasState(t, 1, params.BeaconConfig().ZeroHash, blockHash)
|
||||
// Make IsParentBlockFull() true: bid.BlockHash == LatestBlockHash.
|
||||
base.LatestBlockHash = blockHash[:]
|
||||
// bid.Slot is 0 (pre-fork epoch): the epoch guard should prevent using LatestBlockHash as accessRoot.
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(base)
|
||||
require.NoError(t, err)
|
||||
|
||||
headRoot := bytesutil.ToBytes32([]byte("headroot"))
|
||||
service.head = &head{
|
||||
root: headRoot,
|
||||
state: st,
|
||||
slot: 1,
|
||||
}
|
||||
|
||||
// Trigger late block logic: CurrentSlot > HeadSlot.
|
||||
service.SetGenesisTime(time.Now().Add(-2 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second))
|
||||
service.SetForkChoiceGenesisTime(service.genesisTime)
|
||||
|
||||
service.lateBlockTasks(tr.ctx)
|
||||
require.LogsDoNotContain(t, logHook, "could not perform late block tasks")
|
||||
}
|
||||
@@ -50,6 +50,7 @@ type head struct {
|
||||
block interfaces.ReadOnlySignedBeaconBlock // current head block.
|
||||
state state.BeaconState // current head state.
|
||||
slot primitives.Slot // the head block slot number
|
||||
full bool // whether the head is post-CL or post-EL after Gloas
|
||||
optimistic bool // optimistic status when saved head
|
||||
}
|
||||
|
||||
@@ -60,8 +61,24 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.saveHead")
|
||||
defer span.End()
|
||||
|
||||
// Pre-Gloas we use empty for head because we still key states by blockroot
|
||||
var full bool
|
||||
var err error
|
||||
if headState.Version() >= version.Gloas {
|
||||
gloasFirstSlot, err := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not compute gloas first slot")
|
||||
}
|
||||
if headState.Slot() > gloasFirstSlot {
|
||||
full, err = headState.IsParentBlockFull()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not determine if head is full or not")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Do nothing if head hasn't changed.
|
||||
if !s.isNewHead(newHeadRoot) {
|
||||
if !s.isNewHead(newHeadRoot, full) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,6 +174,7 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
|
||||
state: headState,
|
||||
optimistic: isOptimistic,
|
||||
slot: headBlock.Block().Slot(),
|
||||
full: full,
|
||||
}
|
||||
if err := s.setHead(newHead); err != nil {
|
||||
return errors.Wrap(err, "could not set head")
|
||||
@@ -170,7 +188,7 @@ func (s *Service) saveHead(ctx context.Context, newHeadRoot [32]byte, headBlock
|
||||
// Forward an event capturing a new chain head over a common event feed
|
||||
// done in a goroutine to avoid blocking the critical runtime main routine.
|
||||
go func() {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, headState, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
if err := s.notifyNewHeadEvent(ctx, newHeadSlot, newStateRoot[:], newHeadRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not notify event feed of new chain head")
|
||||
}
|
||||
}()
|
||||
@@ -217,6 +235,7 @@ func (s *Service) setHead(newHead *head) error {
|
||||
root: newHead.root,
|
||||
block: bCp,
|
||||
state: newHead.state.Copy(),
|
||||
full: newHead.full,
|
||||
optimistic: newHead.optimistic,
|
||||
slot: newHead.slot,
|
||||
}
|
||||
@@ -322,7 +341,6 @@ func (s *Service) hasHeadState() bool {
|
||||
func (s *Service) notifyNewHeadEvent(
|
||||
ctx context.Context,
|
||||
newHeadSlot primitives.Slot,
|
||||
newHeadState state.BeaconState,
|
||||
newHeadStateRoot,
|
||||
newHeadRoot []byte,
|
||||
) error {
|
||||
@@ -334,13 +352,16 @@ func (s *Service) notifyNewHeadEvent(
|
||||
if currentDutyDependentRoot == [32]byte{} {
|
||||
currentDutyDependentRoot = s.originBlockRoot
|
||||
}
|
||||
previousDutyDependentRoot := currentDutyDependentRoot
|
||||
var previousDutyDependentRoot [32]byte
|
||||
if currEpoch > 0 {
|
||||
previousDutyDependentRoot, err = s.DependentRoot(currEpoch.Sub(1))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get duty dependent root")
|
||||
}
|
||||
}
|
||||
if previousDutyDependentRoot == [32]byte{} {
|
||||
previousDutyDependentRoot = s.originBlockRoot
|
||||
}
|
||||
|
||||
isOptimistic, err := s.IsOptimistic(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -152,7 +152,6 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
|
||||
func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
t.Run("genesis_state_root", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -165,7 +164,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 1, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), 1, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
@@ -202,7 +201,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 0, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, bState, newHeadStateRoot[:], newHeadRoot[:])
|
||||
err = srv.notifyNewHeadEvent(t.Context(), epoch2Start, newHeadStateRoot[:], newHeadRoot[:])
|
||||
require.NoError(t, err)
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
@@ -214,13 +213,12 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
Block: newHeadRoot[:],
|
||||
State: newHeadStateRoot[:],
|
||||
EpochTransition: true,
|
||||
PreviousDutyDependentRoot: make([]byte, 32),
|
||||
PreviousDutyDependentRoot: srv.originBlockRoot[:],
|
||||
CurrentDutyDependentRoot: srv.originBlockRoot[:],
|
||||
}
|
||||
require.DeepSSZEqual(t, wanted, eventHead)
|
||||
})
|
||||
t.Run("epoch transition", func(t *testing.T) {
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
@@ -234,7 +232,7 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
newHeadSlot := params.BeaconConfig().SlotsPerEpoch
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, bState, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, newHeadStateRoot[:], newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
@@ -245,11 +243,35 @@ func Test_notifyNewHeadEvent(t *testing.T) {
|
||||
Block: newHeadRoot[:],
|
||||
State: newHeadStateRoot[:],
|
||||
EpochTransition: true,
|
||||
PreviousDutyDependentRoot: params.BeaconConfig().ZeroHash[:],
|
||||
PreviousDutyDependentRoot: srv.originBlockRoot[:],
|
||||
CurrentDutyDependentRoot: srv.originBlockRoot[:],
|
||||
}
|
||||
require.DeepSSZEqual(t, wanted, eventHead)
|
||||
})
|
||||
t.Run("previous dependent root zero hash falls back to origin", func(t *testing.T) {
|
||||
srv := testServiceWithDB(t)
|
||||
srv.SetGenesisTime(time.Now())
|
||||
notifier := srv.cfg.StateNotifier.(*mock.MockStateNotifier)
|
||||
srv.originBlockRoot = [32]byte{0xab}
|
||||
st, blk, err := prepareForkchoiceState(t.Context(), 0, [32]byte{}, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
newHeadRoot := [32]byte{3}
|
||||
st, blk, err = prepareForkchoiceState(t.Context(), 32, newHeadRoot, [32]byte{}, [32]byte{}, ðpb.Checkpoint{}, ðpb.Checkpoint{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, srv.cfg.ForkChoiceStore.InsertNode(t.Context(), st, blk))
|
||||
newHeadSlot := params.BeaconConfig().SlotsPerEpoch
|
||||
require.NoError(t, srv.notifyNewHeadEvent(t.Context(), newHeadSlot, []byte{2}, newHeadRoot[:]))
|
||||
events := notifier.ReceivedEvents()
|
||||
require.Equal(t, 1, len(events))
|
||||
|
||||
eventHead, ok := events[0].Data.(*ethpbv1.EventHead)
|
||||
require.Equal(t, true, ok)
|
||||
// DependentRoot(0) returns zero hash since the forkchoice tree is sparse.
|
||||
// The fix ensures it falls back to originBlockRoot instead of sending zeros.
|
||||
assert.DeepEqual(t, srv.originBlockRoot[:], eventHead.PreviousDutyDependentRoot)
|
||||
assert.DeepEqual(t, srv.originBlockRoot[:], eventHead.CurrentDutyDependentRoot)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRetrieveHead_ReadOnly(t *testing.T) {
|
||||
|
||||
@@ -77,10 +77,10 @@ func VerifyBlobKZGProofBatch(blobs [][]byte, commitments [][]byte, proofs [][]by
|
||||
return fmt.Errorf("blobs len (%d) differs from expected (%d)", len(blobs[i]), len(ckzg4844.Blob{}))
|
||||
}
|
||||
if len(commitments[i]) != len(ckzg4844.Bytes48{}) {
|
||||
return fmt.Errorf("commitments len (%d) differs from expected (%d)", len(commitments[i]), len(ckzg4844.Blob{}))
|
||||
return fmt.Errorf("commitments len (%d) differs from expected (%d)", len(commitments[i]), len(ckzg4844.Bytes48{}))
|
||||
}
|
||||
if len(proofs[i]) != len(ckzg4844.Bytes48{}) {
|
||||
return fmt.Errorf("proofs len (%d) differs from expected (%d)", len(proofs[i]), len(ckzg4844.Blob{}))
|
||||
return fmt.Errorf("proofs len (%d) differs from expected (%d)", len(proofs[i]), len(ckzg4844.Bytes48{}))
|
||||
}
|
||||
ckzgBlobs[i] = ckzg4844.Blob(blobs[i])
|
||||
ckzgCommitments[i] = ckzg4844.Bytes48(commitments[i])
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/io/logs"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
prysmTime "github.com/OffchainLabs/prysm/v7/time"
|
||||
@@ -40,7 +40,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
}
|
||||
log = log.WithField("syncBitsCount", agg.SyncCommitteeBits.Count())
|
||||
}
|
||||
if b.Version() >= version.Bellatrix {
|
||||
if b.Version() >= version.Bellatrix && b.Version() < version.Gloas {
|
||||
p, err := b.Body().Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -56,7 +56,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
txsPerSlotCount.Set(float64(len(txs)))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Deneb {
|
||||
if b.Version() >= version.Deneb && b.Version() < version.Gloas {
|
||||
kzgs, err := b.Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get blob KZG commitments")
|
||||
@@ -64,7 +64,7 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Electra {
|
||||
if b.Version() >= version.Electra && b.Version() < version.Gloas {
|
||||
eReqs, err := b.Body().ExecutionRequests()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get execution requests")
|
||||
@@ -80,6 +80,20 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Gloas {
|
||||
signedBid, err := b.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get signed execution payload bid")
|
||||
} else {
|
||||
bid := signedBid.Message
|
||||
log = log.WithFields(logrus.Fields{
|
||||
"blobKzgCommitmentCount": len(bid.BlobKzgCommitments),
|
||||
"payloadHash": fmt.Sprintf("%#x", bytesutil.Trunc(bid.BlockHash)),
|
||||
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(bid.ParentBlockHash)),
|
||||
"builderIndex": bid.BuilderIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
log.Info("Finished applying state transition")
|
||||
return nil
|
||||
}
|
||||
@@ -87,46 +101,62 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
func logBlockSyncStatus(block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte, justified, finalized *ethpb.Checkpoint, receivedTime time.Time, genesis time.Time, daWaitedTime time.Duration) error {
|
||||
startTime, err := slots.StartTime(genesis, block.Slot())
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "failed to get slot start time")
|
||||
}
|
||||
level := log.Logger.GetLevel()
|
||||
if level >= logrus.DebugLevel {
|
||||
parentRoot := block.ParentRoot()
|
||||
lf := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"justifiedEpoch": justified.Epoch,
|
||||
"justifiedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(justified.Root)[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"parentRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(parentRoot[:])[:8]),
|
||||
"version": version.String(block.Version()),
|
||||
"sinceSlotStartTime": prysmTime.Now().Sub(startTime),
|
||||
"chainServiceProcessedTime": prysmTime.Now().Sub(receivedTime) - daWaitedTime,
|
||||
"dataAvailabilityWaitedTime": daWaitedTime,
|
||||
}
|
||||
log.WithFields(lf).Debug("Synced new block")
|
||||
parentRoot := block.ParentRoot()
|
||||
blkRoot := fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8])
|
||||
finalizedRoot := fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8])
|
||||
sinceSlotStartTime := prysmTime.Now().Sub(startTime)
|
||||
|
||||
lessFields := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": blkRoot,
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": finalizedRoot,
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"sinceSlotStartTime": sinceSlotStartTime,
|
||||
}
|
||||
moreFields := logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slotInEpoch": block.Slot() % params.BeaconConfig().SlotsPerEpoch,
|
||||
"block": blkRoot,
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
"justifiedEpoch": justified.Epoch,
|
||||
"justifiedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(justified.Root)[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": finalizedRoot,
|
||||
"parentRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(parentRoot[:])[:8]),
|
||||
"version": version.String(block.Version()),
|
||||
"sinceSlotStartTime": sinceSlotStartTime,
|
||||
"chainServiceProcessedTime": prysmTime.Now().Sub(receivedTime) - daWaitedTime,
|
||||
}
|
||||
if block.Version() < version.Gloas {
|
||||
moreFields["dataAvailabilityWaitedTime"] = daWaitedTime
|
||||
} else {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"block": fmt.Sprintf("0x%s...", hex.EncodeToString(blockRoot[:])[:8]),
|
||||
"finalizedEpoch": finalized.Epoch,
|
||||
"finalizedRoot": fmt.Sprintf("0x%s...", hex.EncodeToString(finalized.Root)[:8]),
|
||||
"epoch": slots.ToEpoch(block.Slot()),
|
||||
}).Info("Synced new block")
|
||||
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get signed execution payload bid for logging")
|
||||
} else {
|
||||
moreFields["blockHash"] = fmt.Sprintf("%#x", bytesutil.Trunc(signedBid.Message.BlockHash))
|
||||
moreFields["parentHash"] = fmt.Sprintf("%#x", bytesutil.Trunc(signedBid.Message.ParentBlockHash))
|
||||
moreFields["builderIndex"] = signedBid.Message.BuilderIndex
|
||||
}
|
||||
}
|
||||
|
||||
level := logs.PackageVerbosity("beacon-chain/blockchain")
|
||||
if level >= logrus.DebugLevel {
|
||||
log.WithFields(moreFields).Info("Synced new block")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithFields(lessFields).WithField(logs.LogTargetField, logs.LogTargetUser).Info("Synced new block")
|
||||
log.WithFields(moreFields).WithField(logs.LogTargetField, logs.LogTargetEphemeral).Info("Synced new block")
|
||||
return nil
|
||||
}
|
||||
|
||||
// logs payload related data every slot.
|
||||
func logPayload(block interfaces.ReadOnlyBeaconBlock) error {
|
||||
isExecutionBlk, err := blocks.IsExecutionBlock(block.Body())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not determine if block is execution block")
|
||||
}
|
||||
if !isExecutionBlk {
|
||||
if block.Version() < version.Bellatrix || block.Version() >= version.Gloas {
|
||||
return nil
|
||||
}
|
||||
payload, err := block.Body().Execution()
|
||||
|
||||
@@ -234,6 +234,25 @@ var (
|
||||
Help: "The maximum number of blobs allowed in a block.",
|
||||
},
|
||||
)
|
||||
beaconExecutionPayloadEnvelopeValidTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "beacon_execution_payload_envelope_valid_total",
|
||||
Help: "Count the number of execution payload envelopes that were processed successfully.",
|
||||
})
|
||||
beaconExecutionPayloadEnvelopeInvalidTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "beacon_execution_payload_envelope_invalid_total",
|
||||
Help: "Count the number of execution payload envelopes that failed processing.",
|
||||
})
|
||||
beaconExecutionPayloadEnvelopeProcessingDurationSeconds = promauto.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "beacon_execution_payload_envelope_processing_duration_seconds",
|
||||
Help: "Captures end-to-end processing time for execution payload envelopes.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
)
|
||||
beaconLatePayloadTaskTriggeredTotal = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "beacon_late_payload_task_triggered_total",
|
||||
Help: "Count the number of times late payload tasks fired.",
|
||||
})
|
||||
)
|
||||
|
||||
// reportSlotMetrics reports slot related metrics.
|
||||
|
||||
@@ -96,6 +96,15 @@ func WithTrackedValidatorsCache(c *cache.TrackedValidatorsCache) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithProposerPreferencesCache sets the proposer preferences cache used to
|
||||
// look up fee recipient and gas limit from Gloas gossip preferences.
|
||||
func WithProposerPreferencesCache(c *cache.ProposerPreferencesCache) Option {
|
||||
return func(s *Service) error {
|
||||
s.cfg.ProposerPreferencesCache = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAttestationCache for attestation lifecycle after chain inclusion.
|
||||
func WithAttestationCache(c *cache.AttestationCache) Option {
|
||||
return func(s *Service) error {
|
||||
|
||||
@@ -96,7 +96,11 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
|
||||
// We assume trusted attestation in this function has verified signature.
|
||||
|
||||
// Update forkchoice store with the new attestation for updating weight.
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Target.Epoch)
|
||||
|
||||
attData := a.GetData()
|
||||
payloadStatus := true
|
||||
if attData.Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = attData.CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(attData.BeaconBlockRoot), attData.Slot, payloadStatus)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
||||
coreTime "github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
@@ -64,7 +66,6 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
return invalidBlock{error: err}
|
||||
}
|
||||
startTime := time.Now()
|
||||
fcuArgs := &fcuConfig{}
|
||||
|
||||
if features.Get().EnableLightClient && slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().AltairForkEpoch {
|
||||
defer s.processLightClientUpdates(cfg)
|
||||
@@ -83,6 +84,9 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
if err := s.handleBlockAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not handle block's attestations")
|
||||
}
|
||||
if err := s.handleBlockPayloadAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not handle block's payload attestations")
|
||||
}
|
||||
|
||||
s.InsertSlashingsToForkChoiceStore(ctx, cfg.roblock.Block().Body().AttesterSlashings())
|
||||
if cfg.isValidPayload {
|
||||
@@ -102,7 +106,14 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
s.logNonCanonicalBlockReceived(cfg.roblock.Root(), cfg.headRoot)
|
||||
return nil
|
||||
}
|
||||
s.sendFCU(cfg, fcuArgs)
|
||||
if cfg.roblock.Version() < version.Gloas {
|
||||
s.sendFCU(cfg)
|
||||
} else if s.isNewHead(cfg.headRoot, false) { // We reach this only when the incoming block is head.
|
||||
if err := s.saveHead(ctx, cfg.headRoot, cfg.roblock, cfg.postState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
s.pruneAttsFromPool(ctx, cfg.postState, cfg.roblock)
|
||||
}
|
||||
|
||||
// Pre-Fulu the caches are updated when computing the payload attributes
|
||||
if cfg.postState.Version() >= version.Fulu {
|
||||
@@ -124,7 +135,7 @@ func getStateVersionAndPayload(st state.BeaconState) (int, interfaces.ExecutionD
|
||||
var err error
|
||||
preStateVersion := st.Version()
|
||||
switch preStateVersion {
|
||||
case version.Phase0, version.Altair:
|
||||
case version.Phase0, version.Altair, version.Gloas:
|
||||
default:
|
||||
preStateHeader, err = st.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
@@ -134,7 +145,112 @@ func getStateVersionAndPayload(st state.BeaconState) (int, interfaces.ExecutionD
|
||||
return preStateVersion, preStateHeader, nil
|
||||
}
|
||||
|
||||
func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlock, avs das.AvailabilityChecker) error {
|
||||
// applyPayloadIfNeeded applies the parent block's execution payload envelope to
|
||||
// preState when the current block's bid indicates it built on a full parent.
|
||||
func (s *Service) applyPayloadIfNeeded(ctx context.Context, b interfaces.ReadOnlyBeaconBlock, parentRoot [32]byte, preState state.BeaconState) error {
|
||||
if b.Version() < version.Gloas || parentRoot == [32]byte{} {
|
||||
return nil
|
||||
}
|
||||
parentBlock, err := s.cfg.BeaconDB.Block(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get parent block with root %#x", parentRoot)
|
||||
}
|
||||
if parentBlock.Version() < version.Gloas {
|
||||
return nil
|
||||
}
|
||||
sb, err := b.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload bid for block")
|
||||
}
|
||||
if sb == nil || sb.Message == nil {
|
||||
return fmt.Errorf("missing execution payload bid for block at slot %d", b.Slot())
|
||||
}
|
||||
parentBid, err := parentBlock.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get execution payload bid for parent block with root %#x", parentRoot)
|
||||
}
|
||||
if parentBid == nil || parentBid.Message == nil {
|
||||
return fmt.Errorf("missing execution payload bid for parent block with root %#x", parentRoot)
|
||||
}
|
||||
if !bytes.Equal(sb.Message.ParentBlockHash, parentBid.Message.BlockHash) {
|
||||
return nil
|
||||
}
|
||||
signedEnvelope, err := s.cfg.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get execution payload envelope for parent block with root %#x", parentRoot)
|
||||
}
|
||||
if signedEnvelope == nil || signedEnvelope.Message == nil {
|
||||
return nil
|
||||
}
|
||||
envelope, err := consensusblocks.WrappedROBlindedExecutionPayloadEnvelope(signedEnvelope.Message)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not wrap blinded execution payload envelope for parent block with root %#x", parentRoot)
|
||||
}
|
||||
return gloas.ProcessBlindedExecutionPayload(ctx, preState, parentBlock.Block().StateRoot(), envelope)
|
||||
}
|
||||
|
||||
// getBatchPrestate returns the pre-state to apply to the first beacon block in the batch and returns true if it applied the first envelope before
|
||||
func (s *Service) getBatchPrestate(ctx context.Context, b consensusblocks.ROBlock, envelopes []interfaces.ROSignedExecutionPayloadEnvelope) (state.BeaconState, bool, error) {
|
||||
if len(envelopes) == 0 || b.Version() < version.Gloas {
|
||||
blockPreState, err := s.cfg.StateGen.StateByRootInitialSync(ctx, b.Block().ParentRoot())
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not get block pre state")
|
||||
}
|
||||
return blockPreState, false, nil
|
||||
}
|
||||
parentRoot := b.Block().ParentRoot()
|
||||
full, err := consensusblocks.BlockBuiltOnEnvelope(envelopes[0], b)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not check if block builds on envelope")
|
||||
}
|
||||
blockPreState, err := s.cfg.StateGen.StateByRootInitialSync(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not get block pre state")
|
||||
}
|
||||
if !full {
|
||||
return blockPreState, false, nil
|
||||
}
|
||||
parentBlock, err := s.cfg.BeaconDB.Block(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not get parent block")
|
||||
}
|
||||
if s.cfg.BeaconDB.HasExecutionPayloadEnvelope(ctx, parentRoot) {
|
||||
// The parent envelope was already saved by a previous batch but the
|
||||
// replayed state may not include it (replay skips the last block's
|
||||
// envelope). Load the blinded form from DB and apply it.
|
||||
blindedEnv, err := s.cfg.BeaconDB.ExecutionPayloadEnvelope(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not load parent blinded envelope from DB")
|
||||
}
|
||||
wrappedEnv, err := consensusblocks.WrappedROBlindedExecutionPayloadEnvelope(blindedEnv.Message)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not wrap blinded envelope")
|
||||
}
|
||||
if err := gloas.ProcessBlindedExecutionPayload(ctx, blockPreState, parentBlock.Block().StateRoot(), wrappedEnv); err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not apply parent blinded envelope from DB")
|
||||
}
|
||||
return blockPreState, true, nil
|
||||
}
|
||||
env, err := envelopes[0].Envelope()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
// notify the engine of the new envelope
|
||||
if _, err := s.notifyNewEnvelope(ctx, blockPreState, env); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := gloas.ProcessBlindedExecutionPayload(ctx, blockPreState, parentBlock.Block().StateRoot(), env); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return blockPreState, true, nil
|
||||
}
|
||||
|
||||
type versionAndHeader struct {
|
||||
version int
|
||||
header interfaces.ExecutionData
|
||||
}
|
||||
|
||||
func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlock, envelopes []interfaces.ROSignedExecutionPayloadEnvelope, avs das.AvailabilityChecker) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.onBlockBatch")
|
||||
defer span.End()
|
||||
|
||||
@@ -148,16 +264,35 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
b := blks[0].Block()
|
||||
|
||||
// Retrieve incoming block's pre state.
|
||||
if err := s.verifyBlkPreState(ctx, b.ParentRoot()); err != nil {
|
||||
parentRoot := b.ParentRoot()
|
||||
if err := s.verifyBlkPreState(ctx, parentRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
preState, err := s.cfg.StateGen.StateByRootInitialSync(ctx, b.ParentRoot())
|
||||
preState, applied, err := s.getBatchPrestate(ctx, blks[0], envelopes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return fmt.Errorf("nil pre state for slot %d", b.Slot())
|
||||
}
|
||||
var eidx int
|
||||
var br [32]byte
|
||||
sigSet := bls.NewSet()
|
||||
if applied {
|
||||
eidx = 1
|
||||
envSigSet, err := gloas.ExecutionPayloadEnvelopeSignatureBatch(preState, envelopes[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigSet.Join(envSigSet)
|
||||
}
|
||||
if eidx < len(envelopes) {
|
||||
env, err := envelopes[eidx].Envelope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
br = env.BeaconBlockRoot()
|
||||
}
|
||||
|
||||
// Fill in missing blocks
|
||||
if err := s.fillInForkChoiceMissingBlocks(ctx, blks[0], preState.FinalizedCheckpoint(), preState.CurrentJustifiedCheckpoint()); err != nil {
|
||||
@@ -166,11 +301,6 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
|
||||
jCheckpoints := make([]*ethpb.Checkpoint, len(blks))
|
||||
fCheckpoints := make([]*ethpb.Checkpoint, len(blks))
|
||||
sigSet := bls.NewSet()
|
||||
type versionAndHeader struct {
|
||||
version int
|
||||
header interfaces.ExecutionData
|
||||
}
|
||||
preVersionAndHeaders := make([]*versionAndHeader, len(blks))
|
||||
postVersionAndHeaders := make([]*versionAndHeader, len(blks))
|
||||
var set *bls.SignatureBatch
|
||||
@@ -192,6 +322,23 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
if err != nil {
|
||||
return invalidBlock{error: err}
|
||||
}
|
||||
if b.Root() == br && eidx < len(envelopes) {
|
||||
envSigSet, err := gloas.ProcessExecutionPayloadWithDeferredSig(ctx, preState, b.Block().StateRoot(), envelopes[eidx])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sigSet.Join(envSigSet)
|
||||
eidx++
|
||||
if eidx < len(envelopes) {
|
||||
nextEnv, err := envelopes[eidx].Envelope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
br = nextEnv.BeaconBlockRoot()
|
||||
} else {
|
||||
br = [32]byte{}
|
||||
}
|
||||
}
|
||||
// Save potential boundary states.
|
||||
if slots.IsEpochStart(preState.Slot()) {
|
||||
boundaries[b.Root()] = preState.Copy()
|
||||
@@ -223,55 +370,9 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return errors.New("batch block signature verification failed")
|
||||
}
|
||||
|
||||
// blocks have been verified, save them and call the engine
|
||||
pendingNodes := make([]*forkchoicetypes.BlockAndCheckpoints, len(blks))
|
||||
var isValidPayload bool
|
||||
for i, b := range blks {
|
||||
root := b.Root()
|
||||
isValidPayload, err = s.notifyNewPayload(ctx,
|
||||
postVersionAndHeaders[i].version,
|
||||
postVersionAndHeaders[i].header, b)
|
||||
if err != nil {
|
||||
return s.handleInvalidExecutionError(ctx, err, root, b.Block().ParentRoot())
|
||||
}
|
||||
if isValidPayload {
|
||||
if err := s.validateMergeTransitionBlock(ctx, preVersionAndHeaders[i].version,
|
||||
preVersionAndHeaders[i].header, b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.areSidecarsAvailable(ctx, avs, b); err != nil {
|
||||
return errors.Wrapf(err, "could not validate sidecar availability for block %#x at slot %d", b.Root(), b.Block().Slot())
|
||||
}
|
||||
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b,
|
||||
JustifiedCheckpoint: jCheckpoints[i],
|
||||
FinalizedCheckpoint: fCheckpoints[i]}
|
||||
pendingNodes[i] = args
|
||||
if err := s.saveInitSyncBlock(ctx, root, b); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
if err := s.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: b.Block().Slot(),
|
||||
Root: root[:],
|
||||
}); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
if i > 0 && jCheckpoints[i].Epoch > jCheckpoints[i-1].Epoch {
|
||||
if err := s.cfg.BeaconDB.SaveJustifiedCheckpoint(ctx, jCheckpoints[i]); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if i > 0 && fCheckpoints[i].Epoch > fCheckpoints[i-1].Epoch {
|
||||
if err := s.updateFinalized(ctx, fCheckpoints[i]); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
pendingNodes, isValidPayload, err := s.notifyEngineAndSaveData(ctx, blks, envelopes, avs, preVersionAndHeaders, postVersionAndHeaders, jCheckpoints, fCheckpoints)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Save boundary states that will be useful for forkchoice
|
||||
for r, st := range boundaries {
|
||||
@@ -286,6 +387,15 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return err
|
||||
}
|
||||
// Insert all nodes to forkchoice
|
||||
if applied {
|
||||
env, err := envelopes[0].Envelope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.InsertPayload(env); err != nil {
|
||||
return errors.Wrap(err, "could not insert first payload in batch to forkchoice")
|
||||
}
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.InsertChain(ctx, pendingNodes); err != nil {
|
||||
return errors.Wrap(err, "could not insert batch to forkchoice")
|
||||
}
|
||||
@@ -298,13 +408,120 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return s.saveHeadNoDB(ctx, lastB, lastBR, preState, !isValidPayload)
|
||||
}
|
||||
|
||||
func (s *Service) notifyEngineAndSaveData(
|
||||
ctx context.Context,
|
||||
blks []consensusblocks.ROBlock,
|
||||
envelopes []interfaces.ROSignedExecutionPayloadEnvelope,
|
||||
avs das.AvailabilityChecker,
|
||||
preVersionAndHeaders []*versionAndHeader,
|
||||
postVersionAndHeaders []*versionAndHeader,
|
||||
jCheckpoints []*ethpb.Checkpoint,
|
||||
fCheckpoints []*ethpb.Checkpoint,
|
||||
) ([]*forkchoicetypes.BlockAndCheckpoints, bool, error) {
|
||||
span := trace.FromContext(ctx)
|
||||
pendingNodes := make([]*forkchoicetypes.BlockAndCheckpoints, len(blks))
|
||||
var isValidPayload bool
|
||||
var err error
|
||||
|
||||
envMap := make(map[[32]byte]int, len(envelopes))
|
||||
for i, e := range envelopes {
|
||||
env, err := e.Envelope()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
envMap[env.BeaconBlockRoot()] = i
|
||||
}
|
||||
|
||||
for i, b := range blks {
|
||||
root := b.Root()
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b,
|
||||
JustifiedCheckpoint: jCheckpoints[i],
|
||||
FinalizedCheckpoint: fCheckpoints[i]}
|
||||
if b.Version() < version.Gloas {
|
||||
isValidPayload, err = s.notifyNewPayload(ctx,
|
||||
postVersionAndHeaders[i].version,
|
||||
postVersionAndHeaders[i].header, b)
|
||||
if err != nil {
|
||||
return nil, false, s.handleInvalidExecutionError(ctx, err, root, b.Block().ParentRoot(), [32]byte(postVersionAndHeaders[i].header.ParentHash()))
|
||||
}
|
||||
if isValidPayload {
|
||||
if err := s.validateMergeTransitionBlock(ctx, preVersionAndHeaders[i].version,
|
||||
preVersionAndHeaders[i].header, b); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
idx, ok := envMap[root]
|
||||
if ok {
|
||||
env, err := envelopes[idx].Envelope()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
isValidPayload, err = s.notifyNewEnvelopeFromBlock(ctx, b, env)
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "could not notify new envelope from block")
|
||||
}
|
||||
args.HasPayload = true
|
||||
bh := env.BlockHash()
|
||||
if err := s.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: b.Block().Slot(),
|
||||
Root: bh[:],
|
||||
}); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := s.areSidecarsAvailable(ctx, avs, b); err != nil {
|
||||
return nil, false, errors.Wrapf(err, "could not validate sidecar availability for block %#x at slot %d", b.Root(), b.Block().Slot())
|
||||
}
|
||||
|
||||
pendingNodes[i] = args
|
||||
if err := s.saveInitSyncBlock(ctx, root, b); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, false, err
|
||||
}
|
||||
if err := s.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{
|
||||
Slot: b.Block().Slot(),
|
||||
Root: root[:],
|
||||
}); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, false, err
|
||||
}
|
||||
if i > 0 && jCheckpoints[i].Epoch > jCheckpoints[i-1].Epoch {
|
||||
if err := s.cfg.BeaconDB.SaveJustifiedCheckpoint(ctx, jCheckpoints[i]); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
if i > 0 && fCheckpoints[i].Epoch > fCheckpoints[i-1].Epoch {
|
||||
if err := s.updateFinalized(ctx, fCheckpoints[i]); err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return pendingNodes, isValidPayload, nil
|
||||
}
|
||||
|
||||
func (s *Service) areSidecarsAvailable(ctx context.Context, avs das.AvailabilityChecker, roBlock consensusblocks.ROBlock) error {
|
||||
blockVersion := roBlock.Version()
|
||||
block := roBlock.Block()
|
||||
slot := block.Slot()
|
||||
|
||||
if blockVersion >= version.Fulu {
|
||||
if err := s.areDataColumnsAvailable(ctx, roBlock.Root(), block); err != nil {
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := s.areDataColumnsAvailable(ctx, roBlock.Root(), slot); err != nil {
|
||||
return errors.Wrapf(err, "are data columns available for block %#x with slot %d", roBlock.Root(), slot)
|
||||
}
|
||||
|
||||
@@ -366,9 +583,49 @@ func (s *Service) updateEpochBoundaryCaches(ctx context.Context, st state.Beacon
|
||||
return nil
|
||||
}
|
||||
|
||||
// refreshCaches updates the next slot state cache and epoch boundary caches.
|
||||
// Before Fulu this is done synchronously, after Fulu it is deferred to a goroutine.
|
||||
func (s *Service) refreshCaches(ctx context.Context, currentSlot primitives.Slot, headRoot [32]byte, headState state.BeaconState, accessRoot [32]byte) {
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
if lastState.Version() < version.Fulu {
|
||||
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
|
||||
} else {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
s.updateCachesAndEpochBoundary(ctx, currentSlot, headState, accessRoot, lastRoot, lastState)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// updateCachesAndEpochBoundary updates the next slot state cache and handles
|
||||
// epoch boundary processing. If the lastRoot matches accessRoot, the cached
|
||||
// last state is reused; otherwise, the head state is advanced instead.
|
||||
func (s *Service) updateCachesAndEpochBoundary(ctx context.Context, currentSlot primitives.Slot, headState state.BeaconState, accessRoot [32]byte, lastRoot []byte, lastState state.BeaconState) {
|
||||
if bytes.Equal(lastRoot, accessRoot[:]) {
|
||||
// Happy case, the last advanced state is head, we thus keep it
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
} else {
|
||||
// Last advanced state was not head, we do not advance this but rather use headstate
|
||||
headState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, accessRoot[:], headState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, accessRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
}
|
||||
|
||||
// Epoch boundary tasks: it copies the headState and updates the epoch boundary
|
||||
// caches. The caller of this function must not hold a lock in forkchoice store.
|
||||
func (s *Service) handleEpochBoundary(ctx context.Context, slot primitives.Slot, headState state.BeaconState, blockRoot []byte) error {
|
||||
func (s *Service) handleEpochBoundary(ctx context.Context, slot primitives.Slot, headState state.ReadOnlyBeaconState, blockRoot []byte) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.handleEpochBoundary")
|
||||
defer span.End()
|
||||
// return early if we are advancing to a past epoch
|
||||
@@ -401,7 +658,11 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
}
|
||||
r := bytesutil.ToBytes32(a.GetData().BeaconBlockRoot)
|
||||
if s.cfg.ForkChoiceStore.HasNode(r) {
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Target.Epoch)
|
||||
payloadStatus := true
|
||||
if a.GetData().Target.Epoch >= params.BeaconConfig().GloasForkEpoch {
|
||||
payloadStatus = a.GetData().CommitteeIndex == 1
|
||||
}
|
||||
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Slot, payloadStatus)
|
||||
} else if features.Get().EnableExperimentalAttestationPool {
|
||||
if err = s.cfg.AttestationCache.Add(a); err != nil {
|
||||
return err
|
||||
@@ -413,6 +674,36 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBlockPayloadAttestations feeds payload attestations included in a Gloas block into forkchoice.
|
||||
func (s *Service) handleBlockPayloadAttestations(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock, st state.BeaconState) error {
|
||||
if blk.Version() < version.Gloas {
|
||||
return nil
|
||||
}
|
||||
atts, err := blk.Body().PayloadAttestations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(atts) == 0 {
|
||||
return nil
|
||||
}
|
||||
committee, err := st.PayloadCommitteeReadOnly(blk.Slot() - 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, att := range atts {
|
||||
root := bytesutil.ToBytes32(att.Data.BeaconBlockRoot)
|
||||
if !s.cfg.ForkChoiceStore.HasNode(root) {
|
||||
continue
|
||||
}
|
||||
for i := range committee {
|
||||
if att.AggregationBits.BitAt(uint64(i)) {
|
||||
s.cfg.ForkChoiceStore.SetPTCVote(root, uint64(i), att.Data.PayloadPresent, att.Data.BlobDataAvailable)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertSlashingsToForkChoiceStore inserts attester slashing indices to fork choice store.
|
||||
// To call this function, it's caller's responsibility to ensure the slashing object is valid.
|
||||
// This function requires a write lock on forkchoice.
|
||||
@@ -543,6 +834,9 @@ func (s *Service) validateMergeTransitionBlock(ctx context.Context, stateVersion
|
||||
if blocks.IsPreBellatrixVersion(blk.Block().Version()) {
|
||||
return nil
|
||||
}
|
||||
if blk.Block().Version() >= version.Gloas {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip validation if block has an empty payload.
|
||||
payload, err := blk.Block().Body().Execution()
|
||||
@@ -583,11 +877,22 @@ func (s *Service) runLateBlockTasks() {
|
||||
return
|
||||
}
|
||||
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 3
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
cfg := params.BeaconConfig()
|
||||
attDueBPS := cfg.AttestationDueBPS
|
||||
if slots.ToEpoch(s.CurrentSlot()) >= cfg.GloasForkEpoch {
|
||||
attDueBPS = cfg.AttestationDueBPSGloas
|
||||
}
|
||||
attThreshold := cfg.SlotComponentDuration(attDueBPS)
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, attThreshold, cfg.SecondsPerSlot)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
case slot := <-ticker.C():
|
||||
if attDueBPS != cfg.AttestationDueBPSGloas && slots.ToEpoch(slot) >= cfg.GloasForkEpoch {
|
||||
ticker.Done()
|
||||
attDueBPS = cfg.AttestationDueBPSGloas
|
||||
attThreshold = cfg.SlotComponentDuration(attDueBPS)
|
||||
ticker = slots.NewSlotTickerWithOffset(s.genesisTime, attThreshold, cfg.SecondsPerSlot)
|
||||
}
|
||||
s.lateBlockTasks(s.ctx)
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
@@ -664,7 +969,18 @@ func (s *Service) isDataAvailable(
|
||||
root := roBlock.Root()
|
||||
blockVersion := block.Version()
|
||||
if blockVersion >= version.Fulu {
|
||||
return s.areDataColumnsAvailable(ctx, root, block)
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.areDataColumnsAvailable(ctx, root, block.Slot())
|
||||
}
|
||||
|
||||
if blockVersion >= version.Deneb {
|
||||
@@ -679,30 +995,15 @@ func (s *Service) isDataAvailable(
|
||||
func (s *Service) areDataColumnsAvailable(
|
||||
ctx context.Context,
|
||||
root [fieldparams.RootLength]byte,
|
||||
block interfaces.ReadOnlyBeaconBlock,
|
||||
slot primitives.Slot,
|
||||
) error {
|
||||
// We are only required to check within MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS
|
||||
blockSlot, currentSlot := block.Slot(), s.CurrentSlot()
|
||||
blockEpoch, currentEpoch := slots.ToEpoch(blockSlot), slots.ToEpoch(currentSlot)
|
||||
currentSlot := s.CurrentSlot()
|
||||
blockEpoch, currentEpoch := slots.ToEpoch(slot), slots.ToEpoch(currentSlot)
|
||||
if !params.WithinDAPeriod(blockEpoch, currentEpoch) {
|
||||
return nil
|
||||
}
|
||||
|
||||
body := block.Body()
|
||||
if body == nil {
|
||||
return errors.New("invalid nil beacon block body")
|
||||
}
|
||||
|
||||
kzgCommitments, err := body.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "blob KZG commitments")
|
||||
}
|
||||
|
||||
// If block has not commitments there is nothing to wait for.
|
||||
if len(kzgCommitments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// All columns to sample need to be available for the block to be considered available.
|
||||
nodeID := s.cfg.P2P.NodeID()
|
||||
|
||||
@@ -756,7 +1057,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
}
|
||||
|
||||
// Log for DA checks that cross over into the next slot; helpful for debugging.
|
||||
nextSlot, err := slots.StartTime(s.genesisTime, block.Slot()+1)
|
||||
nextSlot, err := slots.StartTime(s.genesisTime, slot+1)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to determine slot start time: %w", err)
|
||||
}
|
||||
@@ -771,7 +1072,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": block.Slot(),
|
||||
"slot": slot,
|
||||
"root": fmt.Sprintf("%#x", root),
|
||||
"columnsExpected": helpers.SortedPrettySliceFromMap(peerInfo.CustodyColumns),
|
||||
"columnsWaiting": helpers.SortedPrettySliceFromMap(missing),
|
||||
@@ -817,7 +1118,7 @@ func (s *Service) areDataColumnsAvailable(
|
||||
missingIndices = helpers.SortedPrettySliceFromMap(missing)
|
||||
}
|
||||
|
||||
return errors.Wrapf(ctx.Err(), "data column sidecars slot: %d, BlockRoot: %#x, missing: %v", block.Slot(), root, missingIndices)
|
||||
return errors.Wrapf(ctx.Err(), "data column sidecars slot: %d, BlockRoot: %#x, missing: %v", slot, root, missingIndices)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,37 +1216,20 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
headRoot := s.headRoot()
|
||||
headState := s.headState(ctx)
|
||||
s.headLock.RUnlock()
|
||||
lastRoot, lastState := transition.LastCachedState()
|
||||
if lastState == nil {
|
||||
lastRoot, lastState = headRoot[:], headState
|
||||
}
|
||||
// Before Fulu we need to process the next slot to find out if we are proposing.
|
||||
if lastState.Version() < version.Fulu {
|
||||
// Copy all the field tries in our cached state in the event of late
|
||||
// blocks.
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, headRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
|
||||
var accessRoot [32]byte
|
||||
isFull, err := headState.IsParentBlockFull()
|
||||
gloasFirstSlot, _ := slots.EpochStart(params.BeaconConfig().GloasForkEpoch)
|
||||
if err != nil || !isFull || headState.Slot() <= gloasFirstSlot {
|
||||
accessRoot = headRoot
|
||||
} else {
|
||||
// After Fulu, we can update the caches asynchronously after sending FCU to the engine
|
||||
defer func() {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
lastState.CopyAllTries()
|
||||
if err := transition.UpdateNextSlotCache(ctx, lastRoot, lastState); err != nil {
|
||||
log.WithError(err).Debug("Could not update next slot state cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, currentSlot, headState, headRoot[:]); err != nil {
|
||||
log.WithError(err).Error("Could not update epoch boundary caches")
|
||||
}
|
||||
}()
|
||||
}()
|
||||
accessRoot, err = headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash, using head root as access root")
|
||||
accessRoot = headRoot
|
||||
}
|
||||
}
|
||||
s.refreshCaches(ctx, currentSlot, headRoot, headState, accessRoot)
|
||||
// return early if we already started building a block for the current
|
||||
// head root
|
||||
_, has := s.cfg.PayloadIDCache.PayloadID(s.CurrentSlot()+1, headRoot)
|
||||
@@ -953,12 +1237,27 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
attribute := s.getPayloadAttribute(ctx, headState, s.CurrentSlot()+1, headRoot[:])
|
||||
attribute := s.getPayloadAttribute(ctx, headState, s.CurrentSlot()+1, headRoot[:], accessRoot[:])
|
||||
// return early if we are not proposing next slot
|
||||
if attribute.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
if headState.Version() >= version.Gloas {
|
||||
bh, err := headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash")
|
||||
return
|
||||
}
|
||||
id, err := s.notifyForkchoiceUpdateGloas(ctx, bh, attribute)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
if id != nil {
|
||||
s.cfg.PayloadIDCache.Set(s.CurrentSlot()+1, headRoot, [8]byte(*id))
|
||||
}
|
||||
return
|
||||
}
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
@@ -992,9 +1291,10 @@ func (s *Service) waitForSync() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, blockRoot, parentRoot [fieldparams.RootLength]byte) error {
|
||||
// the caller of this function must hold a write lock in forkchoice store.
|
||||
func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, blockRoot, parentRoot [32]byte, parentHash [32]byte) error {
|
||||
if IsInvalidBlock(err) && InvalidBlockLVH(err) != [32]byte{} {
|
||||
return s.pruneInvalidBlock(ctx, blockRoot, parentRoot, InvalidBlockLVH(err))
|
||||
return s.pruneInvalidBlock(ctx, blockRoot, parentRoot, parentHash, InvalidBlockLVH(err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
mathutil "github.com/OffchainLabs/prysm/v7/math"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
@@ -38,57 +40,67 @@ func (s *Service) CurrentSlot() primitives.Slot {
|
||||
}
|
||||
|
||||
// getFCUArgs returns the arguments to call forkchoice update
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if err := s.getFCUArgsEarlyBlock(cfg, fcuArgs); err != nil {
|
||||
return err
|
||||
func (s *Service) getFCUArgs(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
|
||||
fcuArgs, err := s.getFCUArgsEarlyBlock(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:])
|
||||
return nil
|
||||
fcuArgs.attributes = s.getPayloadAttribute(cfg.ctx, fcuArgs.headState, fcuArgs.proposingSlot, cfg.headRoot[:], cfg.headRoot[:])
|
||||
return fcuArgs, nil
|
||||
}
|
||||
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
fcuArgs.headState = cfg.postState
|
||||
fcuArgs.headBlock = cfg.roblock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: cfg.postState,
|
||||
headBlock: cfg.roblock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
return s.fcuArgsNonCanonicalBlock(cfg, fcuArgs)
|
||||
return s.fcuArgsNonCanonicalBlock(cfg)
|
||||
}
|
||||
|
||||
// logNonCanonicalBlockReceived prints a message informing that the received
|
||||
// block is not the head of the chain. It requires the caller holds a lock on
|
||||
// Forkchoice.
|
||||
func (s *Service) logNonCanonicalBlockReceived(blockRoot [32]byte, headRoot [32]byte) {
|
||||
receivedWeight, err := s.cfg.ForkChoiceStore.Weight(blockRoot)
|
||||
receivedWeight, err := s.cfg.ForkChoiceStore.ConsensusNodeWeight(blockRoot)
|
||||
if err != nil {
|
||||
log.WithField("root", fmt.Sprintf("%#x", blockRoot)).Warn("Could not determine node weight")
|
||||
}
|
||||
headWeight, err := s.cfg.ForkChoiceStore.Weight(headRoot)
|
||||
headWeight, err := s.cfg.ForkChoiceStore.ConsensusNodeWeight(headRoot)
|
||||
if err != nil {
|
||||
log.WithField("root", fmt.Sprintf("%#x", headRoot)).Warn("Could not determine node weight")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
fields := logrus.Fields{
|
||||
"receivedRoot": fmt.Sprintf("%#x", blockRoot),
|
||||
"receivedWeight": receivedWeight,
|
||||
"headRoot": fmt.Sprintf("%#x", headRoot),
|
||||
"headWeight": headWeight,
|
||||
}).Debug("Head block is not the received block")
|
||||
}
|
||||
headEmpty, headFull, err := s.cfg.ForkChoiceStore.PayloadWeights(headRoot)
|
||||
if err == nil {
|
||||
fields["headEmptyWeight"] = headEmpty
|
||||
fields["headFullWeight"] = headFull
|
||||
}
|
||||
log.WithFields(fields).Debug("Head block is not the received block")
|
||||
}
|
||||
|
||||
// fcuArgsNonCanonicalBlock returns the arguments to the FCU call when the
|
||||
// incoming block is non-canonical, that is, based on the head root.
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
headState, headBlock, err := s.getStateAndBlock(cfg.ctx, cfg.headRoot)
|
||||
func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig) (*fcuConfig, error) {
|
||||
headState, headBlock, err := s.getStateAndBlock(cfg.ctx, cfg.headRoot, cfg.headRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
fcuArgs.headState = headState
|
||||
fcuArgs.headBlock = headBlock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
return &fcuConfig{
|
||||
headState: headState,
|
||||
headBlock: headBlock,
|
||||
headRoot: cfg.headRoot,
|
||||
proposingSlot: s.CurrentSlot() + 1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sendStateFeedOnBlock sends an event that a new block has been synced
|
||||
@@ -189,36 +201,62 @@ func reportProcessingTime(startTime time.Time) {
|
||||
onBlockProcessingTime.Observe(float64(time.Since(startTime).Milliseconds()))
|
||||
}
|
||||
|
||||
// getBlockPreState returns the pre state of an incoming block. It uses the parent root of the block
|
||||
// GetPrestateToPropose returns the pre-state for a proposer to base its block on.
|
||||
// It is similar to GetBlockPreState but it lacks unnecessary verifications.
|
||||
func (s *Service) GetPrestateToPropose(ctx context.Context, b consensus_blocks.ROBlock) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.GetPreStateToPropose")
|
||||
defer span.End()
|
||||
|
||||
accessRoot, err := s.getLookupParentRoot(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get lookup parent root")
|
||||
}
|
||||
|
||||
bl := b.Block()
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", bl.Slot())
|
||||
}
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
// GetBlockPreState returns the pre state of an incoming block. It uses the parent root of the block
|
||||
// to retrieve the state in DB. It verifies the pre state's validity and the incoming block
|
||||
// is in the correct time window.
|
||||
func (s *Service) getBlockPreState(ctx context.Context, b interfaces.ReadOnlyBeaconBlock) (state.BeaconState, error) {
|
||||
func (s *Service) GetBlockPreState(ctx context.Context, b consensus_blocks.ROBlock) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.getBlockPreState")
|
||||
defer span.End()
|
||||
|
||||
accessRoot, err := s.getLookupParentRoot(b)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get lookup parent root")
|
||||
}
|
||||
// Verify incoming block has a valid pre state.
|
||||
if err := s.verifyBlkPreState(ctx, b.ParentRoot()); err != nil {
|
||||
if err := s.verifyBlkPreState(ctx, accessRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, b.ParentRoot())
|
||||
bl := b.Block()
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, accessRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", b.Slot())
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", bl.Slot())
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", b.Slot())
|
||||
return nil, errors.Wrapf(err, "nil pre state for slot %d", bl.Slot())
|
||||
}
|
||||
|
||||
// Verify block slot time is not from the future.
|
||||
if err := slots.VerifyTime(s.genesisTime, b.Slot(), params.BeaconConfig().MaximumGossipClockDisparityDuration()); err != nil {
|
||||
if err := slots.VerifyTime(s.genesisTime, bl.Slot(), params.BeaconConfig().MaximumGossipClockDisparityDuration()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify block is later than the finalized epoch slot.
|
||||
if err := s.verifyBlkFinalizedSlot(b); err != nil {
|
||||
if err := s.verifyBlkFinalizedSlot(bl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
@@ -351,6 +389,7 @@ func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, signed inte
|
||||
return err
|
||||
}
|
||||
root := signed.Block().ParentRoot()
|
||||
child := signed
|
||||
// As long as parent node is not in fork choice store, and parent node is in DB.
|
||||
for !s.cfg.ForkChoiceStore.HasNode(root) && s.cfg.BeaconDB.HasBlock(ctx, root) {
|
||||
b, err := s.getBlock(ctx, root)
|
||||
@@ -364,10 +403,33 @@ func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, signed inte
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasPayload := false
|
||||
if roblock.Version() >= version.Gloas {
|
||||
sbid, err := child.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get execution payload bid for block at slot %d", child.Block().Slot())
|
||||
}
|
||||
if sbid == nil || sbid.Message == nil {
|
||||
return fmt.Errorf("missing execution payload bid for block at slot %d", child.Block().Slot())
|
||||
}
|
||||
parentBid, err := b.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not get execution payload bid for block at slot %d", b.Block().Slot())
|
||||
}
|
||||
if parentBid == nil || parentBid.Message == nil {
|
||||
return fmt.Errorf("missing execution payload bid for block at slot %d", b.Block().Slot())
|
||||
}
|
||||
if bytes.Equal(sbid.Message.ParentBlockHash, parentBid.Message.BlockHash) {
|
||||
hasPayload = true
|
||||
}
|
||||
}
|
||||
root = b.Block().ParentRoot()
|
||||
child = b
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: roblock,
|
||||
JustifiedCheckpoint: jCheckpoint,
|
||||
FinalizedCheckpoint: fCheckpoint}
|
||||
FinalizedCheckpoint: fCheckpoint,
|
||||
HasPayload: hasPayload,
|
||||
}
|
||||
pendingNodes = append(pendingNodes, args)
|
||||
}
|
||||
if len(pendingNodes) == 0 {
|
||||
|
||||
@@ -163,7 +163,7 @@ func TestStore_OnBlockBatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
blks = append(blks, rwsb)
|
||||
}
|
||||
err := service.onBlockBatch(ctx, blks, &das.MockAvailabilityStore{})
|
||||
err := service.onBlockBatch(ctx, blks, nil, &das.MockAvailabilityStore{})
|
||||
require.NoError(t, err)
|
||||
jcp := service.CurrentJustifiedCheckpt()
|
||||
jroot := bytesutil.ToBytes32(jcp.Root)
|
||||
@@ -193,7 +193,7 @@ func TestStore_OnBlockBatch_NotifyNewPayload(t *testing.T) {
|
||||
require.NoError(t, service.saveInitSyncBlock(ctx, rwsb.Root(), wsb))
|
||||
blks = append(blks, rwsb)
|
||||
}
|
||||
require.NoError(t, service.onBlockBatch(ctx, blks, &das.MockAvailabilityStore{}))
|
||||
require.NoError(t, service.onBlockBatch(ctx, blks, nil, &das.MockAvailabilityStore{}))
|
||||
}
|
||||
|
||||
func TestCachedPreState_CanGetFromStateSummary(t *testing.T) {
|
||||
@@ -731,13 +731,13 @@ func TestOnBlock_CanFinalize_WithOnTick(t *testing.T) {
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -783,13 +783,13 @@ func TestOnBlock_CanFinalize(t *testing.T) {
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -847,13 +847,13 @@ func TestOnBlock_CallNewPayloadAndForkchoiceUpdated(t *testing.T) {
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1322,13 +1322,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Add(4)
|
||||
var lock sync.Mutex
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb1.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb1, r1)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb1)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb1, r1)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1336,13 +1336,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb2.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb2, r2)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb2)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb2, r2)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1350,13 +1350,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb3.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb3, r3)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb3)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb3, r3)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1364,13 +1364,13 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
preState, err := service.getBlockPreState(ctx, wsb4.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb4, r4)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb4)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb4, r4)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1442,13 +1442,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1464,13 +1464,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1487,13 +1487,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1512,13 +1512,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
firstInvalidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, firstInvalidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1549,12 +1549,12 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
// Check that forkchoice's head and store's headroot are the previous head (since the invalid block did
|
||||
@@ -1578,13 +1578,13 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1647,13 +1647,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1670,13 +1670,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1692,13 +1692,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
lastValidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1723,13 +1723,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
invalidRoots[i-13], err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, invalidRoots[i-13])
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, invalidRoots[i-13], wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, invalidRoots[i-13])
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1753,12 +1753,12 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
@@ -1793,13 +1793,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1820,13 +1820,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1850,13 +1850,13 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1912,13 +1912,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1934,13 +1934,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1956,13 +1956,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
lastValidRoot, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -1989,13 +1989,13 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
// Save current justified and finalized epochs for future use.
|
||||
currStoreJustifiedEpoch := service.CurrentJustifiedCheckpt().Epoch
|
||||
currStoreFinalizedEpoch := service.FinalizedCheckpt().Epoch
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2006,6 +2006,7 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
// Check that we have justified the second epoch
|
||||
jc := service.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
require.Equal(t, primitives.Epoch(2), jc.Epoch)
|
||||
time.Sleep(20 * time.Millisecond) // wait for async forkchoice update to be processed
|
||||
|
||||
// import block 19 to find out that the whole chain 13--18 was in fact
|
||||
// invalid
|
||||
@@ -2020,12 +2021,12 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, rowsb)
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
@@ -2072,7 +2073,7 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
rwsb, err := consensusblocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
// We use onBlockBatch here because the valid chain is missing in forkchoice
|
||||
require.NoError(t, service.onBlockBatch(ctx, []consensusblocks.ROBlock{rwsb}, &das.MockAvailabilityStore{}))
|
||||
require.NoError(t, service.onBlockBatch(ctx, []consensusblocks.ROBlock{rwsb}, nil, &das.MockAvailabilityStore{}))
|
||||
// Check that the head is now VALID and the node is not optimistic
|
||||
require.Equal(t, genesisRoot, service.ensureRootNotZeros(service.cfg.ForkChoiceStore.CachedHeadRoot()))
|
||||
headRoot, err = service.HeadRoot(ctx)
|
||||
@@ -2112,13 +2113,13 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2180,13 +2181,13 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2416,14 +2417,12 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
isValidPayload: true,
|
||||
}
|
||||
// error branch
|
||||
fcuArgs := &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
_, err = s.getFCUArgs(cfg)
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
// canonical branch
|
||||
cfg.headRoot = cfg.roblock.Root()
|
||||
fcuArgs = &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
fcuArgs, err := s.getFCUArgs(cfg)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot)
|
||||
}
|
||||
@@ -2455,7 +2454,9 @@ func TestRollbackBlock(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2468,7 +2469,7 @@ func TestRollbackBlock(t *testing.T) {
|
||||
|
||||
// Set invalid parent root to trigger forkchoice error.
|
||||
wsb.SetParentRoot([]byte("bad"))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Rollback block insertion into db and caches.
|
||||
@@ -2513,7 +2514,9 @@ func TestRollbackBlock_SavePostStateInfo_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2569,13 +2572,13 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -2586,7 +2589,9 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err = b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err = service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err = service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
@@ -2600,8 +2605,6 @@ func TestRollbackBlock_ContextDeadline(t *testing.T) {
|
||||
// Set deadlined context when processing the block
|
||||
cancCtx, canc := context.WithCancel(t.Context())
|
||||
canc()
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
|
||||
parentRoot = roblock.Block().ParentRoot()
|
||||
|
||||
@@ -3477,3 +3480,219 @@ func TestProcessLightClientFinalityUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBlockPayloadAttestations(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
t.Run("pre-Gloas block is no-op", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
blk := util.NewBeaconBlockElectra()
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
st, err := util.NewBeaconStateElectra()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(t.Context(), wsb.Block(), st))
|
||||
})
|
||||
|
||||
t.Run("empty payload attestations", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
blk := util.NewBeaconBlockGloas()
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
st, err := util.NewBeaconStateGloas()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(t.Context(), wsb.Block(), st))
|
||||
})
|
||||
|
||||
t.Run("unknown root is skipped", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
unknownRoot := bytesutil.ToBytes32([]byte("unknown"))
|
||||
bits := bitfield.NewBitvector512()
|
||||
bits.SetBitAt(0, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: unknownRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
|
||||
t.Run("known root sets PTC votes", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
base, insertBlk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, insertBlk, blockRoot)
|
||||
|
||||
ptc, err := headState.PayloadCommitteeReadOnly(1)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(ptc))
|
||||
|
||||
bits := bitfield.NewBitvector512()
|
||||
bits.SetBitAt(0, true)
|
||||
bits.SetBitAt(2, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
|
||||
t.Run("multiple attestations", func(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 2, numVals)
|
||||
|
||||
base, insertBlk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, insertBlk, blockRoot)
|
||||
|
||||
bits1 := bitfield.NewBitvector512()
|
||||
bits1.SetBitAt(0, true)
|
||||
bits2 := bitfield.NewBitvector512()
|
||||
bits2.SetBitAt(1, true)
|
||||
blk := util.HydrateSignedBeaconBlockGloas(ðpb.SignedBeaconBlockGloas{
|
||||
Block: ðpb.BeaconBlockGloas{
|
||||
Slot: 2,
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
PayloadAttestations: []*ethpb.PayloadAttestation{
|
||||
{
|
||||
AggregationBits: bits1,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: false,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
{
|
||||
AggregationBits: bits2,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: false,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.handleBlockPayloadAttestations(ctx, wsb.Block(), headState))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateCachesAndEpochBoundary_MatchingRoots(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
|
||||
service.updateCachesAndEpochBoundary(t.Context(), 1, st, accessRoot, accessRoot[:], st)
|
||||
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
func TestUpdateCachesAndEpochBoundary_DifferentRoots(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
headState, _ := util.DeterministicGenesisState(t, 1)
|
||||
lastState, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
lastRoot := [32]byte{'b'}
|
||||
|
||||
service.updateCachesAndEpochBoundary(t.Context(), 1, headState, accessRoot, lastRoot[:], lastState)
|
||||
|
||||
// Cache should be keyed by accessRoot, not lastRoot.
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
|
||||
cached = transition.NextSlotState(lastRoot[:], 1)
|
||||
require.Equal(t, true, cached == nil)
|
||||
}
|
||||
|
||||
func TestRefreshCaches_NoCachedState(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
headRoot := [32]byte{'h'}
|
||||
|
||||
service.refreshCaches(t.Context(), 1, headRoot, st, headRoot)
|
||||
|
||||
cached := transition.NextSlotState(headRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
func TestRefreshCaches_CachedStateMatchesAccessRoot(t *testing.T) {
|
||||
service := testServiceNoDB(t)
|
||||
st, _ := util.DeterministicGenesisState(t, 1)
|
||||
accessRoot := [32]byte{'a'}
|
||||
headRoot := [32]byte{'h'}
|
||||
|
||||
// Pre-populate the cache with accessRoot.
|
||||
require.NoError(t, transition.UpdateNextSlotCache(t.Context(), accessRoot[:], st))
|
||||
|
||||
service.refreshCaches(t.Context(), 1, headRoot, st, accessRoot)
|
||||
|
||||
cached := transition.NextSlotState(accessRoot[:], 1)
|
||||
require.NotNil(t, cached)
|
||||
require.Equal(t, primitives.Slot(1), cached.Slot())
|
||||
}
|
||||
|
||||
@@ -134,38 +134,64 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
|
||||
|
||||
start = time.Now()
|
||||
// return early if we haven't changed head
|
||||
newHeadRoot, err := s.cfg.ForkChoiceStore.Head(ctx)
|
||||
newHeadRoot, newHeadBlockHash, full, err := s.cfg.ForkChoiceStore.FullHead(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not compute head from new attestations")
|
||||
return
|
||||
}
|
||||
if !s.isNewHead(newHeadRoot) {
|
||||
if !s.isNewHead(newHeadRoot, full) {
|
||||
return
|
||||
}
|
||||
log.WithField("newHeadRoot", fmt.Sprintf("%#x", newHeadRoot)).Debug("Head changed due to attestations")
|
||||
headState, headBlock, err := s.getStateAndBlock(ctx, newHeadRoot)
|
||||
var accessRoot [32]byte
|
||||
postGloas := slots.ToEpoch(proposingSlot) >= params.BeaconConfig().GloasForkEpoch
|
||||
if full && postGloas {
|
||||
accessRoot = newHeadBlockHash
|
||||
} else {
|
||||
accessRoot = newHeadRoot
|
||||
}
|
||||
headState, headBlock, err := s.getStateAndBlock(ctx, newHeadRoot, accessRoot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get head block")
|
||||
log.WithError(err).Error("Could not get head block and state")
|
||||
return
|
||||
}
|
||||
newAttHeadElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: newHeadRoot,
|
||||
headBlock: headBlock,
|
||||
proposingSlot: proposingSlot,
|
||||
}
|
||||
if s.inRegularSync() {
|
||||
fcuArgs.attributes = s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:])
|
||||
if fcuArgs.attributes != nil && s.shouldOverrideFCU(newHeadRoot, proposingSlot) {
|
||||
attr := s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:], accessRoot[:])
|
||||
if attr != nil && s.shouldOverrideFCU(newHeadRoot, proposingSlot) {
|
||||
return
|
||||
}
|
||||
go s.forkchoiceUpdateWithExecution(s.ctx, fcuArgs)
|
||||
if postGloas {
|
||||
go func() {
|
||||
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, newHeadBlockHash, attr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not update forkchoice with engine")
|
||||
}
|
||||
if pid == nil {
|
||||
if attr != nil {
|
||||
log.Warn("Engine did not return a payload ID for the fork choice update with attributes")
|
||||
}
|
||||
return
|
||||
}
|
||||
var pId [8]byte
|
||||
copy(pId[:], pid[:])
|
||||
s.cfg.PayloadIDCache.Set(proposingSlot, newHeadRoot, pId)
|
||||
}()
|
||||
} else {
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: newHeadRoot,
|
||||
headBlock: headBlock,
|
||||
proposingSlot: proposingSlot,
|
||||
attributes: attr,
|
||||
}
|
||||
go s.forkchoiceUpdateWithExecution(s.ctx, fcuArgs)
|
||||
}
|
||||
}
|
||||
if err := s.saveHead(s.ctx, fcuArgs.headRoot, fcuArgs.headBlock, fcuArgs.headState); err != nil {
|
||||
if err := s.saveHead(s.ctx, newHeadRoot, headBlock, headState); err != nil {
|
||||
log.WithError(err).Error("Could not save head")
|
||||
}
|
||||
s.pruneAttsFromPool(s.ctx, fcuArgs.headState, fcuArgs.headBlock)
|
||||
s.pruneAttsFromPool(s.ctx, headState, headBlock)
|
||||
}
|
||||
|
||||
// This processes fork choice attestations from the pool to account for validator votes and fork choice.
|
||||
|
||||
@@ -110,13 +110,13 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
@@ -172,13 +172,13 @@ func TestService_UpdateHead_NoAtts(t *testing.T) {
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
roblock, err := blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
service.cfg.ForkChoiceStore.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
service.cfg.ForkChoiceStore.Unlock()
|
||||
|
||||
@@ -41,10 +41,12 @@ var epochsSinceFinalityExpandCache = primitives.Epoch(4)
|
||||
// BlockReceiver interface defines the methods of chain service for receiving and processing new blocks.
|
||||
type BlockReceiver interface {
|
||||
ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte, avs das.AvailabilityChecker) error
|
||||
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, avs das.AvailabilityChecker) error
|
||||
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, envelopes []interfaces.ROSignedExecutionPayloadEnvelope, avs das.AvailabilityChecker) error
|
||||
HasBlock(ctx context.Context, root [32]byte) bool
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
BlockBeingSynced([32]byte) bool
|
||||
GetBlockPreState(ctx context.Context, b blocks.ROBlock) (state.BeaconState, error)
|
||||
GetPrestateToPropose(ctx context.Context, b blocks.ROBlock) (state.BeaconState, error)
|
||||
}
|
||||
|
||||
// BlobReceiver interface defines the methods of chain service for receiving new
|
||||
@@ -95,18 +97,17 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "block copy")
|
||||
}
|
||||
|
||||
preState, err := s.getBlockPreState(ctx, blockCopy.Block())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get block's prestate")
|
||||
}
|
||||
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
roblock, err := blocks.NewROBlockWithRoot(blockCopy, blockRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "new ro block with root")
|
||||
}
|
||||
|
||||
preState, err := s.GetBlockPreState(ctx, roblock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get block's prestate")
|
||||
}
|
||||
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
postState, isValidPayload, err := s.validateExecutionAndConsensus(ctx, preState, roblock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "validator execution and consensus")
|
||||
@@ -152,7 +153,7 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
|
||||
// Have we been finalizing? Should we start saving hot states to db?
|
||||
if err := s.checkSaveHotStateDB(ctx); err != nil {
|
||||
return errors.Wrap(err, "check save hot state db")
|
||||
log.WithError(err).Error("Could not check save hot state DB")
|
||||
}
|
||||
|
||||
// We apply the same heuristic to some of our more important caches.
|
||||
@@ -211,6 +212,16 @@ func (s *Service) validateExecutionAndConsensus(
|
||||
preState state.BeaconState,
|
||||
block blocks.ROBlock,
|
||||
) (state.BeaconState, bool, error) {
|
||||
if block.Version() >= version.Gloas {
|
||||
postState, err := s.validateStateTransition(ctx, preState, block)
|
||||
if errors.Is(err, ErrNotDescendantOfFinalized) {
|
||||
return nil, false, invalidBlock{error: err, root: block.Root()}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, false, errors.Wrap(err, "failed to validate consensus state transition function")
|
||||
}
|
||||
return postState, false, nil
|
||||
}
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
@@ -244,6 +255,10 @@ func (s *Service) validateExecutionAndConsensus(
|
||||
}
|
||||
|
||||
func (s *Service) handleDA(ctx context.Context, avs das.AvailabilityChecker, block blocks.ROBlock) (time.Duration, error) {
|
||||
// Gloas DA is handled on the payload enevelope.
|
||||
if block.Version() >= version.Gloas {
|
||||
return 0, nil
|
||||
}
|
||||
var err error
|
||||
start := time.Now()
|
||||
if avs != nil {
|
||||
@@ -351,12 +366,14 @@ func (s *Service) executePostFinalizationTasks(ctx context.Context, finalizedSta
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go s.checkpointStateCache.EvictUpTo(finalized.Epoch)
|
||||
}
|
||||
|
||||
// ReceiveBlockBatch processes the whole block batch at once, assuming the block batch is linear ,transitioning
|
||||
// the state, performing batch verification of all collected signatures and then performing the appropriate
|
||||
// actions for a block post-transition.
|
||||
func (s *Service) ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, avs das.AvailabilityChecker) error {
|
||||
func (s *Service) ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, envelopes []interfaces.ROSignedExecutionPayloadEnvelope, avs das.AvailabilityChecker) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.ReceiveBlockBatch")
|
||||
defer span.End()
|
||||
|
||||
@@ -364,7 +381,7 @@ func (s *Service) ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
|
||||
// Apply state transition on the incoming newly received block batches, one by one.
|
||||
if err := s.onBlockBatch(ctx, blocks, avs); err != nil {
|
||||
if err := s.onBlockBatch(ctx, blocks, envelopes, avs); err != nil {
|
||||
err := errors.Wrap(err, "could not process block in batch")
|
||||
tracing.AnnotateError(span, err)
|
||||
return err
|
||||
@@ -404,6 +421,15 @@ func (s *Service) ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock
|
||||
if err := s.cfg.BeaconDB.SaveBlocks(ctx, s.getInitSyncBlocks()); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range envelopes {
|
||||
protoEnv, ok := e.Proto().(*ethpb.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return errors.New("could not type assert signed envelope to proto")
|
||||
}
|
||||
if err := s.cfg.BeaconDB.SaveExecutionPayloadEnvelope(ctx, protoEnv); err != nil {
|
||||
return errors.Wrap(err, "could not save execution payload envelope")
|
||||
}
|
||||
}
|
||||
finalized := s.cfg.ForkChoiceStore.FinalizedCheckpoint()
|
||||
if finalized == nil {
|
||||
return errNilFinalizedInStore
|
||||
@@ -633,7 +659,7 @@ func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header
|
||||
isValidPayload, err := s.notifyNewPayload(ctx, ver, header, block)
|
||||
if err != nil {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot())
|
||||
err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot(), [32]byte(header.BlockHash()))
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ func TestService_ReceiveBlockBatch(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
rwsb, err := blocks.NewROBlock(wsb)
|
||||
require.NoError(t, err)
|
||||
err = s.ReceiveBlockBatch(ctx, []blocks.ROBlock{rwsb}, &das.MockAvailabilityStore{})
|
||||
err = s.ReceiveBlockBatch(ctx, []blocks.ROBlock{rwsb}, nil, &das.MockAvailabilityStore{})
|
||||
if tt.wantedErr != "" {
|
||||
assert.ErrorContains(t, tt.wantedErr, err)
|
||||
} else {
|
||||
|
||||
356
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
356
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
@@ -0,0 +1,356 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed"
|
||||
statefeed "github.com/OffchainLabs/prysm/v7/beacon-chain/core/feed/state"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/execution"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
payloadattribute "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attribute"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ExecutionPayloadEnvelopeReceiver defines the methods for receiving execution payload envelopes.
|
||||
type ExecutionPayloadEnvelopeReceiver interface {
|
||||
ReceiveExecutionPayloadEnvelope(context.Context, interfaces.ROSignedExecutionPayloadEnvelope) error
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope processes a signed execution payload envelope for the Gloas fork.
|
||||
func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope) (err error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.ReceiveExecutionPayloadEnvelope")
|
||||
defer span.End()
|
||||
start := time.Now()
|
||||
defer func() {
|
||||
beaconExecutionPayloadEnvelopeProcessingDurationSeconds.Observe(time.Since(start).Seconds())
|
||||
if err != nil {
|
||||
beaconExecutionPayloadEnvelopeInvalidTotal.Inc()
|
||||
return
|
||||
}
|
||||
beaconExecutionPayloadEnvelopeValidTotal.Inc()
|
||||
}()
|
||||
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope")
|
||||
}
|
||||
root := envelope.BeaconBlockRoot()
|
||||
|
||||
err = s.payloadBeingSynced.set(root)
|
||||
if errors.Is(err, errBlockBeingSynced) {
|
||||
log.WithField("blockRoot", fmt.Sprintf("%#x", root)).Debug("Ignoring payload envelope currently being synced")
|
||||
return nil
|
||||
}
|
||||
defer s.payloadBeingSynced.unset(root)
|
||||
|
||||
preState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var isValidPayload bool
|
||||
g, gCtx := errgroup.WithContext(ctx)
|
||||
|
||||
g.Go(func() error {
|
||||
return gloas.ProcessExecutionPayload(gCtx, preState, signed)
|
||||
})
|
||||
|
||||
g.Go(func() error {
|
||||
var elErr error
|
||||
isValidPayload, elErr = s.validateExecutionOnEnvelope(gCtx, preState, envelope)
|
||||
return elErr
|
||||
})
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// DA check: verify data columns are available before inserting payload.
|
||||
bid, err := preState.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get latest execution payload bid")
|
||||
}
|
||||
if len(bid.BlobKzgCommitments()) > 0 {
|
||||
if err := s.areDataColumnsAvailable(ctx, root, envelope.Slot()); err != nil {
|
||||
return errors.Wrap(err, "data availability check failed for payload envelope")
|
||||
}
|
||||
}
|
||||
if err := s.savePostPayload(ctx, signed, preState); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.InsertPayload(envelope); err != nil {
|
||||
return errors.Wrap(err, "could not insert payload into forkchoice")
|
||||
}
|
||||
|
||||
if isValidPayload {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
if err := s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, root); err != nil {
|
||||
log.WithError(err).Error("Could not set optimistic to valid")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
}
|
||||
|
||||
headRoot, err := s.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get head root")
|
||||
return nil
|
||||
}
|
||||
if err := s.postPayloadHeadUpdate(ctx, envelope, preState, root, headRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.PayloadProcessed,
|
||||
Data: &statefeed.PayloadProcessedData{
|
||||
Slot: envelope.Slot(),
|
||||
BlockRoot: root,
|
||||
},
|
||||
})
|
||||
|
||||
execution, err := envelope.Execution()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get execution payload from envelope for logging")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": envelope.Slot(),
|
||||
"blockRoot": fmt.Sprintf("%#x", bytesutil.Trunc(root[:])),
|
||||
"blockHash": fmt.Sprintf("%#x", bytesutil.Trunc(execution.BlockHash())),
|
||||
"parentHash": fmt.Sprintf("%#x", bytesutil.Trunc(execution.ParentHash())),
|
||||
}).Info("Processed execution payload envelope")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) postPayloadHeadUpdate(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, st state.BeaconState, root [32]byte, headRoot []byte) error {
|
||||
if !bytes.Equal(headRoot, root[:]) {
|
||||
return nil
|
||||
}
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
blockHash := bytesutil.ToBytes32(payload.BlockHash())
|
||||
|
||||
s.headLock.Lock()
|
||||
s.head.state = st
|
||||
s.head.full = true
|
||||
s.headLock.Unlock()
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(s.ctx, slotDeadline)
|
||||
defer cancel()
|
||||
if err := transition.UpdateNextSlotCache(ctx, blockHash[:], st); err != nil {
|
||||
log.WithError(err).Error("Could not update next slot cache")
|
||||
}
|
||||
if err := s.handleEpochBoundary(ctx, envelope.Slot(), st, blockHash[:]); err != nil {
|
||||
log.WithError(err).Error("Could not handle epoch boundary")
|
||||
}
|
||||
}()
|
||||
|
||||
attr := s.getPayloadAttribute(ctx, st, envelope.Slot()+1, headRoot, blockHash[:])
|
||||
if s.inRegularSync() {
|
||||
go func() {
|
||||
pid, err := s.notifyForkchoiceUpdateGloas(s.ctx, blockHash, attr)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not notify forkchoice update")
|
||||
return
|
||||
}
|
||||
if attr != nil && !attr.IsEmpty() && pid != nil {
|
||||
var pId [8]byte
|
||||
copy(pId[:], pid[:])
|
||||
s.cfg.PayloadIDCache.Set(envelope.Slot()+1, root, pId)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) getPayloadEnvelopePrestate(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope) (state.BeaconState, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.getPayloadEnvelopePrestate")
|
||||
defer span.End()
|
||||
|
||||
root := envelope.BeaconBlockRoot()
|
||||
if !s.InForkchoice(root) {
|
||||
return nil, fmt.Errorf("beacon block root %#x not found in forkchoice", root)
|
||||
}
|
||||
if err := s.verifyBlkPreState(ctx, root); err != nil {
|
||||
return nil, errors.Wrap(err, "could not verify pre-state")
|
||||
}
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, root)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get pre-state by root")
|
||||
}
|
||||
if preState == nil || preState.IsNil() {
|
||||
return nil, fmt.Errorf("nil pre-state for beacon block root %#x", root)
|
||||
}
|
||||
return preState, nil
|
||||
}
|
||||
|
||||
func (s *Service) callNewPayload(
|
||||
ctx context.Context,
|
||||
payload interfaces.ExecutionData,
|
||||
versionedHashes []common.Hash,
|
||||
parentRoot common.Hash,
|
||||
requests *enginev1.ExecutionRequests,
|
||||
slot primitives.Slot,
|
||||
) (bool, error) {
|
||||
_, err := s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &parentRoot, requests, slot)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus) {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": slot,
|
||||
"payloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(payload.BlockHash())),
|
||||
}).Info("Called new payload with optimistic envelope")
|
||||
return false, nil
|
||||
}
|
||||
if errors.Is(err, execution.ErrInvalidPayloadStatus) {
|
||||
return false, invalidBlock{error: ErrInvalidPayload}
|
||||
}
|
||||
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
|
||||
}
|
||||
|
||||
func (s *Service) notifyNewEnvelopeFromBlock(ctx context.Context, b blocks.ROBlock, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyNewEnvelopeFromBlock")
|
||||
defer span.End()
|
||||
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
sbid, err := b.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get signed execution payload bid from block")
|
||||
}
|
||||
versionedHashes := make([]common.Hash, len(sbid.Message.BlobKzgCommitments))
|
||||
for i, c := range sbid.Message.BlobKzgCommitments {
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(c)
|
||||
}
|
||||
return s.callNewPayload(ctx, payload, versionedHashes, common.Hash(b.Block().ParentRoot()), envelope.ExecutionRequests(), envelope.Slot())
|
||||
}
|
||||
|
||||
// The returned boolean indicates whether the payload was valid or if it was accepted as syncing (optimistic).
|
||||
func (s *Service) notifyNewEnvelope(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyNewEnvelope")
|
||||
defer span.End()
|
||||
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
latestBid, err := st.LatestExecutionPayloadBid()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "could not get latest execution payload bid")
|
||||
}
|
||||
commitments := latestBid.BlobKzgCommitments()
|
||||
versionedHashes := make([]common.Hash, len(commitments))
|
||||
for i, c := range commitments {
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(c)
|
||||
}
|
||||
return s.callNewPayload(ctx, payload, versionedHashes, common.Hash(bytesutil.ToBytes32(st.LatestBlockHeader().ParentRoot)), envelope.ExecutionRequests(), envelope.Slot())
|
||||
}
|
||||
|
||||
func (s *Service) validateExecutionOnEnvelope(ctx context.Context, st state.BeaconState, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
|
||||
isValid, err := s.notifyNewEnvelope(ctx, st, envelope)
|
||||
if err == nil {
|
||||
return isValid, nil
|
||||
}
|
||||
|
||||
blockRoot := envelope.BeaconBlockRoot()
|
||||
parentRoot := bytesutil.ToBytes32(st.LatestBlockHeader().ParentRoot)
|
||||
payload, payloadErr := envelope.Execution()
|
||||
if payloadErr != nil {
|
||||
return false, errors.Wrap(payloadErr, "could not get execution payload from envelope")
|
||||
}
|
||||
parentHash := bytesutil.ToBytes32(payload.ParentHash())
|
||||
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, s.handleInvalidExecutionError(ctx, err, blockRoot, parentRoot, parentHash)
|
||||
}
|
||||
|
||||
func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope, st state.BeaconState) error {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.savePostPayload")
|
||||
defer span.End()
|
||||
|
||||
protoEnv, ok := signed.Proto().(*ethpb.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return errors.New("could not type assert signed envelope to proto")
|
||||
}
|
||||
if err := s.cfg.BeaconDB.SaveExecutionPayloadEnvelope(ctx, protoEnv); err != nil {
|
||||
return errors.Wrap(err, "could not save execution payload envelope")
|
||||
}
|
||||
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get envelope")
|
||||
}
|
||||
payload, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get execution payload from envelope")
|
||||
}
|
||||
return s.cfg.StateGen.SaveState(ctx, bytesutil.ToBytes32(payload.BlockHash()), st)
|
||||
}
|
||||
|
||||
// notifyForkchoiceUpdateGloas takes the block hash directly because Gloas
|
||||
// blocks don't carry an execution payload in the body.
|
||||
func (s *Service) notifyForkchoiceUpdateGloas(ctx context.Context, blockHash [32]byte, attributes payloadattribute.Attributer) (*enginev1.PayloadIDBytes, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyForkchoiceUpdateGloas")
|
||||
defer span.End()
|
||||
|
||||
s.cfg.ForkChoiceStore.RLock()
|
||||
finalizedHash := s.cfg.ForkChoiceStore.FinalizedPayloadBlockHash()
|
||||
justifiedHash := s.cfg.ForkChoiceStore.UnrealizedJustifiedPayloadBlockHash()
|
||||
s.cfg.ForkChoiceStore.RUnlock()
|
||||
fcs := &enginev1.ForkchoiceState{
|
||||
HeadBlockHash: blockHash[:],
|
||||
SafeBlockHash: justifiedHash[:],
|
||||
FinalizedBlockHash: finalizedHash[:],
|
||||
}
|
||||
if attributes == nil {
|
||||
attributes = payloadattribute.EmptyWithVersion(version.Gloas)
|
||||
}
|
||||
|
||||
payloadID, lastValidHash, err := s.cfg.ExecutionEngineCaller.ForkchoiceUpdated(ctx, fcs, attributes)
|
||||
if err == nil {
|
||||
return payloadID, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
|
||||
log.WithFields(logrus.Fields{
|
||||
"headBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(blockHash[:])),
|
||||
"finalizedPayloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(finalizedHash[:])),
|
||||
}).Info("Called forkchoice updated with optimistic block (Gloas)")
|
||||
return payloadID, nil
|
||||
case errors.Is(err, execution.ErrInvalidPayloadStatus):
|
||||
if len(lastValidHash) == 0 {
|
||||
lastValidHash = defaultLatestValidHash
|
||||
}
|
||||
return nil, invalidBlock{
|
||||
error: ErrInvalidPayload,
|
||||
lastValidHash: bytesutil.ToBytes32(lastValidHash),
|
||||
}
|
||||
default:
|
||||
log.WithError(err).Error(ErrUndefinedExecutionEngineError)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
// PayloadAttestationReceiver interface defines the methods of chain service for receiving
|
||||
// validated payload attestation messages.
|
||||
type PayloadAttestationReceiver interface {
|
||||
ReceivePayloadAttestationMessage(context.Context, *ethpb.PayloadAttestationMessage) error
|
||||
}
|
||||
|
||||
// ReceivePayloadAttestationMessage accepts a payload attestation message and updates the
|
||||
// forkchoice PTC vote bitvectors for the referenced beacon block.
|
||||
func (s *Service) ReceivePayloadAttestationMessage(ctx context.Context, a *ethpb.PayloadAttestationMessage) error {
|
||||
if a == nil || a.Data == nil {
|
||||
return errors.New("nil payload attestation message")
|
||||
}
|
||||
root := bytesutil.ToBytes32(a.Data.BeaconBlockRoot)
|
||||
|
||||
st, err := s.HeadStateReadOnly(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx, err := gloas.PayloadCommitteeIndex(ctx, st, a.Data.Slot, a.ValidatorIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
s.cfg.ForkChoiceStore.SetPTCVote(root, idx, a.Data.PayloadPresent, a.Data.BlobDataAvailable)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
mockExecution "github.com/OffchainLabs/prysm/v7/beacon-chain/execution/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
)
|
||||
|
||||
func TestReceivePayloadAttestationMessage_NilMessage(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
err := s.ReceivePayloadAttestationMessage(t.Context(), nil)
|
||||
require.ErrorContains(t, "nil payload attestation message", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_NilData(t *testing.T) {
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
msg := ðpb.PayloadAttestationMessage{}
|
||||
err := s.ReceivePayloadAttestationMessage(t.Context(), msg)
|
||||
require.ErrorContains(t, "nil payload attestation message", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_ValidatorNotInPTC(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
numVals := 2048
|
||||
headState := gloasStateWithValidators(t, 1, numVals)
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
s.head = &head{root: blockRoot, block: wsb, state: headState, slot: 1}
|
||||
|
||||
ptc, err := headState.PayloadCommitteeReadOnly(1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Pick a validator index not in the PTC.
|
||||
inPTC := make(map[primitives.ValidatorIndex]bool)
|
||||
for _, idx := range ptc {
|
||||
inPTC[idx] = true
|
||||
}
|
||||
var notInPTC primitives.ValidatorIndex
|
||||
for i := primitives.ValidatorIndex(0); int(i) < numVals; i++ {
|
||||
if !inPTC[i] {
|
||||
notInPTC = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
msg := ðpb.PayloadAttestationMessage{
|
||||
ValidatorIndex: notInPTC,
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
},
|
||||
}
|
||||
err = s.ReceivePayloadAttestationMessage(ctx, msg)
|
||||
require.ErrorContains(t, "validator not in PTC", err)
|
||||
}
|
||||
|
||||
func TestReceivePayloadAttestationMessage_OK(t *testing.T) {
|
||||
params.SetupTestConfigCleanup(t)
|
||||
cfg := params.BeaconConfig()
|
||||
cfg.GloasForkEpoch = 0
|
||||
params.OverrideBeaconConfig(cfg)
|
||||
|
||||
s, _ := setupGloasService(t, &mockExecution.EngineClient{})
|
||||
ctx := t.Context()
|
||||
|
||||
blockRoot := bytesutil.ToBytes32([]byte("root1"))
|
||||
parentRoot := params.BeaconConfig().ZeroHash
|
||||
blockHash := bytesutil.ToBytes32([]byte("hash1"))
|
||||
|
||||
headState := gloasStateWithValidators(t, 1, 2048)
|
||||
|
||||
base, blk := testGloasState(t, 1, parentRoot, blockHash)
|
||||
insertGloasBlock(t, s, base, blk, blockRoot)
|
||||
|
||||
wsb, err := blocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
s.head = &head{root: blockRoot, block: wsb, state: headState, slot: 1}
|
||||
|
||||
ptc, err := headState.PayloadCommitteeReadOnly(1)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, 0, len(ptc))
|
||||
|
||||
msg := ðpb.PayloadAttestationMessage{
|
||||
ValidatorIndex: ptc[0],
|
||||
Data: ðpb.PayloadAttestationData{
|
||||
BeaconBlockRoot: blockRoot[:],
|
||||
Slot: 1,
|
||||
PayloadPresent: true,
|
||||
BlobDataAvailable: true,
|
||||
},
|
||||
}
|
||||
require.NoError(t, s.ReceivePayloadAttestationMessage(ctx, msg))
|
||||
}
|
||||
|
||||
// gloasStateWithValidators returns a Gloas beacon state with active validators
|
||||
// for PTC committee computation.
|
||||
func gloasStateWithValidators(t *testing.T, slot primitives.Slot, numVals int) state.BeaconState {
|
||||
t.Helper()
|
||||
validators := make([]*ethpb.Validator, numVals)
|
||||
balances := make([]uint64, numVals)
|
||||
for i := range validators {
|
||||
validators[i] = ðpb.Validator{
|
||||
PublicKey: make([]byte, 48),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra,
|
||||
ActivationEpoch: 0,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
balances[i] = params.BeaconConfig().MaxEffectiveBalanceElectra
|
||||
}
|
||||
st, err := util.NewBeaconStateGloas(func(s *ethpb.BeaconStateGloas) error {
|
||||
s.Slot = slot
|
||||
s.Validators = validators
|
||||
s.Balances = balances
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return st
|
||||
}
|
||||
@@ -62,6 +62,7 @@ type Service struct {
|
||||
syncComplete chan struct{}
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
payloadBeingSynced *currentlySyncingBlock
|
||||
blobStorage *filesystem.BlobStorage
|
||||
dataColumnStorage *filesystem.DataColumnStorage
|
||||
slasherEnabled bool
|
||||
@@ -72,29 +73,30 @@ type Service struct {
|
||||
|
||||
// config options for the service.
|
||||
type config struct {
|
||||
BeaconBlockBuf int
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
DepositCache cache.DepositCache
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingPool slashings.PoolManager
|
||||
BLSToExecPool blstoexec.PoolManager
|
||||
P2P p2p.Accessor
|
||||
MaxRoutines int
|
||||
StateNotifier statefeed.Notifier
|
||||
ForkChoiceStore f.ForkChoicer
|
||||
AttService *attestations.Service
|
||||
StateGen *stategen.State
|
||||
SlasherAttestationsFeed *event.Feed
|
||||
WeakSubjectivityCheckpt *ethpb.Checkpoint
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
FinalizedStateAtStartUp state.BeaconState
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
SyncChecker Checker
|
||||
BeaconBlockBuf int
|
||||
ChainStartFetcher execution.ChainStartFetcher
|
||||
BeaconDB db.HeadAccessDatabase
|
||||
DepositCache cache.DepositCache
|
||||
PayloadIDCache *cache.PayloadIDCache
|
||||
TrackedValidatorsCache *cache.TrackedValidatorsCache
|
||||
ProposerPreferencesCache *cache.ProposerPreferencesCache
|
||||
AttestationCache *cache.AttestationCache
|
||||
AttPool attestations.Pool
|
||||
ExitPool voluntaryexits.PoolManager
|
||||
SlashingPool slashings.PoolManager
|
||||
BLSToExecPool blstoexec.PoolManager
|
||||
P2P p2p.Accessor
|
||||
MaxRoutines int
|
||||
StateNotifier statefeed.Notifier
|
||||
ForkChoiceStore f.ForkChoicer
|
||||
AttService *attestations.Service
|
||||
StateGen *stategen.State
|
||||
SlasherAttestationsFeed *event.Feed
|
||||
WeakSubjectivityCheckpt *ethpb.Checkpoint
|
||||
BlockFetcher execution.POWBlockFetcher
|
||||
FinalizedStateAtStartUp state.BeaconState
|
||||
ExecutionEngineCaller execution.EngineCaller
|
||||
SyncChecker Checker
|
||||
}
|
||||
|
||||
// Checker is an interface used to determine if a node is in initial sync
|
||||
@@ -186,6 +188,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
blobNotifiers: bn,
|
||||
cfg: &config{},
|
||||
blockBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
payloadBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
syncCommitteeHeadState: cache.NewSyncCommitteeHeadState(),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
@@ -211,6 +214,7 @@ func (s *Service) Start() {
|
||||
}
|
||||
s.spawnProcessAttestationsRoutine()
|
||||
go s.runLateBlockTasks()
|
||||
go s.runLatePayloadTasks()
|
||||
}
|
||||
|
||||
// Stop the blockchain service's main event loop and associated goroutines.
|
||||
@@ -341,7 +345,7 @@ func (s *Service) initializeHead(ctx context.Context, st state.BeaconState) erro
|
||||
return errors.Wrap(err, "could not get head state")
|
||||
}
|
||||
}
|
||||
if err := s.setHead(&head{root, blk, st, blk.Block().Slot(), false}); err != nil {
|
||||
if err := s.setHead(&head{root, blk, st, blk.Block().Slot(), false, false}); err != nil {
|
||||
return errors.Wrap(err, "could not set head")
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
@@ -430,6 +434,7 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon
|
||||
genesisState,
|
||||
genesisBlk.Block().Slot(),
|
||||
false,
|
||||
false,
|
||||
}); err != nil {
|
||||
log.WithError(err).Fatal("Could not set head")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -77,8 +78,12 @@ func (s *Service) setupForkchoiceTree(st state.BeaconState) error {
|
||||
log.WithError(err).Error("Could not build forkchoice chain, starting with finalized block as head")
|
||||
return nil
|
||||
}
|
||||
resolveChainPayloadStatus(chain)
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
if err := s.markFinalizedRootFull(chain, fRoot); err != nil {
|
||||
log.WithError(err).Error("Could not mark finalized root as full in forkchoice")
|
||||
}
|
||||
return s.cfg.ForkChoiceStore.InsertChain(s.ctx, chain)
|
||||
}
|
||||
|
||||
@@ -145,6 +150,68 @@ func (s *Service) setupForkchoiceRoot(st state.BeaconState) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolveChainPayloadStatus determines which blocks in the chain had their
|
||||
// execution payloads delivered by checking if consecutive blocks' bids indicate
|
||||
// payload delivery. For each pair of blocks (chain[i], chain[i+1]), if the next
|
||||
// block's bid parentBlockHash equals the current block's bid blockHash, the
|
||||
// current block's payload was delivered.
|
||||
func resolveChainPayloadStatus(chain []*forkchoicetypes.BlockAndCheckpoints) {
|
||||
for i := 0; i < len(chain)-1; i++ {
|
||||
curr := chain[i].Block.Block()
|
||||
next := chain[i+1].Block.Block()
|
||||
if curr.Version() < version.Gloas || next.Version() < version.Gloas {
|
||||
continue
|
||||
}
|
||||
currBid, err := curr.Body().SignedExecutionPayloadBid()
|
||||
if err != nil || currBid == nil || currBid.Message == nil {
|
||||
continue
|
||||
}
|
||||
nextBid, err := next.Body().SignedExecutionPayloadBid()
|
||||
if err != nil || nextBid == nil || nextBid.Message == nil {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(nextBid.Message.ParentBlockHash, currBid.Message.BlockHash) {
|
||||
chain[i].HasPayload = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// markFinalizedRootFull checks whether the finalized root block's execution
|
||||
// payload was delivered by inspecting the first block in the chain. If the first
|
||||
// block's bid parentBlockHash equals the finalized block's bid blockHash, the
|
||||
// finalized block's payload was delivered and a full node must be created in
|
||||
// forkchoice. The caller must hold the forkchoice lock.
|
||||
func (s *Service) markFinalizedRootFull(chain []*forkchoicetypes.BlockAndCheckpoints, fRoot [32]byte) error {
|
||||
if len(chain) == 0 {
|
||||
return nil
|
||||
}
|
||||
firstBlock := chain[0].Block.Block()
|
||||
if firstBlock.Version() < version.Gloas {
|
||||
return nil
|
||||
}
|
||||
firstBid, err := firstBlock.Body().SignedExecutionPayloadBid()
|
||||
if err != nil || firstBid == nil || firstBid.Message == nil {
|
||||
return nil
|
||||
}
|
||||
fBlock, err := s.cfg.BeaconDB.Block(s.ctx, fRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized block")
|
||||
}
|
||||
if fBlock.Block().Version() < version.Gloas {
|
||||
return nil
|
||||
}
|
||||
fBid, err := fBlock.Block().Body().SignedExecutionPayloadBid()
|
||||
if err != nil || fBid == nil || fBid.Message == nil {
|
||||
return nil
|
||||
}
|
||||
if !bytes.Equal(firstBid.Message.ParentBlockHash, fBid.Message.BlockHash) {
|
||||
return nil
|
||||
}
|
||||
// The finalized block's payload was delivered. Create the full node.
|
||||
s.cfg.ForkChoiceStore.MarkFullNode(fRoot)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) setupForkchoiceCheckpoints() error {
|
||||
justified, err := s.cfg.BeaconDB.JustifiedCheckpoint(s.ctx)
|
||||
if err != nil {
|
||||
@@ -166,11 +233,11 @@ func (s *Service) setupForkchoiceCheckpoints() error {
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
if err := s.cfg.ForkChoiceStore.UpdateJustifiedCheckpoint(s.ctx, &forkchoicetypes.Checkpoint{Epoch: justified.Epoch,
|
||||
Root: bytesutil.ToBytes32(justified.Root)}); err != nil {
|
||||
return errors.Wrap(err, "could not update forkchoice's justified checkpoint")
|
||||
log.WithError(err).Error("Could not update forkchoice's justified checkpoint, trying to update finalized checkpoint anyway")
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: finalized.Epoch,
|
||||
Root: fRoot}); err != nil {
|
||||
return errors.Wrap(err, "could not update forkchoice's finalized checkpoint")
|
||||
log.WithError(err).Error("Could not update forkchoice's finalized checkpoint")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.SetGenesisTime(s.genesisTime)
|
||||
return nil
|
||||
|
||||
@@ -104,7 +104,9 @@ func Test_setupForkchoiceTree_Head(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
preState, err := service.GetBlockPreState(ctx, roblock)
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -94,6 +94,11 @@ func (mb *mockBroadcaster) BroadcastDataColumnSidecars(_ context.Context, _ []bl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mb *mockBroadcaster) BroadcastForEpoch(_ context.Context, _ proto.Message, _ primitives.Epoch) error {
|
||||
mb.broadcastCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mb *mockBroadcaster) BroadcastBLSChanges(_ context.Context, _ []*ethpb.SignedBLSToExecutionChange) {
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,14 @@ type ChainService struct {
|
||||
DataColumns []blocks.VerifiedRODataColumn
|
||||
TargetRoot [32]byte
|
||||
MockHeadSlot *primitives.Slot
|
||||
DependentRootCB func([32]byte, primitives.Epoch) ([32]byte, error)
|
||||
MockCanonicalRoots map[primitives.Slot][32]byte
|
||||
MockCanonicalFull map[primitives.Slot]bool
|
||||
MockPayloadContentLookup map[[32]byte][32]byte
|
||||
MockPayloadContentIsFull map[[32]byte]bool
|
||||
ParentPayloadReadyVal *bool
|
||||
ForkchoiceRoots map[[32]byte]bool
|
||||
ForkchoiceBlockHashes map[[32]byte][32]byte
|
||||
}
|
||||
|
||||
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
|
||||
@@ -274,7 +282,7 @@ func (s *ChainService) ReceiveBlockInitialSync(ctx context.Context, block interf
|
||||
}
|
||||
|
||||
// ReceiveBlockBatch processes blocks in batches from initial-sync.
|
||||
func (s *ChainService) ReceiveBlockBatch(ctx context.Context, blks []blocks.ROBlock, _ das.AvailabilityChecker) error {
|
||||
func (s *ChainService) ReceiveBlockBatch(ctx context.Context, blks []blocks.ROBlock, _ []interfaces.ROSignedExecutionPayloadEnvelope, _ das.AvailabilityChecker) error {
|
||||
if s.State == nil {
|
||||
return ErrNilState
|
||||
}
|
||||
@@ -334,6 +342,16 @@ func (s *ChainService) ReceiveBlock(ctx context.Context, block interfaces.ReadOn
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockPreState mocks the same method in the chain service.
|
||||
func (s *ChainService) GetBlockPreState(_ context.Context, _ blocks.ROBlock) (state.BeaconState, error) {
|
||||
return s.State, nil
|
||||
}
|
||||
|
||||
// GetPrestateToPropose mocks the same method in the chain service.
|
||||
func (s *ChainService) GetPrestateToPropose(_ context.Context, _ blocks.ROBlock) (state.BeaconState, error) {
|
||||
return s.State.Copy(), nil
|
||||
}
|
||||
|
||||
// HeadSlot mocks HeadSlot method in chain service.
|
||||
func (s *ChainService) HeadSlot() primitives.Slot {
|
||||
if s.MockHeadSlot != nil {
|
||||
@@ -569,10 +587,23 @@ func (s *ChainService) IsOptimistic(_ context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
// InForkchoice mocks the same method in the chain service
|
||||
func (s *ChainService) InForkchoice(_ [32]byte) bool {
|
||||
func (s *ChainService) InForkchoice(root [32]byte) bool {
|
||||
if s.ForkchoiceRoots != nil {
|
||||
return s.ForkchoiceRoots[root]
|
||||
}
|
||||
return !s.NotFinalized
|
||||
}
|
||||
|
||||
// BlockHash mocks the execution payload block hash lookup for a beacon block root.
|
||||
func (s *ChainService) BlockHash(root [32]byte) ([32]byte, error) {
|
||||
if s.ForkchoiceBlockHashes != nil {
|
||||
if blockHash, ok := s.ForkchoiceBlockHashes[root]; ok {
|
||||
return blockHash, nil
|
||||
}
|
||||
}
|
||||
return [32]byte{}, errors.New("block hash not found")
|
||||
}
|
||||
|
||||
// IsOptimisticForRoot mocks the same method in the chain service.
|
||||
func (s *ChainService) IsOptimisticForRoot(_ context.Context, root [32]byte) (bool, error) {
|
||||
s.OptimisticCheckRootReceived = root
|
||||
@@ -630,7 +661,7 @@ func prepareForkchoiceState(
|
||||
}
|
||||
|
||||
base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...)
|
||||
st, err := state_native.InitializeFromProtoBellatrix(base)
|
||||
st, err := state_native.InitializeFromProtoUnsafeBellatrix(base)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
@@ -689,7 +720,60 @@ func (s *ChainService) HighestReceivedBlockSlot() primitives.Slot {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.HighestReceivedBlockSlot()
|
||||
}
|
||||
return 0
|
||||
if s.Slot != nil {
|
||||
return *s.Slot
|
||||
}
|
||||
return s.BlockSlot
|
||||
}
|
||||
|
||||
// HighestReceivedBlockRoot mocks the same method in the chain service
|
||||
func (s *ChainService) HighestReceivedBlockRoot() [32]byte {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.HighestReceivedBlockRoot()
|
||||
}
|
||||
if s.Slot != nil && s.MockCanonicalRoots != nil {
|
||||
if root, ok := s.MockCanonicalRoots[*s.Slot]; ok {
|
||||
return root
|
||||
}
|
||||
}
|
||||
if len(s.Root) == 32 {
|
||||
return bytesutil.ToBytes32(s.Root)
|
||||
}
|
||||
return [32]byte{}
|
||||
}
|
||||
|
||||
// HasFullNode mocks the same method in the chain service
|
||||
func (s *ChainService) HasFullNode(root [32]byte) bool {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.HasFullNode(root)
|
||||
}
|
||||
if s.Slot != nil && s.MockCanonicalRoots != nil && s.MockCanonicalFull != nil {
|
||||
if r, ok := s.MockCanonicalRoots[*s.Slot]; ok && r == root {
|
||||
return s.MockCanonicalFull[*s.Slot]
|
||||
}
|
||||
}
|
||||
if s.ForkchoiceRoots != nil {
|
||||
return s.ForkchoiceRoots[root]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ShouldIgnoreData returns true if the data for the given parent root and slot should be ignored.
|
||||
func (s *ChainService) ShouldIgnoreData(_ [32]byte, _ primitives.Slot) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// PayloadContentLookup mocks the same method in the chain service.
|
||||
func (s *ChainService) PayloadContentLookup(root [32]byte) ([32]byte, bool) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.PayloadContentLookup(root)
|
||||
}
|
||||
if s.MockPayloadContentLookup != nil {
|
||||
if value, ok := s.MockPayloadContentLookup[root]; ok {
|
||||
return value, s.MockPayloadContentIsFull[root]
|
||||
}
|
||||
}
|
||||
return root, false
|
||||
}
|
||||
|
||||
// InsertNode mocks the same method in the chain service
|
||||
@@ -700,6 +784,14 @@ func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, blo
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertPayload mocks the same method in the chain service
|
||||
func (s *ChainService) InsertPayload(pe interfaces.ROExecutionPayloadEnvelope) error {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.InsertPayload(pe)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForkChoiceDump mocks the same method in the chain service
|
||||
func (s *ChainService) ForkChoiceDump(ctx context.Context) (*forkchoice2.Dump, error) {
|
||||
if s.ForkChoiceStore != nil {
|
||||
@@ -757,8 +849,29 @@ func (c *ChainService) ReceiveDataColumns(dcs []blocks.VerifiedRODataColumn) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceivePayloadAttestationMessage implements the same method in the chain service.
|
||||
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceiveExecutionPayloadEnvelope implements the same method in the chain service.
|
||||
func (c *ChainService) ReceiveExecutionPayloadEnvelope(_ context.Context, _ interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParentPayloadReady mocks the same method in the chain service.
|
||||
func (s *ChainService) ParentPayloadReady(_ interfaces.ReadOnlyBeaconBlock) bool {
|
||||
if s.ParentPayloadReadyVal != nil {
|
||||
return *s.ParentPayloadReadyVal
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// DependentRootForEpoch mocks the same method in the chain service
|
||||
func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
|
||||
func (c *ChainService) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
|
||||
if c.DependentRootCB != nil {
|
||||
return c.DependentRootCB(root, epoch)
|
||||
}
|
||||
return c.TargetRoot, nil
|
||||
}
|
||||
|
||||
@@ -767,6 +880,17 @@ func (c *ChainService) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]b
|
||||
return c.TargetRoot, nil
|
||||
}
|
||||
|
||||
func (c *ChainService) CanonicalNodeAtSlot(slot primitives.Slot) ([32]byte, bool) {
|
||||
var root [32]byte
|
||||
if c.MockCanonicalRoots != nil {
|
||||
root = c.MockCanonicalRoots[slot]
|
||||
}
|
||||
if c.MockCanonicalFull != nil {
|
||||
return root, c.MockCanonicalFull[slot]
|
||||
}
|
||||
return root, false
|
||||
}
|
||||
|
||||
// MockSyncChecker is a mock implementation of blockchain.Checker.
|
||||
// We can't make an assertion here that this is true because that would create a circular dependency.
|
||||
type MockSyncChecker struct {
|
||||
|
||||
@@ -8,11 +8,35 @@ import (
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
// proposerPreference returns a TrackedValidator from the ProposerPreferencesCache
|
||||
// if a preference exists for the given slot.
|
||||
func (s *Service) proposerPreference(slot primitives.Slot) (cache.TrackedValidator, bool) {
|
||||
if s.cfg.ProposerPreferencesCache == nil {
|
||||
return cache.TrackedValidator{}, false
|
||||
}
|
||||
pref, ok := s.cfg.ProposerPreferencesCache.Get(slot)
|
||||
if !ok {
|
||||
return cache.TrackedValidator{}, false
|
||||
}
|
||||
var feeRecipient primitives.ExecutionAddress
|
||||
copy(feeRecipient[:], pref.FeeRecipient)
|
||||
return cache.TrackedValidator{Active: true, FeeRecipient: feeRecipient, GasLimit: pref.GasLimit}, true
|
||||
}
|
||||
|
||||
// trackedProposer returns whether the beacon node was informed, via the
|
||||
// validators/prepare_proposer endpoint, of the proposer at the given slot.
|
||||
// It only returns true if the tracked proposer is present and active.
|
||||
//
|
||||
// When PrepareAllPayloads is enabled, the node prepares payloads for every
|
||||
// slot. After the Gloas fork, proposers broadcast their preferences (fee
|
||||
// recipient, gas limit) via gossip into the ProposerPreferencesCache. When
|
||||
// available, these preferences supply the fee recipient; otherwise the
|
||||
// default (burn address) is used.
|
||||
func (s *Service) trackedProposer(st state.ReadOnlyBeaconState, slot primitives.Slot) (cache.TrackedValidator, bool) {
|
||||
if features.Get().PrepareAllPayloads {
|
||||
if val, ok := s.proposerPreference(slot); ok {
|
||||
return val, true
|
||||
}
|
||||
return cache.TrackedValidator{Active: true}, true
|
||||
}
|
||||
id, err := helpers.BeaconProposerIndexAtSlot(s.ctx, st, slot)
|
||||
@@ -23,5 +47,8 @@ func (s *Service) trackedProposer(st state.ReadOnlyBeaconState, slot primitives.
|
||||
if !ok {
|
||||
return cache.TrackedValidator{}, false
|
||||
}
|
||||
if pref, ok := s.proposerPreference(slot); ok {
|
||||
return pref, true
|
||||
}
|
||||
return val, val.Active
|
||||
}
|
||||
|
||||
83
beacon-chain/blockchain/tracked_proposer_test.go
Normal file
83
beacon-chain/blockchain/tracked_proposer_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/config/features"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/util"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func TestTrackedProposer_NotTracked(t *testing.T) {
|
||||
service, _ := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
_, ok := service.trackedProposer(st, 0)
|
||||
require.Equal(t, false, ok)
|
||||
}
|
||||
|
||||
func TestTrackedProposer_Tracked(t *testing.T) {
|
||||
service, _ := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
addr := common.HexToAddress("0x1234")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(addr), Index: 0})
|
||||
val, ok := service.trackedProposer(st, 0)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, primitives.ExecutionAddress(addr), val.FeeRecipient)
|
||||
}
|
||||
|
||||
func TestTrackedProposer_PrepareAllPayloads_Default(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{PrepareAllPayloads: true})
|
||||
defer resetCfg()
|
||||
|
||||
service, _ := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
val, ok := service.trackedProposer(st, 0)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, true, val.Active)
|
||||
require.Equal(t, params.BeaconConfig().EthBurnAddressHex, common.BytesToAddress(val.FeeRecipient[:]).String())
|
||||
}
|
||||
|
||||
func TestTrackedProposer_PrepareAllPayloads_WithProposerPreference(t *testing.T) {
|
||||
resetCfg := features.InitWithReset(&features.Flags{PrepareAllPayloads: true})
|
||||
defer resetCfg()
|
||||
|
||||
prefCache := cache.NewProposerPreferencesCache()
|
||||
service, _ := minimalTestService(t,
|
||||
WithPayloadIDCache(cache.NewPayloadIDCache()),
|
||||
WithProposerPreferencesCache(prefCache),
|
||||
)
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
|
||||
addr := common.HexToAddress("0xabcd")
|
||||
prefCache.Add(0, addr.Bytes(), 42_000_000)
|
||||
|
||||
val, ok := service.trackedProposer(st, 0)
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, true, val.Active)
|
||||
require.Equal(t, primitives.ExecutionAddress(addr), val.FeeRecipient)
|
||||
require.Equal(t, uint64(42_000_000), val.GasLimit)
|
||||
}
|
||||
|
||||
func TestTrackedProposer_TrackedWithProposerPreferenceOverride(t *testing.T) {
|
||||
prefCache := cache.NewProposerPreferencesCache()
|
||||
service, _ := minimalTestService(t,
|
||||
WithPayloadIDCache(cache.NewPayloadIDCache()),
|
||||
WithProposerPreferencesCache(prefCache),
|
||||
)
|
||||
st, _ := util.DeterministicGenesisStateBellatrix(t, 1)
|
||||
|
||||
trackedAddr := common.HexToAddress("0x1111")
|
||||
prefAddr := common.HexToAddress("0x2222")
|
||||
service.cfg.TrackedValidatorsCache.Set(cache.TrackedValidator{Active: true, FeeRecipient: primitives.ExecutionAddress(trackedAddr), Index: 0})
|
||||
prefCache.Add(0, prefAddr.Bytes(), 50_000_000)
|
||||
|
||||
val, ok := service.trackedProposer(st, 0)
|
||||
require.Equal(t, true, ok)
|
||||
// Proposer preference overrides tracked validator.
|
||||
require.Equal(t, primitives.ExecutionAddress(prefAddr), val.FeeRecipient)
|
||||
require.Equal(t, uint64(50_000_000), val.GasLimit)
|
||||
}
|
||||
7
beacon-chain/cache/BUILD.bazel
vendored
7
beacon-chain/cache/BUILD.bazel
vendored
@@ -15,12 +15,15 @@ go_library(
|
||||
"common.go",
|
||||
"doc.go",
|
||||
"error.go",
|
||||
"highest_execution_payload_bid.go",
|
||||
"interfaces.go",
|
||||
"log.go",
|
||||
"payload_attestation.go",
|
||||
"payload_id.go",
|
||||
"proposer_indices.go",
|
||||
"proposer_indices_disabled.go", # keep
|
||||
"proposer_indices_type.go",
|
||||
"proposer_preferences.go",
|
||||
"registration.go",
|
||||
"skip_slot_cache.go",
|
||||
"subnet_ids.go",
|
||||
@@ -54,6 +57,7 @@ go_library(
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//proto/prysm/v1alpha1/attestation:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
"@com_github_hashicorp_golang_lru//:go_default_library",
|
||||
"@com_github_patrickmn_go_cache//:go_default_library",
|
||||
@@ -76,9 +80,12 @@ go_test(
|
||||
"checkpoint_state_test.go",
|
||||
"committee_fuzz_test.go",
|
||||
"committee_test.go",
|
||||
"highest_execution_payload_bid_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_id_test.go",
|
||||
"private_access_test.go",
|
||||
"proposer_indices_test.go",
|
||||
"proposer_preferences_test.go",
|
||||
"registration_test.go",
|
||||
"skip_slot_cache_test.go",
|
||||
"subnet_ids_test.go",
|
||||
|
||||
9
beacon-chain/cache/attestation_data.go
vendored
9
beacon-chain/cache/attestation_data.go
vendored
@@ -9,10 +9,11 @@ import (
|
||||
)
|
||||
|
||||
type AttestationConsensusData struct {
|
||||
Slot primitives.Slot
|
||||
HeadRoot []byte
|
||||
Target forkchoicetypes.Checkpoint
|
||||
Source forkchoicetypes.Checkpoint
|
||||
Slot primitives.Slot
|
||||
HeadRoot []byte
|
||||
Target forkchoicetypes.Checkpoint
|
||||
Source forkchoicetypes.Checkpoint
|
||||
IsPayloadFull bool
|
||||
}
|
||||
|
||||
// AttestationDataCache stores cached results of AttestationData requests.
|
||||
|
||||
51
beacon-chain/cache/checkpoint_state.go
vendored
51
beacon-chain/cache/checkpoint_state.go
vendored
@@ -3,8 +3,10 @@ package cache
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
lruwrpr "github.com/OffchainLabs/prysm/v7/cache/lru"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/hash"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
@@ -25,6 +27,14 @@ var (
|
||||
Name: "check_point_state_cache_hit",
|
||||
Help: "The number of check point state requests that are present in the cache.",
|
||||
})
|
||||
checkpointStateSize = promauto.NewGauge(prometheus.GaugeOpts{
|
||||
Name: "check_point_state_cache_size",
|
||||
Help: "The number of entries in the check point state cache.",
|
||||
})
|
||||
checkpointStateEvicted = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "check_point_state_cache_evicted_total",
|
||||
Help: "The number of entries evicted from the check point state cache.",
|
||||
})
|
||||
)
|
||||
|
||||
// CheckpointStateCache is a struct with 1 queue for looking up state by checkpoint.
|
||||
@@ -49,14 +59,14 @@ func (c *CheckpointStateCache) StateByCheckpoint(cp *ethpb.Checkpoint) (state.Be
|
||||
|
||||
item, exists := c.cache.Get(h)
|
||||
|
||||
if exists && item != nil {
|
||||
checkpointStateHit.Inc()
|
||||
// Copy here is unnecessary since the return will only be used to verify attestation signature.
|
||||
return item.(state.BeaconState), nil
|
||||
if !exists || item == nil {
|
||||
checkpointStateMiss.Inc()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
checkpointStateMiss.Inc()
|
||||
return nil, nil
|
||||
checkpointStateHit.Inc()
|
||||
// Copy here is unnecessary since the return will only be used to verify attestation signature.
|
||||
return item.(state.BeaconState), nil
|
||||
}
|
||||
|
||||
// AddCheckpointState adds CheckpointState object to the cache. This method also trims the least
|
||||
@@ -66,6 +76,35 @@ func (c *CheckpointStateCache) AddCheckpointState(cp *ethpb.Checkpoint, s state.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.cache.Add(h, s)
|
||||
checkpointStateSize.Set(float64(c.cache.Len()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// EvictUpTo removes all entries from the cache whose state epoch is at
|
||||
// or before the given epoch. Returns the number of evicted entries.
|
||||
func (c *CheckpointStateCache) EvictUpTo(epoch primitives.Epoch) int {
|
||||
evicted := 0
|
||||
for _, key := range c.cache.Keys() {
|
||||
// Peek is used here to avoid updating the recency of the entry,
|
||||
// as we are only checking for eviction.
|
||||
v, ok := c.cache.Peek(key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
st := v.(state.ReadOnlyBeaconState)
|
||||
if slots.ToEpoch(st.Slot()) <= epoch {
|
||||
c.cache.Remove(key)
|
||||
evicted++
|
||||
}
|
||||
}
|
||||
|
||||
if evicted > 0 {
|
||||
checkpointStateSize.Set(float64(c.cache.Len()))
|
||||
checkpointStateEvicted.Add(float64(evicted))
|
||||
}
|
||||
|
||||
return evicted
|
||||
}
|
||||
|
||||
73
beacon-chain/cache/checkpoint_state_test.go
vendored
73
beacon-chain/cache/checkpoint_state_test.go
vendored
@@ -72,3 +72,76 @@ func TestCheckpointStateCache_MaxSize(t *testing.T) {
|
||||
|
||||
assert.Equal(t, cache.MaxCheckpointStateSize(), len(c.Cache().Keys()))
|
||||
}
|
||||
|
||||
func TestCheckpointStateCache_EvictFinalized_FinalizedEntry(t *testing.T) {
|
||||
c := cache.NewCheckpointStateCache()
|
||||
|
||||
cp := ðpb.Checkpoint{Epoch: 1, Root: bytesutil.PadTo([]byte{'A'}, 32)}
|
||||
st, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Slot: 32})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.AddCheckpointState(cp, st))
|
||||
|
||||
evicted := c.EvictUpTo(1)
|
||||
assert.Equal(t, 1, evicted, "expected finalized entry to be evicted")
|
||||
|
||||
s, err := c.StateByCheckpoint(cp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, state.BeaconState(nil), s, "expected cache to be empty after eviction")
|
||||
}
|
||||
|
||||
func TestCheckpointStateCache_EvictFinalized_NotFinalizedEntry(t *testing.T) {
|
||||
c := cache.NewCheckpointStateCache()
|
||||
|
||||
cp := ðpb.Checkpoint{Epoch: 5, Root: bytesutil.PadTo([]byte{'A'}, 32)}
|
||||
st, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Slot: 160})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.AddCheckpointState(cp, st))
|
||||
|
||||
evicted := c.EvictUpTo(3)
|
||||
assert.Equal(t, 0, evicted, "expected non-finalized entry NOT to be evicted")
|
||||
|
||||
s, err := c.StateByCheckpoint(cp)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, s, "expected entry to still be in cache")
|
||||
}
|
||||
|
||||
func TestCheckpointStateCache_EvictFinalized_Mixed(t *testing.T) {
|
||||
c := cache.NewCheckpointStateCache()
|
||||
|
||||
cp1 := ðpb.Checkpoint{Epoch: 1, Root: bytesutil.PadTo([]byte{'A'}, 32)}
|
||||
st1, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Slot: 32})
|
||||
require.NoError(t, err)
|
||||
|
||||
cp2 := ðpb.Checkpoint{Epoch: 2, Root: bytesutil.PadTo([]byte{'B'}, 32)}
|
||||
st2, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Slot: 64})
|
||||
require.NoError(t, err)
|
||||
|
||||
cp5 := ðpb.Checkpoint{Epoch: 5, Root: bytesutil.PadTo([]byte{'C'}, 32)}
|
||||
st5, err := state_native.InitializeFromProtoPhase0(ðpb.BeaconState{Slot: 160})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, c.AddCheckpointState(cp1, st1))
|
||||
require.NoError(t, c.AddCheckpointState(cp2, st2))
|
||||
require.NoError(t, c.AddCheckpointState(cp5, st5))
|
||||
|
||||
evicted := c.EvictUpTo(3)
|
||||
assert.Equal(t, 2, evicted, "expected epochs 1 and 2 to be evicted")
|
||||
|
||||
s, err := c.StateByCheckpoint(cp1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, state.BeaconState(nil), s, "expected cp1 to be evicted")
|
||||
|
||||
s, err = c.StateByCheckpoint(cp2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, state.BeaconState(nil), s, "expected cp2 to be evicted")
|
||||
|
||||
s, err = c.StateByCheckpoint(cp5)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, s, "expected cp5 to still be in cache")
|
||||
}
|
||||
|
||||
func TestCheckpointStateCache_EvictFinalized_EmptyCache(t *testing.T) {
|
||||
c := cache.NewCheckpointStateCache()
|
||||
evicted := c.EvictUpTo(0)
|
||||
assert.Equal(t, 0, evicted, "expected no eviction from empty cache")
|
||||
}
|
||||
|
||||
76
beacon-chain/cache/highest_execution_payload_bid.go
vendored
Normal file
76
beacon-chain/cache/highest_execution_payload_bid.go
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
type executionPayloadBidKey struct {
|
||||
slot primitives.Slot
|
||||
parentHash [32]byte
|
||||
parentRoot [32]byte
|
||||
}
|
||||
|
||||
// HighestExecutionPayloadBidCache stores the highest bid for each
|
||||
// (slot, parent_block_hash, parent_block_root) tuple.
|
||||
type HighestExecutionPayloadBidCache struct {
|
||||
bids map[executionPayloadBidKey]*ethpb.SignedExecutionPayloadBid
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewHighestExecutionPayloadBidCache initializes a highest-bid cache.
|
||||
func NewHighestExecutionPayloadBidCache() *HighestExecutionPayloadBidCache {
|
||||
return &HighestExecutionPayloadBidCache{
|
||||
bids: make(map[executionPayloadBidKey]*ethpb.SignedExecutionPayloadBid),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the highest cached bid for the given tuple.
|
||||
func (c *HighestExecutionPayloadBidCache) Get(
|
||||
slot primitives.Slot,
|
||||
parentHash [32]byte,
|
||||
parentRoot [32]byte,
|
||||
) (*ethpb.SignedExecutionPayloadBid, bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
bid, ok := c.bids[executionPayloadBidKey{
|
||||
slot: slot,
|
||||
parentHash: parentHash,
|
||||
parentRoot: parentRoot,
|
||||
}]
|
||||
return bid, ok
|
||||
}
|
||||
|
||||
// SetIfHigher inserts the bid if absent, or replaces the cached bid only if
|
||||
// the incoming value is strictly greater.
|
||||
func (c *HighestExecutionPayloadBidCache) SetIfHigher(bid *ethpb.SignedExecutionPayloadBid) bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key := executionPayloadBidKey{
|
||||
slot: bid.Message.Slot,
|
||||
parentHash: [32]byte(bid.Message.ParentBlockHash),
|
||||
parentRoot: [32]byte(bid.Message.ParentBlockRoot),
|
||||
}
|
||||
cached, ok := c.bids[key]
|
||||
if !ok || bid.Message.Value > cached.Message.Value {
|
||||
c.bids[key] = bid
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PruneBefore removes all cached bids for slots before the provided slot.
|
||||
func (c *HighestExecutionPayloadBidCache) PruneBefore(slot primitives.Slot) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
for key := range c.bids {
|
||||
if key.slot < slot {
|
||||
delete(c.bids, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
105
beacon-chain/cache/highest_execution_payload_bid_test.go
vendored
Normal file
105
beacon-chain/cache/highest_execution_payload_bid_test.go
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestHighestExecutionPayloadBidCache_GetSetIfHigher(t *testing.T) {
|
||||
c := NewHighestExecutionPayloadBidCache()
|
||||
bid := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x02}, 100)
|
||||
|
||||
inserted := c.SetIfHigher(bid)
|
||||
require.Equal(t, true, inserted)
|
||||
|
||||
got, ok := c.Get(10, [32]byte{0x01}, [32]byte{0x02})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, bid, got)
|
||||
}
|
||||
|
||||
func TestHighestExecutionPayloadBidCache_SetIfHigher_ReplacesOnlyOnHigherValue(t *testing.T) {
|
||||
c := NewHighestExecutionPayloadBidCache()
|
||||
low := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x02}, 100)
|
||||
same := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x02}, 100)
|
||||
high := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x02}, 101)
|
||||
|
||||
require.Equal(t, true, c.SetIfHigher(low))
|
||||
require.Equal(t, false, c.SetIfHigher(same))
|
||||
|
||||
got, ok := c.Get(10, [32]byte{0x01}, [32]byte{0x02})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, low, got)
|
||||
|
||||
require.Equal(t, true, c.SetIfHigher(high))
|
||||
got, ok = c.Get(10, [32]byte{0x01}, [32]byte{0x02})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, high, got)
|
||||
}
|
||||
|
||||
func TestHighestExecutionPayloadBidCache_SetIfHigher_KeepsDistinctTuples(t *testing.T) {
|
||||
c := NewHighestExecutionPayloadBidCache()
|
||||
first := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x02}, 100)
|
||||
second := testSignedExecutionPayloadBid(10, [32]byte{0x03}, [32]byte{0x02}, 50)
|
||||
third := testSignedExecutionPayloadBid(10, [32]byte{0x01}, [32]byte{0x04}, 75)
|
||||
|
||||
require.Equal(t, true, c.SetIfHigher(first))
|
||||
require.Equal(t, true, c.SetIfHigher(second))
|
||||
require.Equal(t, true, c.SetIfHigher(third))
|
||||
|
||||
got, ok := c.Get(10, [32]byte{0x01}, [32]byte{0x02})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, first, got)
|
||||
|
||||
got, ok = c.Get(10, [32]byte{0x03}, [32]byte{0x02})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, second, got)
|
||||
|
||||
got, ok = c.Get(10, [32]byte{0x01}, [32]byte{0x04})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, third, got)
|
||||
}
|
||||
|
||||
func TestHighestExecutionPayloadBidCache_PruneBefore(t *testing.T) {
|
||||
c := NewHighestExecutionPayloadBidCache()
|
||||
oldBid := testSignedExecutionPayloadBid(9, [32]byte{0x01}, [32]byte{0x02}, 100)
|
||||
currentBid := testSignedExecutionPayloadBid(10, [32]byte{0x03}, [32]byte{0x04}, 101)
|
||||
|
||||
require.Equal(t, true, c.SetIfHigher(oldBid))
|
||||
require.Equal(t, true, c.SetIfHigher(currentBid))
|
||||
|
||||
c.PruneBefore(10)
|
||||
|
||||
_, ok := c.Get(9, [32]byte{0x01}, [32]byte{0x02})
|
||||
require.Equal(t, false, ok)
|
||||
|
||||
got, ok := c.Get(10, [32]byte{0x03}, [32]byte{0x04})
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, currentBid, got)
|
||||
}
|
||||
|
||||
func testSignedExecutionPayloadBid(
|
||||
slot primitives.Slot,
|
||||
parentHash [32]byte,
|
||||
parentRoot [32]byte,
|
||||
value uint64,
|
||||
) *ethpb.SignedExecutionPayloadBid {
|
||||
return ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
Slot: slot,
|
||||
ParentBlockHash: bytes.Clone(parentHash[:]),
|
||||
ParentBlockRoot: bytes.Clone(parentRoot[:]),
|
||||
BlockHash: bytes.Repeat([]byte{0x03}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x04}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x05}, 20),
|
||||
GasLimit: 30_000_000,
|
||||
BuilderIndex: 1,
|
||||
Value: primitives.Gwei(value),
|
||||
ExecutionPayment: 10,
|
||||
},
|
||||
Signature: bytes.Repeat([]byte{0x06}, 96),
|
||||
}
|
||||
}
|
||||
53
beacon-chain/cache/payload_attestation.go
vendored
Normal file
53
beacon-chain/cache/payload_attestation.go
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
// PayloadAttestationCache tracks seen payload attestation messages for a single slot.
|
||||
type PayloadAttestationCache struct {
|
||||
slot primitives.Slot
|
||||
seen map[primitives.ValidatorIndex]struct{}
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// Seen returns true if a vote for the given slot has already been
|
||||
// processed for this validator index.
|
||||
func (p *PayloadAttestationCache) Seen(slot primitives.Slot, idx primitives.ValidatorIndex) bool {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
if p.slot != slot {
|
||||
return false
|
||||
}
|
||||
if p.seen == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := p.seen[idx]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Add marks the given slot and validator index as seen.
|
||||
// This function assumes that the message has already been validated.
|
||||
func (p *PayloadAttestationCache) Add(slot primitives.Slot, idx primitives.ValidatorIndex) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.slot != slot {
|
||||
p.slot = slot
|
||||
p.seen = make(map[primitives.ValidatorIndex]struct{})
|
||||
}
|
||||
if p.seen == nil {
|
||||
p.seen = make(map[primitives.ValidatorIndex]struct{})
|
||||
}
|
||||
p.seen[idx] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear clears the internal cache.
|
||||
func (p *PayloadAttestationCache) Clear() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.slot = 0
|
||||
p.seen = nil
|
||||
}
|
||||
48
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
48
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPayloadAttestationCache_SeenAndAdd(t *testing.T) {
|
||||
var c cache.PayloadAttestationCache
|
||||
slot1 := primitives.Slot(1)
|
||||
slot2 := primitives.Slot(2)
|
||||
idx1 := primitives.ValidatorIndex(3)
|
||||
idx2 := primitives.ValidatorIndex(4)
|
||||
|
||||
require.False(t, c.Seen(slot1, idx1))
|
||||
|
||||
require.NoError(t, c.Add(slot1, idx1))
|
||||
require.True(t, c.Seen(slot1, idx1))
|
||||
require.False(t, c.Seen(slot1, idx2))
|
||||
require.False(t, c.Seen(slot2, idx1))
|
||||
|
||||
require.NoError(t, c.Add(slot1, idx2))
|
||||
require.True(t, c.Seen(slot1, idx1))
|
||||
require.True(t, c.Seen(slot1, idx2))
|
||||
|
||||
require.NoError(t, c.Add(slot2, idx1))
|
||||
require.True(t, c.Seen(slot2, idx1))
|
||||
require.False(t, c.Seen(slot1, idx1))
|
||||
require.False(t, c.Seen(slot1, idx2))
|
||||
}
|
||||
|
||||
func TestPayloadAttestationCache_Clear(t *testing.T) {
|
||||
var c cache.PayloadAttestationCache
|
||||
slot := primitives.Slot(10)
|
||||
idx := primitives.ValidatorIndex(42)
|
||||
|
||||
require.NoError(t, c.Add(slot, idx))
|
||||
require.True(t, c.Seen(slot, idx))
|
||||
|
||||
c.Clear()
|
||||
require.False(t, c.Seen(slot, idx))
|
||||
|
||||
require.NoError(t, c.Add(slot, idx))
|
||||
require.True(t, c.Seen(slot, idx))
|
||||
}
|
||||
87
beacon-chain/cache/proposer_preferences.go
vendored
Normal file
87
beacon-chain/cache/proposer_preferences.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
)
|
||||
|
||||
// ProposerPreference stores the proposer fee recipient and gas limit for a slot.
|
||||
type ProposerPreference struct {
|
||||
FeeRecipient []byte
|
||||
GasLimit uint64
|
||||
}
|
||||
|
||||
// ProposerPreferencesCache stores proposer preferences by slot.
|
||||
type ProposerPreferencesCache struct {
|
||||
slotToPreferences map[primitives.Slot]ProposerPreference
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewProposerPreferencesCache initializes a proposer preferences cache.
|
||||
func NewProposerPreferencesCache() *ProposerPreferencesCache {
|
||||
return &ProposerPreferencesCache{
|
||||
slotToPreferences: make(map[primitives.Slot]ProposerPreference),
|
||||
}
|
||||
}
|
||||
|
||||
// Add stores proposer preferences for a slot. If the slot already exists, the
|
||||
// existing value is kept and false is returned.
|
||||
func (c *ProposerPreferencesCache) Add(slot primitives.Slot, feeRecipient []byte, gasLimit uint64) bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if _, ok := c.slotToPreferences[slot]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// FeeRecipient comes from validated SSZ-decoded proposer preferences, so
|
||||
// retaining the slice reference here is intentional.
|
||||
c.slotToPreferences[slot] = ProposerPreference{
|
||||
FeeRecipient: feeRecipient,
|
||||
GasLimit: gasLimit,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get returns proposer preferences for a slot.
|
||||
func (c *ProposerPreferencesCache) Get(slot primitives.Slot) (ProposerPreference, bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
pref, ok := c.slotToPreferences[slot]
|
||||
if !ok {
|
||||
return ProposerPreference{}, false
|
||||
}
|
||||
|
||||
return pref, true
|
||||
}
|
||||
|
||||
// Has returns true if proposer preferences for the slot already exist.
|
||||
func (c *ProposerPreferencesCache) Has(slot primitives.Slot) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
_, ok := c.slotToPreferences[slot]
|
||||
return ok
|
||||
}
|
||||
|
||||
// PruneBefore removes all proposer preferences for slots before the provided slot.
|
||||
func (c *ProposerPreferencesCache) PruneBefore(slot primitives.Slot) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
for cachedSlot := range c.slotToPreferences {
|
||||
if cachedSlot < slot {
|
||||
delete(c.slotToPreferences, cachedSlot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear removes all cached proposer preferences.
|
||||
func (c *ProposerPreferencesCache) Clear() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.slotToPreferences = make(map[primitives.Slot]ProposerPreference)
|
||||
}
|
||||
63
beacon-chain/cache/proposer_preferences_test.go
vendored
Normal file
63
beacon-chain/cache/proposer_preferences_test.go
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestProposerPreferencesCache_AddGetHas(t *testing.T) {
|
||||
c := NewProposerPreferencesCache()
|
||||
slot := primitives.Slot(123)
|
||||
feeRecipient := []byte{1, 2, 3, 4}
|
||||
|
||||
require.Equal(t, false, c.Has(slot))
|
||||
added := c.Add(slot, feeRecipient, 42)
|
||||
require.Equal(t, true, added)
|
||||
require.Equal(t, true, c.Has(slot))
|
||||
|
||||
pref, ok := c.Get(slot)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, feeRecipient, pref.FeeRecipient)
|
||||
require.Equal(t, uint64(42), pref.GasLimit)
|
||||
}
|
||||
|
||||
func TestProposerPreferencesCache_AddDuplicateSlot(t *testing.T) {
|
||||
c := NewProposerPreferencesCache()
|
||||
slot := primitives.Slot(456)
|
||||
|
||||
require.Equal(t, true, c.Add(slot, []byte{1}, 10))
|
||||
require.Equal(t, false, c.Add(slot, []byte{2}, 20))
|
||||
|
||||
pref, ok := c.Get(slot)
|
||||
require.Equal(t, true, ok)
|
||||
require.DeepEqual(t, []byte{1}, pref.FeeRecipient)
|
||||
require.Equal(t, uint64(10), pref.GasLimit)
|
||||
}
|
||||
|
||||
func TestProposerPreferencesCache_Clear(t *testing.T) {
|
||||
c := NewProposerPreferencesCache()
|
||||
slot := primitives.Slot(789)
|
||||
|
||||
require.Equal(t, true, c.Add(slot, []byte{1}, 10))
|
||||
c.Clear()
|
||||
|
||||
require.Equal(t, false, c.Has(slot))
|
||||
_, ok := c.Get(slot)
|
||||
require.Equal(t, false, ok)
|
||||
}
|
||||
|
||||
func TestProposerPreferencesCache_PruneBefore(t *testing.T) {
|
||||
c := NewProposerPreferencesCache()
|
||||
|
||||
require.Equal(t, true, c.Add(10, []byte{1}, 10))
|
||||
require.Equal(t, true, c.Add(11, []byte{2}, 11))
|
||||
require.Equal(t, true, c.Add(12, []byte{3}, 12))
|
||||
|
||||
c.PruneBefore(11)
|
||||
|
||||
require.Equal(t, false, c.Has(10))
|
||||
require.Equal(t, true, c.Has(11))
|
||||
require.Equal(t, true, c.Has(12))
|
||||
}
|
||||
2
beacon-chain/cache/sync_committee.go
vendored
2
beacon-chain/cache/sync_committee.go
vendored
@@ -172,7 +172,7 @@ func (s *SyncCommitteeCache) idxPositionInCommittee(
|
||||
// UpdatePositionsInCommittee updates caching of validators position in sync committee in respect to
|
||||
// current epoch and next epoch. This should be called when `current_sync_committee` and `next_sync_committee`
|
||||
// change and that happens every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD`.
|
||||
func (s *SyncCommitteeCache) UpdatePositionsInCommittee(syncCommitteeBoundaryRoot [32]byte, st state.BeaconState) error {
|
||||
func (s *SyncCommitteeCache) UpdatePositionsInCommittee(syncCommitteeBoundaryRoot [32]byte, st state.ReadOnlyBeaconState) error {
|
||||
// since we call UpdatePositionsInCommittee asynchronously, keep track of the cache value
|
||||
// seen at the beginning of the routine and compare at the end before updating. If the underlying value has been
|
||||
// cycled (new address), don't update it.
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *FakeSyncCommitteeCache) NextPeriodIndexPosition(root [32]byte, valIdx p
|
||||
}
|
||||
|
||||
// UpdatePositionsInCommittee -- fake.
|
||||
func (s *FakeSyncCommitteeCache) UpdatePositionsInCommittee(syncCommitteeBoundaryRoot [32]byte, state state.BeaconState) error {
|
||||
func (s *FakeSyncCommitteeCache) UpdatePositionsInCommittee(syncCommitteeBoundaryRoot [32]byte, state state.ReadOnlyBeaconState) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
1
beacon-chain/cache/tracked_validators.go
vendored
1
beacon-chain/cache/tracked_validators.go
vendored
@@ -21,6 +21,7 @@ type (
|
||||
Active bool
|
||||
FeeRecipient primitives.ExecutionAddress
|
||||
Index primitives.ValidatorIndex
|
||||
GasLimit uint64
|
||||
}
|
||||
|
||||
TrackedValidatorsCache struct {
|
||||
|
||||
@@ -20,6 +20,7 @@ go_library(
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/gloas:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/time"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
@@ -75,7 +76,11 @@ func ProcessAttestationNoVerifySignature(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance)
|
||||
if err := beaconState.UpdatePendingPaymentWeight(att, indices, participatedFlags); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to update pending payment weight")
|
||||
}
|
||||
|
||||
return SetParticipationAndRewardProposer(ctx, beaconState, att.GetData().Target.Epoch, indices, participatedFlags, totalBalance, att)
|
||||
}
|
||||
|
||||
// SetParticipationAndRewardProposer retrieves and sets the epoch participation bits in state. Based on the epoch participation, it rewards
|
||||
@@ -105,7 +110,9 @@ func SetParticipationAndRewardProposer(
|
||||
beaconState state.BeaconState,
|
||||
targetEpoch primitives.Epoch,
|
||||
indices []uint64,
|
||||
participatedFlags map[uint8]bool, totalBalance uint64) (state.BeaconState, error) {
|
||||
participatedFlags map[uint8]bool,
|
||||
totalBalance uint64,
|
||||
att ethpb.Att) (state.BeaconState, error) {
|
||||
var proposerRewardNumerator uint64
|
||||
currentEpoch := time.CurrentEpoch(beaconState)
|
||||
var stateErr error
|
||||
@@ -299,6 +306,19 @@ func AttestationParticipationFlagIndices(beaconState state.ReadOnlyBeaconState,
|
||||
participatedFlags[targetFlagIndex] = true
|
||||
}
|
||||
matchedSrcTgtHead := matchedHead && matchedSrcTgt
|
||||
|
||||
var beaconBlockRoot [32]byte
|
||||
copy(beaconBlockRoot[:], data.BeaconBlockRoot)
|
||||
matchingPayload, err := gloas.MatchingPayload(
|
||||
beaconState,
|
||||
beaconBlockRoot,
|
||||
data.Slot,
|
||||
uint64(data.CommitteeIndex),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchedSrcTgtHead = matchedSrcTgtHead && matchingPayload
|
||||
if matchedSrcTgtHead && delay == cfg.MinAttestationInclusionDelay {
|
||||
participatedFlags[headFlagIndex] = true
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package altair_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/go-bitfield"
|
||||
@@ -556,7 +558,7 @@ func TestSetParticipationAndRewardProposer(t *testing.T) {
|
||||
|
||||
b, err := helpers.TotalActiveBalance(beaconState)
|
||||
require.NoError(t, err)
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b)
|
||||
st, err := altair.SetParticipationAndRewardProposer(t.Context(), beaconState, test.epoch, test.indices, test.participatedFlags, b, ðpb.Attestation{})
|
||||
require.NoError(t, err)
|
||||
|
||||
i, err := helpers.BeaconProposerIndex(t.Context(), st)
|
||||
@@ -775,11 +777,67 @@ func TestAttestationParticipationFlagIndices(t *testing.T) {
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gloas same-slot committee index non-zero errors",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
prevRoot := bytes.Repeat([]byte{0xCC}, 32)
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, prevRoot, 0, 0)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1, // invalid for same-slot
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: nil,
|
||||
},
|
||||
{
|
||||
name: "gloas payload availability matches committee index",
|
||||
inputState: func() state.BeaconState {
|
||||
stateSlot := primitives.Slot(5)
|
||||
slot := primitives.Slot(3)
|
||||
targetRoot := bytes.Repeat([]byte{0xAA}, 32)
|
||||
headRoot := bytes.Repeat([]byte{0xBB}, 32)
|
||||
// Same prev root to make SameSlotAttestation false and use payload availability.
|
||||
return buildGloasStateForFlags(t, stateSlot, slot, targetRoot, headRoot, headRoot, 1, slot)
|
||||
}(),
|
||||
inputData: ðpb.AttestationData{
|
||||
Slot: 3,
|
||||
CommitteeIndex: 1,
|
||||
BeaconBlockRoot: bytes.Repeat([]byte{0xBB}, 32),
|
||||
Source: ðpb.Checkpoint{Root: bytes.Repeat([]byte{0xDD}, 32)},
|
||||
Target: ðpb.Checkpoint{
|
||||
Epoch: 0,
|
||||
Root: bytes.Repeat([]byte{0xAA}, 32),
|
||||
},
|
||||
},
|
||||
inputDelay: 1,
|
||||
participationIndices: map[uint8]bool{
|
||||
sourceFlagIndex: true,
|
||||
targetFlagIndex: true,
|
||||
headFlagIndex: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
flagIndices, err := altair.AttestationParticipationFlagIndices(test.inputState, test.inputData, test.inputDelay)
|
||||
if test.participationIndices == nil {
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
continue
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.DeepEqual(t, test.participationIndices, flagIndices)
|
||||
if !reflect.DeepEqual(test.participationIndices, flagIndices) {
|
||||
t.Fatalf("unexpected participation indices: got %v want %v", flagIndices, test.participationIndices)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,3 +916,61 @@ func TestMatchingStatus(t *testing.T) {
|
||||
require.Equal(t, test.matchedHead, head)
|
||||
}
|
||||
}
|
||||
|
||||
func buildGloasStateForFlags(t *testing.T, stateSlot, slot primitives.Slot, targetRoot, headRoot, prevRoot []byte, availabilityBit uint8, availabilitySlot primitives.Slot) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
blockRoots[0] = targetRoot
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = headRoot
|
||||
blockRoots[(slot-1)%cfg.SlotsPerHistoricalRoot] = prevRoot
|
||||
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
execPayloadAvailability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
idx := availabilitySlot % cfg.SlotsPerHistoricalRoot
|
||||
byteIndex := idx / 8
|
||||
bitIndex := idx % 8
|
||||
if availabilityBit == 1 {
|
||||
execPayloadAvailability[byteIndex] |= 1 << bitIndex
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xDD}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Root: checkpointRoot}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: execPayloadAvailability,
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return beaconState
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ go_test(
|
||||
"block_operations_fuzz_test.go",
|
||||
"block_regression_test.go",
|
||||
"eth1_data_test.go",
|
||||
"exit_builder_test.go",
|
||||
"exit_test.go",
|
||||
"exports_test.go",
|
||||
"genesis_test.go",
|
||||
|
||||
@@ -111,10 +111,21 @@ func VerifyAttestationNoVerifySignature(
|
||||
var indexedAtt ethpb.IndexedAtt
|
||||
|
||||
if att.Version() >= version.Electra {
|
||||
if att.GetData().CommitteeIndex != 0 {
|
||||
return errors.New("committee index must be 0 post-Electra")
|
||||
ci := att.GetData().CommitteeIndex
|
||||
// Spec v1.7.0-alpha pseudocode:
|
||||
//
|
||||
// # [Modified in Gloas:EIP7732]
|
||||
// assert data.index < 2
|
||||
//
|
||||
if beaconState.Version() >= version.Gloas {
|
||||
if ci >= 2 {
|
||||
return fmt.Errorf("incorrect committee index %d", ci)
|
||||
}
|
||||
} else {
|
||||
if ci != 0 {
|
||||
return errors.New("committee index must be 0 between Electra and Gloas forks")
|
||||
}
|
||||
}
|
||||
|
||||
aggBits := att.GetAggregationBits()
|
||||
committeeIndices := att.CommitteeBitsVal().BitIndices()
|
||||
committees := make([][]primitives.ValidatorIndex, len(committeeIndices))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
@@ -262,7 +263,7 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
|
||||
CommitteeBits: bitfield.NewBitvector64(),
|
||||
}
|
||||
err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att)
|
||||
assert.ErrorContains(t, "committee index must be 0 post-Electra", err)
|
||||
assert.ErrorContains(t, "committee index must be 0", err)
|
||||
})
|
||||
t.Run("index of committee too big", func(t *testing.T) {
|
||||
aggBits := bitfield.NewBitlist(3)
|
||||
@@ -314,6 +315,75 @@ func TestVerifyAttestationNoVerifySignature_Electra(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyAttestationNoVerifySignature_GloasCommitteeIndexLimit(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
stateSlot := cfg.MinAttestationInclusionDelay + 1
|
||||
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range blockRoots {
|
||||
blockRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
stateRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for i := range stateRoots {
|
||||
stateRoots[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
randaoMixes := make([][]byte, cfg.EpochsPerHistoricalVector)
|
||||
for i := range randaoMixes {
|
||||
randaoMixes[i] = make([]byte, fieldparams.RootLength)
|
||||
}
|
||||
|
||||
checkpointRoot := bytes.Repeat([]byte{0xAA}, fieldparams.RootLength)
|
||||
justified := ðpb.Checkpoint{Epoch: 0, Root: checkpointRoot}
|
||||
|
||||
gloasStateProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
GenesisValidatorsRoot: bytes.Repeat([]byte{0x11}, fieldparams.RootLength),
|
||||
BlockRoots: blockRoots,
|
||||
StateRoots: stateRoots,
|
||||
RandaoMixes: randaoMixes,
|
||||
ExecutionPayloadAvailability: make([]byte, cfg.SlotsPerHistoricalRoot/8),
|
||||
CurrentJustifiedCheckpoint: justified,
|
||||
PreviousJustifiedCheckpoint: justified,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: cfg.MinActivationBalance,
|
||||
WithdrawalCredentials: append([]byte{cfg.ETH1AddressWithdrawalPrefixByte}, bytes.Repeat([]byte{0x01}, 31)...),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{cfg.MinActivationBalance},
|
||||
BuilderPendingPayments: make([]*ethpb.BuilderPendingPayment, cfg.SlotsPerEpoch*2),
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x01}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
}
|
||||
|
||||
beaconState, err := state_native.InitializeFromProtoGloas(gloasStateProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
committeeBits := bitfield.NewBitvector64()
|
||||
committeeBits.SetBitAt(0, true)
|
||||
aggBits := bitfield.NewBitlist(1)
|
||||
aggBits.SetBitAt(0, true)
|
||||
|
||||
att := ðpb.AttestationElectra{
|
||||
Data: ðpb.AttestationData{
|
||||
Slot: 0,
|
||||
CommitteeIndex: 2, // invalid for Gloas (must be <2)
|
||||
BeaconBlockRoot: blockRoots[0],
|
||||
Source: justified,
|
||||
Target: justified,
|
||||
},
|
||||
AggregationBits: aggBits,
|
||||
CommitteeBits: committeeBits,
|
||||
Signature: bytes.Repeat([]byte{0x00}, fieldparams.BLSSignatureLength),
|
||||
}
|
||||
|
||||
err = blocks.VerifyAttestationNoVerifySignature(context.TODO(), beaconState, att)
|
||||
assert.ErrorContains(t, "incorrect committee index 2", err)
|
||||
}
|
||||
|
||||
func TestConvertToIndexed_OK(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
validators := make([]*ethpb.Validator, 2*params.BeaconConfig().SlotsPerEpoch)
|
||||
@@ -583,6 +653,7 @@ func TestVerifyAttestations_HandlesPlannedFork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRetrieveAttestationSignatureSet_VerifiesMultipleAttestations(t *testing.T) {
|
||||
helpers.ClearCache()
|
||||
ctx := t.Context()
|
||||
numOfValidators := uint64(params.BeaconConfig().SlotsPerEpoch.Mul(4))
|
||||
validators := make([]*ethpb.Validator, numOfValidators)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
v "github.com/OffchainLabs/prysm/v7/beacon-chain/core/validators"
|
||||
@@ -62,6 +63,16 @@ func ProcessVoluntaryExits(
|
||||
if exit == nil || exit.Exit == nil {
|
||||
return nil, errors.New("nil voluntary exit in block body")
|
||||
}
|
||||
// [New in Gloas:EIP7732] Builder exits are identified by the builder index flag.
|
||||
if beaconState.Version() >= version.Gloas && exit.Exit.ValidatorIndex.IsBuilderIndex() {
|
||||
if err := verifyBuilderExitAndSignature(beaconState, exit); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not verify builder exit %d", idx)
|
||||
}
|
||||
if err := gloas.InitiateBuilderExit(beaconState, exit.Exit.ValidatorIndex.ToBuilderIndex()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
val, err := beaconState.ValidatorAtIndexReadOnly(exit.Exit.ValidatorIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -102,19 +113,24 @@ func ProcessVoluntaryExits(
|
||||
// initiate_validator_exit(state, voluntary_exit.validator_index)
|
||||
func VerifyExitAndSignature(
|
||||
validator state.ReadOnlyValidator,
|
||||
state state.ReadOnlyBeaconState,
|
||||
st state.ReadOnlyBeaconState,
|
||||
signed *ethpb.SignedVoluntaryExit,
|
||||
) error {
|
||||
if signed == nil || signed.Exit == nil {
|
||||
return errors.New("nil exit")
|
||||
}
|
||||
|
||||
fork := state.Fork()
|
||||
genesisRoot := state.GenesisValidatorsRoot()
|
||||
// [New in Gloas:EIP7732] Builder exits are verified separately.
|
||||
if st.Version() >= version.Gloas && signed.Exit.ValidatorIndex.IsBuilderIndex() {
|
||||
return verifyBuilderExitAndSignature(st, signed)
|
||||
}
|
||||
|
||||
fork := st.Fork()
|
||||
genesisRoot := st.GenesisValidatorsRoot()
|
||||
|
||||
// EIP-7044: Beginning in Deneb, fix the fork version to Capella.
|
||||
// This allows for signed validator exits to be valid forever.
|
||||
if state.Version() >= version.Deneb {
|
||||
if st.Version() >= version.Deneb {
|
||||
fork = ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
@@ -123,7 +139,7 @@ func VerifyExitAndSignature(
|
||||
}
|
||||
|
||||
exit := signed.Exit
|
||||
if err := verifyExitConditions(state, validator, exit); err != nil {
|
||||
if err := verifyExitConditions(st, validator, exit); err != nil {
|
||||
return err
|
||||
}
|
||||
domain, err := signing.Domain(fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit, genesisRoot)
|
||||
@@ -198,3 +214,57 @@ func verifyExitConditions(st state.ReadOnlyBeaconState, validator state.ReadOnly
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyBuilderExitAndSignature validates a builder voluntary exit.
|
||||
// [New in Gloas:EIP7732]
|
||||
func verifyBuilderExitAndSignature(st state.ReadOnlyBeaconState, signed *ethpb.SignedVoluntaryExit) error {
|
||||
if signed == nil || signed.Exit == nil {
|
||||
return errors.New("nil exit")
|
||||
}
|
||||
exit := signed.Exit
|
||||
builderIndex := exit.ValidatorIndex.ToBuilderIndex()
|
||||
|
||||
// Exits must specify an epoch when they become valid; they are not valid before then.
|
||||
currentEpoch := slots.ToEpoch(st.Slot())
|
||||
if currentEpoch < exit.Epoch {
|
||||
return fmt.Errorf("expected current epoch >= exit epoch, received %d < %d", currentEpoch, exit.Epoch)
|
||||
}
|
||||
|
||||
// Verify the builder is active.
|
||||
active, err := st.IsActiveBuilder(builderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if builder is active")
|
||||
}
|
||||
if !active {
|
||||
return fmt.Errorf("builder %d is not active", builderIndex)
|
||||
}
|
||||
|
||||
// Only exit builder if it has no pending balance to withdraw.
|
||||
pendingBalance, err := st.BuilderPendingBalanceToWithdraw(builderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get builder pending balance to withdraw")
|
||||
}
|
||||
if pendingBalance != 0 {
|
||||
return fmt.Errorf("builder %d has pending balance to withdraw: %d", builderIndex, pendingBalance)
|
||||
}
|
||||
|
||||
// Verify signature using builder pubkey with Capella fork version (EIP-7044).
|
||||
pubkey, err := st.BuilderPubkey(builderIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get builder pubkey")
|
||||
}
|
||||
fork := ðpb.Fork{
|
||||
PreviousVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
CurrentVersion: params.BeaconConfig().CapellaForkVersion,
|
||||
Epoch: params.BeaconConfig().CapellaForkEpoch,
|
||||
}
|
||||
genesisRoot := st.GenesisValidatorsRoot()
|
||||
domain, err := signing.Domain(fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit, genesisRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := signing.VerifySigningRoot(exit, pubkey[:], signed.Signature, domain); err != nil {
|
||||
return signing.ErrSigFailedToVerify
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
307
beacon-chain/core/blocks/exit_builder_test.go
Normal file
307
beacon-chain/core/blocks/exit_builder_test.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package blocks_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/blocks"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/validators"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/assert"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
// setGloasTestConfig sets fork epochs so Gloas is active at epoch 5.
|
||||
func setGloasTestConfig(t *testing.T) {
|
||||
t.Helper()
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.CapellaForkEpoch = 1
|
||||
cfg.DenebForkEpoch = 2
|
||||
cfg.ElectraForkEpoch = 3
|
||||
cfg.FuluForkEpoch = 4
|
||||
cfg.GloasForkEpoch = 5
|
||||
params.SetActiveTestCleanup(t, cfg)
|
||||
}
|
||||
|
||||
// newGloasStateWithBuilder creates a minimal Gloas beacon state with one active builder
|
||||
// and returns the state along with the builder's BLS private key.
|
||||
func newGloasStateWithBuilder(t *testing.T, builderIndex primitives.BuilderIndex, epoch primitives.Epoch) (state.BeaconState, bls.SecretKey) {
|
||||
t.Helper()
|
||||
|
||||
priv, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
|
||||
builder := ðpb.Builder{
|
||||
Pubkey: priv.PublicKey().Marshal(),
|
||||
WithdrawableEpoch: cfg.FarFutureEpoch,
|
||||
DepositEpoch: 0,
|
||||
Balance: 32_000_000_000,
|
||||
ExecutionAddress: make([]byte, 20),
|
||||
}
|
||||
|
||||
builders := make([]*ethpb.Builder, int(builderIndex)+1)
|
||||
for i := range builders {
|
||||
if primitives.BuilderIndex(i) == builderIndex {
|
||||
builders[i] = builder
|
||||
} else {
|
||||
builders[i] = ðpb.Builder{
|
||||
Pubkey: make([]byte, 48),
|
||||
WithdrawableEpoch: cfg.FarFutureEpoch,
|
||||
DepositEpoch: 0,
|
||||
ExecutionAddress: make([]byte, 20),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: cfg.SlotsPerEpoch * primitives.Slot(epoch),
|
||||
Fork: ðpb.Fork{
|
||||
PreviousVersion: cfg.FuluForkVersion,
|
||||
CurrentVersion: cfg.GloasForkVersion,
|
||||
Epoch: cfg.GloasForkEpoch,
|
||||
},
|
||||
GenesisValidatorsRoot: make([]byte, 32),
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{
|
||||
Epoch: epoch - 1,
|
||||
Root: make([]byte, 32),
|
||||
},
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
Builders: builders,
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
ExitEpoch: cfg.FarFutureEpoch,
|
||||
ActivationEpoch: 0,
|
||||
PublicKey: make([]byte, 48),
|
||||
},
|
||||
},
|
||||
Balances: []uint64{32_000_000_000},
|
||||
BlockRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
|
||||
StateRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
|
||||
RandaoMixes: make([][]byte, cfg.EpochsPerHistoricalVector),
|
||||
Slashings: make([]uint64, cfg.EpochsPerSlashingsVector),
|
||||
ExecutionPayloadAvailability: make([]byte, cfg.SlotsPerHistoricalRoot/8),
|
||||
}
|
||||
|
||||
for i := range stProto.BlockRoots {
|
||||
stProto.BlockRoots[i] = make([]byte, 32)
|
||||
}
|
||||
for i := range stProto.StateRoots {
|
||||
stProto.StateRoots[i] = make([]byte, 32)
|
||||
}
|
||||
for i := range stProto.RandaoMixes {
|
||||
stProto.RandaoMixes[i] = make([]byte, 32)
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return st, priv
|
||||
}
|
||||
|
||||
func signBuilderExit(t *testing.T, st state.ReadOnlyBeaconState, exit *ethpb.VoluntaryExit, priv bls.SecretKey) *ethpb.SignedVoluntaryExit {
|
||||
t.Helper()
|
||||
|
||||
sb, err := signing.ComputeDomainAndSign(st, exit.Epoch, exit, params.BeaconConfig().DomainVoluntaryExit, priv)
|
||||
require.NoError(t, err)
|
||||
sig, err := bls.SignatureFromBytes(sb)
|
||||
require.NoError(t, err)
|
||||
|
||||
return ðpb.SignedVoluntaryExit{
|
||||
Exit: exit,
|
||||
Signature: sig.Marshal(),
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature_BuilderExit_HappyPath(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, priv := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, priv)
|
||||
|
||||
err := blocks.VerifyExitAndSignature(nil, st, signed)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature_BuilderNotActive(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, priv := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
// Make builder not active by setting withdrawable epoch (already initiated exit).
|
||||
builder, err := st.Builder(builderIndex)
|
||||
require.NoError(t, err)
|
||||
builder.WithdrawableEpoch = 5
|
||||
require.NoError(t, st.UpdateBuilderAtIndex(builderIndex, builder))
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, priv)
|
||||
|
||||
err = blocks.VerifyExitAndSignature(nil, st, signed)
|
||||
assert.ErrorContains(t, "is not active", err)
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature_BuilderPendingWithdrawal(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, priv := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
// Give the builder a pending withdrawal.
|
||||
require.NoError(t, st.AppendBuilderPendingWithdrawals([]*ethpb.BuilderPendingWithdrawal{
|
||||
{
|
||||
BuilderIndex: builderIndex,
|
||||
Amount: 1000,
|
||||
FeeRecipient: make([]byte, 20),
|
||||
},
|
||||
}))
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, priv)
|
||||
|
||||
err := blocks.VerifyExitAndSignature(nil, st, signed)
|
||||
assert.ErrorContains(t, "pending balance to withdraw", err)
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature_BuilderBadSignature(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, _ := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
wrongKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, wrongKey)
|
||||
|
||||
err = blocks.VerifyExitAndSignature(nil, st, signed)
|
||||
assert.ErrorContains(t, "signature did not verify", err)
|
||||
}
|
||||
|
||||
func TestVerifyExitAndSignature_BuilderExitInFuture(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, priv := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch + 1, // Future epoch.
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, priv)
|
||||
|
||||
err := blocks.VerifyExitAndSignature(nil, st, signed)
|
||||
assert.ErrorContains(t, "expected current epoch >= exit epoch", err)
|
||||
}
|
||||
|
||||
func TestProcessVoluntaryExits_BuilderExit(t *testing.T) {
|
||||
setGloasTestConfig(t)
|
||||
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
epoch := primitives.Epoch(10)
|
||||
st, priv := newGloasStateWithBuilder(t, builderIndex, epoch)
|
||||
|
||||
exit := ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
}
|
||||
signed := signBuilderExit(t, st, exit, priv)
|
||||
|
||||
newState, err := blocks.ProcessVoluntaryExits(t.Context(), st, []*ethpb.SignedVoluntaryExit{signed}, validators.ExitInformation(st))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify builder's withdrawable epoch was set.
|
||||
builder, err := newState.Builder(builderIndex)
|
||||
require.NoError(t, err)
|
||||
cfg := params.BeaconConfig()
|
||||
expectedWithdrawableEpoch := epoch + cfg.MinBuilderWithdrawabilityDelay
|
||||
assert.Equal(t, expectedWithdrawableEpoch, builder.WithdrawableEpoch)
|
||||
}
|
||||
|
||||
func TestProcessVoluntaryExits_BuilderExitPreGloas(t *testing.T) {
|
||||
cfg := params.BeaconConfig().Copy()
|
||||
cfg.CapellaForkEpoch = 1
|
||||
cfg.DenebForkEpoch = 2
|
||||
cfg.ElectraForkEpoch = 3
|
||||
cfg.FuluForkEpoch = 4
|
||||
cfg.GloasForkEpoch = 100 // Gloas not yet active.
|
||||
params.SetActiveTestCleanup(t, cfg)
|
||||
|
||||
epoch := primitives.Epoch(10)
|
||||
builderIndex := primitives.BuilderIndex(0)
|
||||
|
||||
stProto := ðpb.BeaconStateFulu{
|
||||
Slot: cfg.SlotsPerEpoch * primitives.Slot(epoch),
|
||||
Fork: ðpb.Fork{
|
||||
PreviousVersion: cfg.DenebForkVersion,
|
||||
CurrentVersion: cfg.FuluForkVersion,
|
||||
Epoch: cfg.FuluForkEpoch,
|
||||
},
|
||||
GenesisValidatorsRoot: make([]byte, 32),
|
||||
FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, 32)},
|
||||
Validators: []*ethpb.Validator{
|
||||
{ExitEpoch: cfg.FarFutureEpoch, ActivationEpoch: 0, PublicKey: make([]byte, 48)},
|
||||
},
|
||||
Balances: []uint64{32_000_000_000},
|
||||
BlockRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
|
||||
StateRoots: make([][]byte, cfg.SlotsPerHistoricalRoot),
|
||||
RandaoMixes: make([][]byte, cfg.EpochsPerHistoricalVector),
|
||||
Slashings: make([]uint64, cfg.EpochsPerSlashingsVector),
|
||||
}
|
||||
for i := range stProto.BlockRoots {
|
||||
stProto.BlockRoots[i] = make([]byte, 32)
|
||||
}
|
||||
for i := range stProto.StateRoots {
|
||||
stProto.StateRoots[i] = make([]byte, 32)
|
||||
}
|
||||
for i := range stProto.RandaoMixes {
|
||||
stProto.RandaoMixes[i] = make([]byte, 32)
|
||||
}
|
||||
|
||||
st, err := state_native.InitializeFromProtoUnsafeFulu(stProto)
|
||||
require.NoError(t, err)
|
||||
|
||||
signed := ðpb.SignedVoluntaryExit{
|
||||
Exit: ðpb.VoluntaryExit{
|
||||
ValidatorIndex: builderIndex.ToValidatorIndex(),
|
||||
Epoch: epoch,
|
||||
},
|
||||
Signature: make([]byte, 96),
|
||||
}
|
||||
|
||||
// On pre-Gloas state, builder-flagged exits are not routed to the builder path.
|
||||
// ProcessVoluntaryExits treats the builder-flagged index as a regular validator index,
|
||||
// which fails because no such validator exists.
|
||||
_, err = blocks.ProcessVoluntaryExits(t.Context(), st, []*ethpb.SignedVoluntaryExit{signed}, validators.ExitInformation(st))
|
||||
require.ErrorContains(t, "out of bounds", err)
|
||||
}
|
||||
@@ -2,4 +2,5 @@ package blocks
|
||||
|
||||
var ProcessBLSToExecutionChange = processBLSToExecutionChange
|
||||
var ErrInvalidBLSPrefix = errInvalidBLSPrefix
|
||||
var ErrInvalidWithdrawalCredentials = errInvalidWithdrawalCredentials
|
||||
var VerifyBlobCommitmentCount = verifyBlobCommitmentCount
|
||||
|
||||
@@ -192,11 +192,47 @@ func NewGenesisBlockForState(ctx context.Context, st state.BeaconState) (interfa
|
||||
Block: electraGenesisBlock(root),
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
})
|
||||
case *ethpb.BeaconStateGloas:
|
||||
return blocks.NewSignedBeaconBlock(ðpb.SignedBeaconBlockGloas{
|
||||
Block: gloasGenesisBlock(root),
|
||||
Signature: params.BeaconConfig().EmptySignature[:],
|
||||
})
|
||||
default:
|
||||
return nil, ErrUnrecognizedState
|
||||
}
|
||||
}
|
||||
|
||||
func gloasGenesisBlock(root [fieldparams.RootLength]byte) *ethpb.BeaconBlockGloas {
|
||||
return ðpb.BeaconBlockGloas{
|
||||
ParentRoot: params.BeaconConfig().ZeroHash[:],
|
||||
StateRoot: root[:],
|
||||
Body: ðpb.BeaconBlockBodyGloas{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncCommitteeLength/8),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
SignedExecutionPayloadBid: ðpb.SignedExecutionPayloadBid{
|
||||
Message: ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
PrevRandao: make([]byte, 32),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
BlobKzgCommitments: make([][]byte, 0),
|
||||
},
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
PayloadAttestations: make([]*ethpb.PayloadAttestation, 0),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func electraGenesisBlock(root [fieldparams.RootLength]byte) *ethpb.BeaconBlockElectra {
|
||||
return ðpb.BeaconBlockElectra{
|
||||
ParentRoot: params.BeaconConfig().ZeroHash[:],
|
||||
|
||||
@@ -147,7 +147,7 @@ func TestProcessBLSToExecutionChange(t *testing.T) {
|
||||
_, err = blocks.ValidateBLSToExecutionChange(st, signed)
|
||||
// The state should return an empty validator, even when the validator object in the registry is
|
||||
// nil. This error should return when the withdrawal credentials are invalid or too short.
|
||||
require.ErrorIs(t, err, blocks.ErrInvalidBLSPrefix)
|
||||
require.ErrorIs(t, err, blocks.ErrInvalidWithdrawalCredentials)
|
||||
})
|
||||
t.Run("non-existent validator", func(t *testing.T) {
|
||||
priv, err := bls.RandKey()
|
||||
|
||||
@@ -20,37 +20,46 @@ import (
|
||||
)
|
||||
|
||||
func TestProcessPendingDepositsMultiplesSameDeposits(t *testing.T) {
|
||||
st := stateWithActiveBalanceETH(t, 1000)
|
||||
deps := make([]*eth.PendingDeposit, 2) // Make same deposit twice
|
||||
validators := st.Validators()
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
for i := 0; i < len(deps); i += 1 {
|
||||
wc := make([]byte, 32)
|
||||
wc[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
wc[31] = byte(i)
|
||||
validators[i].PublicKey = sk.PublicKey().Marshal()
|
||||
validators[i].WithdrawalCredentials = wc
|
||||
deps[i] = stateTesting.GeneratePendingDeposit(t, sk, 32, bytesutil.ToBytes32(wc), 0)
|
||||
}
|
||||
require.NoError(t, st.SetPendingDeposits(deps))
|
||||
const (
|
||||
depositCount = uint64(2)
|
||||
amountETH = uint64(32)
|
||||
slot = 0
|
||||
activeBalanceGwei = 10_000
|
||||
)
|
||||
|
||||
err = electra.ProcessPendingDeposits(context.TODO(), st, 10000)
|
||||
state := stateWithActiveBalanceETH(t, 0)
|
||||
|
||||
secretKey, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
val := st.Validators()
|
||||
seenPubkeys := make(map[string]struct{})
|
||||
for i := 0; i < len(val); i += 1 {
|
||||
if len(val[i].PublicKey) == 0 {
|
||||
continue
|
||||
}
|
||||
_, ok := seenPubkeys[string(val[i].PublicKey)]
|
||||
if ok {
|
||||
t.Fatalf("duplicated pubkeys")
|
||||
} else {
|
||||
seenPubkeys[string(val[i].PublicKey)] = struct{}{}
|
||||
}
|
||||
withdrawalCredentialsBytes := make([]byte, 32)
|
||||
withdrawalCredentialsBytes[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
withdrawalCredentials := bytesutil.ToBytes32(withdrawalCredentialsBytes)
|
||||
|
||||
validators := state.Validators()
|
||||
require.Equal(t, 0, len(validators))
|
||||
|
||||
deposits := make([]*eth.PendingDeposit, 0, depositCount)
|
||||
for range depositCount {
|
||||
deposit := stateTesting.GeneratePendingDeposit(t, secretKey, amountETH, withdrawalCredentials, slot)
|
||||
deposits = append(deposits, deposit)
|
||||
}
|
||||
|
||||
err = state.SetPendingDeposits(deposits)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = electra.ProcessPendingDeposits(t.Context(), state, activeBalanceGwei)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The first deposit should create a new validator,
|
||||
// and the second deposit should top up the same validator
|
||||
// We should have 1 validator with balance of 64 ETH.
|
||||
validators = state.Validators()
|
||||
require.Equal(t, 1, len(validators))
|
||||
|
||||
balance, err := state.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, depositCount*amountETH, balance)
|
||||
}
|
||||
|
||||
func TestProcessPendingDeposits(t *testing.T) {
|
||||
|
||||
@@ -39,9 +39,6 @@ func ProcessEffectiveBalanceUpdates(st state.BeaconState) error {
|
||||
|
||||
// Update effective balances with hysteresis.
|
||||
validatorFunc := func(idx int, val state.ReadOnlyValidator) (newVal *ethpb.Validator, err error) {
|
||||
if val.IsNil() {
|
||||
return nil, fmt.Errorf("validator %d is nil in state", idx)
|
||||
}
|
||||
if idx >= len(bals) {
|
||||
return nil, fmt.Errorf("validator index exceeds validator length in state %d >= %d", idx, len(st.Balances()))
|
||||
}
|
||||
@@ -54,8 +51,10 @@ func ProcessEffectiveBalanceUpdates(st state.BeaconState) error {
|
||||
|
||||
if balance+downwardThreshold < val.EffectiveBalance() || val.EffectiveBalance()+upwardThreshold < balance {
|
||||
effectiveBal := min(balance-balance%effBalanceInc, effectiveBalanceLimit)
|
||||
newVal = val.Copy()
|
||||
newVal.EffectiveBalance = effectiveBal
|
||||
if effectiveBal != val.EffectiveBalance() {
|
||||
newVal = val.Copy()
|
||||
newVal.EffectiveBalance = effectiveBal
|
||||
}
|
||||
}
|
||||
return newVal, nil
|
||||
}
|
||||
|
||||
@@ -16,9 +16,6 @@ import (
|
||||
func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{
|
||||
Validators: []*eth.Validator{
|
||||
{
|
||||
WithdrawalCredentials: []byte{}, // No withdrawal credentials
|
||||
},
|
||||
{
|
||||
WithdrawalCredentials: []byte{0x01, 0xFF}, // Has withdrawal credentials
|
||||
},
|
||||
@@ -27,22 +24,19 @@ func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Balances: []uint64{
|
||||
params.BeaconConfig().MinActivationBalance,
|
||||
params.BeaconConfig().MinActivationBalance,
|
||||
params.BeaconConfig().MinActivationBalance + 100_000, // Has excess balance
|
||||
},
|
||||
})
|
||||
// Test that a validator with no withdrawal credentials cannot be switched to compounding.
|
||||
require.NoError(t, err)
|
||||
require.ErrorContains(t, "validator has no withdrawal credentials", electra.SwitchToCompoundingValidator(s, 0))
|
||||
|
||||
// Test that a validator with withdrawal credentials can be switched to compounding.
|
||||
require.NoError(t, electra.SwitchToCompoundingValidator(s, 1))
|
||||
v, err := s.ValidatorAtIndex(1)
|
||||
require.NoError(t, electra.SwitchToCompoundingValidator(s, 0))
|
||||
v, err := s.ValidatorAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, bytes.HasPrefix(v.WithdrawalCredentials, []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte}), "withdrawal credentials were not updated")
|
||||
// val_1 Balance is not changed
|
||||
b, err := s.BalanceAtIndex(1)
|
||||
// val_0 Balance is not changed
|
||||
b, err := s.BalanceAtIndex(0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b, "balance was changed")
|
||||
pbd, err := s.PendingDeposits()
|
||||
@@ -50,8 +44,8 @@ func TestSwitchToCompoundingValidator(t *testing.T) {
|
||||
require.Equal(t, 0, len(pbd), "pending balance deposits should be empty")
|
||||
|
||||
// Test that a validator with excess balance can be switched to compounding, excess balance is queued.
|
||||
require.NoError(t, electra.SwitchToCompoundingValidator(s, 2))
|
||||
b, err = s.BalanceAtIndex(2)
|
||||
require.NoError(t, electra.SwitchToCompoundingValidator(s, 1))
|
||||
b, err = s.BalanceAtIndex(1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, params.BeaconConfig().MinActivationBalance, b, "balance was not changed")
|
||||
pbd, err = s.PendingDeposits()
|
||||
|
||||
@@ -46,6 +46,9 @@ const (
|
||||
|
||||
// DataColumnReceived is sent after a data column has been seen after gossip validation rules.
|
||||
DataColumnReceived = 12
|
||||
|
||||
// PayloadAttestationMessageReceived is sent after a payload attestation message is received from gossip or rpc.
|
||||
PayloadAttestationMessageReceived = 13
|
||||
)
|
||||
|
||||
// UnAggregatedAttReceivedData is the data sent with UnaggregatedAttReceived events.
|
||||
@@ -114,3 +117,8 @@ type DataColumnReceivedData struct {
|
||||
BlockRoot [32]byte
|
||||
KzgCommitments [][]byte
|
||||
}
|
||||
|
||||
// PayloadAttestationMessageReceivedData is the data sent with PayloadAttestationMessageReceived events.
|
||||
type PayloadAttestationMessageReceivedData struct {
|
||||
Message *ethpb.PayloadAttestationMessage
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ const (
|
||||
LightClientOptimisticUpdate
|
||||
// PayloadAttributes events are fired upon a missed slot or new head.
|
||||
PayloadAttributes
|
||||
// PayloadProcessed is sent after a payload envelope has been processed.
|
||||
PayloadProcessed
|
||||
)
|
||||
|
||||
// BlockProcessedData is the data sent with BlockProcessed events.
|
||||
@@ -72,3 +74,9 @@ type InitializedData struct {
|
||||
// GenesisValidatorsRoot represents state.validators.HashTreeRoot().
|
||||
GenesisValidatorsRoot []byte
|
||||
}
|
||||
|
||||
// PayloadProcessedData is the data sent with PayloadProcessed events.
|
||||
type PayloadProcessedData struct {
|
||||
Slot primitives.Slot
|
||||
BlockRoot [32]byte
|
||||
}
|
||||
|
||||
@@ -3,18 +3,28 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"bid.go",
|
||||
"builder_exit.go",
|
||||
"deposit_request.go",
|
||||
"log.go",
|
||||
"metrics.go",
|
||||
"payload.go",
|
||||
"payload_attestation.go",
|
||||
"pending_payment.go",
|
||||
"proposer_slashing.go",
|
||||
"upgrade.go",
|
||||
"withdrawals.go",
|
||||
],
|
||||
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/requests:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types:go_default_library",
|
||||
@@ -25,26 +35,40 @@ go_library(
|
||||
"//crypto/bls/common:go_default_library",
|
||||
"//crypto/hash:go_default_library",
|
||||
"//encoding/bytesutil:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
"@com_github_sirupsen_logrus//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"attestation_test.go",
|
||||
"bid_test.go",
|
||||
"deposit_request_test.go",
|
||||
"payload_attestation_test.go",
|
||||
"payload_test.go",
|
||||
"pending_payment_test.go",
|
||||
"proposer_slashing_test.go",
|
||||
"upgrade_test.go",
|
||||
"withdrawals_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/time:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/testing:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
|
||||
52
beacon-chain/core/gloas/attestation.go
Normal file
52
beacon-chain/core/gloas/attestation.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// MatchingPayload returns true if the attestation's committee index matches the expected payload index.
|
||||
//
|
||||
// For pre-Gloas forks, this always returns true.
|
||||
//
|
||||
// Spec v1.7.0-alpha (pseudocode):
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// if is_attestation_same_slot(state, data):
|
||||
// assert data.index == 0
|
||||
// payload_matches = True
|
||||
// else:
|
||||
// slot_index = data.slot % SLOTS_PER_HISTORICAL_ROOT
|
||||
// payload_index = state.execution_payload_availability[slot_index]
|
||||
// payload_matches = data.index == payload_index
|
||||
func MatchingPayload(
|
||||
beaconState state.ReadOnlyBeaconState,
|
||||
beaconBlockRoot [32]byte,
|
||||
slot primitives.Slot,
|
||||
committeeIndex uint64,
|
||||
) (bool, error) {
|
||||
if beaconState.Version() < version.Gloas {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
sameSlot, err := beaconState.IsAttestationSameSlot(beaconBlockRoot, slot)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get same slot attestation status")
|
||||
}
|
||||
if sameSlot {
|
||||
if committeeIndex != 0 {
|
||||
return false, fmt.Errorf("committee index %d for same slot attestation must be 0", committeeIndex)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
executionPayloadAvail, err := beaconState.ExecutionPayloadAvailability(slot)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to get execution payload availability status")
|
||||
}
|
||||
return executionPayloadAvail == committeeIndex, nil
|
||||
}
|
||||
110
beacon-chain/core/gloas/attestation_test.go
Normal file
110
beacon-chain/core/gloas/attestation_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func buildStateWithBlockRoots(t *testing.T, stateSlot primitives.Slot, roots map[primitives.Slot][]byte) *state_native.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
for slot, root := range roots {
|
||||
blockRoots[slot%cfg.SlotsPerHistoricalRoot] = root
|
||||
}
|
||||
|
||||
stProto := ðpb.BeaconStateGloas{
|
||||
Slot: stateSlot,
|
||||
BlockRoots: blockRoots,
|
||||
}
|
||||
|
||||
state, err := state_native.InitializeFromProtoGloas(stProto)
|
||||
require.NoError(t, err)
|
||||
return state.(*state_native.BeaconState)
|
||||
}
|
||||
|
||||
func TestMatchingPayload(t *testing.T) {
|
||||
t.Run("pre-gloas always true", func(t *testing.T) {
|
||||
stIface, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ok, err := MatchingPayload(stIface, [32]byte{}, 0, 123)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("same slot requires committee index 0", func(t *testing.T) {
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
state := buildStateWithBlockRoots(t, 6, map[primitives.Slot][]byte{
|
||||
4: root,
|
||||
3: bytes.Repeat([]byte{0xBB}, 32),
|
||||
})
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 1)
|
||||
require.ErrorContains(t, "committee index", err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
|
||||
t.Run("same slot matches when committee index is 0", func(t *testing.T) {
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
state := buildStateWithBlockRoots(t, 6, map[primitives.Slot][]byte{
|
||||
4: root,
|
||||
3: bytes.Repeat([]byte{0xBB}, 32),
|
||||
})
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
})
|
||||
|
||||
t.Run("non same slot checks payload availability", func(t *testing.T) {
|
||||
cfg := params.BeaconConfig()
|
||||
root := bytes.Repeat([]byte{0xAA}, 32)
|
||||
blockRoots := make([][]byte, cfg.SlotsPerHistoricalRoot)
|
||||
blockRoots[4%cfg.SlotsPerHistoricalRoot] = bytes.Repeat([]byte{0xCC}, 32)
|
||||
blockRoots[3%cfg.SlotsPerHistoricalRoot] = bytes.Repeat([]byte{0xBB}, 32)
|
||||
|
||||
availability := make([]byte, cfg.SlotsPerHistoricalRoot/8)
|
||||
slotIndex := uint64(4)
|
||||
availability[slotIndex/8] = byte(1 << (slotIndex % 8))
|
||||
|
||||
stIface, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
Slot: 6,
|
||||
BlockRoots: blockRoots,
|
||||
ExecutionPayloadAvailability: availability,
|
||||
Fork: ðpb.Fork{
|
||||
CurrentVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
PreviousVersion: bytes.Repeat([]byte{0x66}, 4),
|
||||
Epoch: 0,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
state := stIface.(*state_native.BeaconState)
|
||||
require.Equal(t, version.Gloas, state.Version())
|
||||
|
||||
var rootArr [32]byte
|
||||
copy(rootArr[:], root)
|
||||
|
||||
ok, err := MatchingPayload(state, rootArr, 4, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
ok, err = MatchingPayload(state, rootArr, 4, 0)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, ok)
|
||||
})
|
||||
}
|
||||
@@ -17,27 +17,56 @@ import (
|
||||
)
|
||||
|
||||
// ProcessExecutionPayloadBid processes a signed execution payload bid in the Gloas fork.
|
||||
// Spec v1.7.0-alpha.0 (pseudocode):
|
||||
// process_execution_payload_bid(state: BeaconState, block: BeaconBlock):
|
||||
//
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
// assert bid.slot == block.slot
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
// if amount > 0:
|
||||
// state.builder_pending_payments[...] = BuilderPendingPayment(weight=0, withdrawal=BuilderPendingWithdrawal(fee_recipient=bid.fee_recipient, amount=amount, builder_index=builder_index))
|
||||
// state.latest_execution_payload_bid = bid
|
||||
// <spec fn="process_execution_payload_bid" fork="gloas" hash="823c9f3a">
|
||||
// def process_execution_payload_bid(state: BeaconState, block: BeaconBlock) -> None:
|
||||
// signed_bid = block.body.signed_execution_payload_bid
|
||||
// bid = signed_bid.message
|
||||
// builder_index = bid.builder_index
|
||||
// amount = bid.value
|
||||
//
|
||||
// # For self-builds, amount must be zero regardless of withdrawal credential prefix
|
||||
// if builder_index == BUILDER_INDEX_SELF_BUILD:
|
||||
// assert amount == 0
|
||||
// assert signed_bid.signature == bls.G2_POINT_AT_INFINITY
|
||||
// else:
|
||||
// # Verify that the builder is active
|
||||
// assert is_active_builder(state, builder_index)
|
||||
// # Verify that the builder has funds to cover the bid
|
||||
// assert can_builder_cover_bid(state, builder_index, amount)
|
||||
// # Verify that the bid signature is valid
|
||||
// assert verify_execution_payload_bid_signature(state, signed_bid)
|
||||
//
|
||||
// # Verify commitments are under limit
|
||||
// assert (
|
||||
// len(bid.blob_kzg_commitments)
|
||||
// <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block
|
||||
// )
|
||||
//
|
||||
// # Verify that the bid is for the current slot
|
||||
// assert bid.slot == block.slot
|
||||
// # Verify that the bid is for the right parent block
|
||||
// assert bid.parent_block_hash == state.latest_block_hash
|
||||
// assert bid.parent_block_root == block.parent_root
|
||||
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state))
|
||||
//
|
||||
// # Record the pending payment if there is some payment
|
||||
// if amount > 0:
|
||||
// pending_payment = BuilderPendingPayment(
|
||||
// weight=0,
|
||||
// withdrawal=BuilderPendingWithdrawal(
|
||||
// fee_recipient=bid.fee_recipient,
|
||||
// amount=amount,
|
||||
// builder_index=builder_index,
|
||||
// ),
|
||||
// )
|
||||
// state.builder_pending_payments[SLOTS_PER_EPOCH + bid.slot % SLOTS_PER_EPOCH] = (
|
||||
// pending_payment
|
||||
// )
|
||||
//
|
||||
// # Cache the signed execution payload bid
|
||||
// state.latest_execution_payload_bid = bid
|
||||
// </spec>
|
||||
func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
|
||||
signedBid, err := block.Body().SignedExecutionPayloadBid()
|
||||
if err != nil {
|
||||
@@ -81,11 +110,17 @@ func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyB
|
||||
return fmt.Errorf("builder %d cannot cover bid amount %d", builderIndex, amount)
|
||||
}
|
||||
|
||||
if err := validatePayloadBidSignature(st, wrappedBid); err != nil {
|
||||
if err := ValidatePayloadBidSignature(st, wrappedBid); err != nil {
|
||||
return errors.Wrap(err, "bid signature validation failed")
|
||||
}
|
||||
}
|
||||
|
||||
maxBlobsPerBlock := params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(block.Slot()))
|
||||
commitmentCount := bid.BlobKzgCommitmentCount()
|
||||
if commitmentCount > uint64(maxBlobsPerBlock) {
|
||||
return fmt.Errorf("bid has %d blob KZG commitments over max %d", commitmentCount, maxBlobsPerBlock)
|
||||
}
|
||||
|
||||
if err := validateBidConsistency(st, bid, block); err != nil {
|
||||
return errors.Wrap(err, "bid consistency validation failed")
|
||||
}
|
||||
@@ -144,10 +179,10 @@ func validateBidConsistency(st state.BeaconState, bid interfaces.ROExecutionPayl
|
||||
return nil
|
||||
}
|
||||
|
||||
// validatePayloadBidSignature verifies the BLS signature on a signed execution payload bid.
|
||||
// ValidatePayloadBidSignature verifies the BLS signature on a signed execution payload bid.
|
||||
// It validates that the signature was created by the builder specified in the bid
|
||||
// using the appropriate domain for the beacon builder.
|
||||
func validatePayloadBidSignature(st state.ReadOnlyBeaconState, signedBid interfaces.ROSignedExecutionPayloadBid) error {
|
||||
func ValidatePayloadBidSignature(st state.ReadOnlyBeaconState, signedBid interfaces.ROSignedExecutionPayloadBid) error {
|
||||
bid, err := signedBid.Bid()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get bid")
|
||||
|
||||
@@ -184,6 +184,28 @@ func signBid(t *testing.T, sk common.SecretKey, bid *ethpb.ExecutionPayloadBid,
|
||||
return out
|
||||
}
|
||||
|
||||
func blobCommitmentsForSlot(slot primitives.Slot, count int) [][]byte {
|
||||
max := int(params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(slot)))
|
||||
if count > max {
|
||||
count = max
|
||||
}
|
||||
commitments := make([][]byte, count)
|
||||
for i := range commitments {
|
||||
commitments[i] = bytes.Repeat([]byte{0xEE}, 48)
|
||||
}
|
||||
return commitments
|
||||
}
|
||||
|
||||
func tooManyBlobCommitmentsForSlot(slot primitives.Slot) [][]byte {
|
||||
max := int(params.BeaconConfig().MaxBlobsPerBlockAtEpoch(slots.ToEpoch(slot)))
|
||||
count := max + 1
|
||||
commitments := make([][]byte, count)
|
||||
for i := range commitments {
|
||||
commitments[i] = bytes.Repeat([]byte{0xEE}, 48)
|
||||
}
|
||||
return commitments
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
|
||||
slot := primitives.Slot(12)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
@@ -194,17 +216,17 @@ func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 0,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
@@ -236,16 +258,16 @@ func TestProcessExecutionPayloadBid_SelfBuildNonZeroAmountFails(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, [48]byte{})
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
@@ -280,17 +302,17 @@ func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 500_000,
|
||||
ExecutionPayment: 1,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 500_000,
|
||||
ExecutionPayment: 1,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
@@ -341,17 +363,17 @@ func TestProcessExecutionPayloadBid_BuilderNotActive(t *testing.T) {
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x04}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x05}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x04}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -394,17 +416,17 @@ func TestProcessExecutionPayloadBid_CannotCoverBid(t *testing.T) {
|
||||
state = stateIface.(*state_native.BeaconState)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 25,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 25,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -436,17 +458,17 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 10,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
// Use an invalid signature.
|
||||
invalidSig := [96]byte{1}
|
||||
@@ -463,6 +485,42 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
|
||||
require.ErrorContains(t, "bid signature validation failed", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_TooManyBlobCommitments(t *testing.T) {
|
||||
slot := primitives.Slot(9)
|
||||
proposerIdx := primitives.ValidatorIndex(0)
|
||||
builderIdx := params.BeaconConfig().BuilderIndexSelfBuild
|
||||
randao := [32]byte(bytes.Repeat([]byte{0xAA}, 32))
|
||||
latestHash := [32]byte(bytes.Repeat([]byte{0xBB}, 32))
|
||||
pubKey := [48]byte{}
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xDD}, 32),
|
||||
PrevRandao: randao[:],
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
BlobKzgCommitments: tooManyBlobCommitmentsForSlot(slot),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
|
||||
}
|
||||
signed := ðpb.SignedExecutionPayloadBid{
|
||||
Message: bid,
|
||||
Signature: common.InfiniteSignature[:],
|
||||
}
|
||||
|
||||
block := stubBlock{
|
||||
slot: slot,
|
||||
proposer: proposerIdx,
|
||||
parentRoot: bytesutil.ToBytes32(bid.ParentBlockRoot),
|
||||
body: stubBlockBody{signedBid: signed},
|
||||
v: version.Gloas,
|
||||
}
|
||||
|
||||
err := ProcessExecutionPayloadBid(state, block)
|
||||
require.ErrorContains(t, "blob KZG commitments over max", err)
|
||||
}
|
||||
|
||||
func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
|
||||
slot := primitives.Slot(10)
|
||||
builderIdx := primitives.BuilderIndex(1)
|
||||
@@ -478,17 +536,17 @@ func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot + 1, // mismatch
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0xBB}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot + 1, // mismatch
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -520,17 +578,17 @@ func TestProcessExecutionPayloadBid_ParentHashMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -563,17 +621,17 @@ func TestProcessExecutionPayloadBid_ParentRootMismatch(t *testing.T) {
|
||||
|
||||
parentRoot := bytes.Repeat([]byte{0x22}, 32)
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: parentRoot,
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: parentRoot,
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: randao[:],
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
@@ -605,17 +663,17 @@ func TestProcessExecutionPayloadBid_PrevRandaoMismatch(t *testing.T) {
|
||||
state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
|
||||
|
||||
bid := ðpb.ExecutionPayloadBid{
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
ParentBlockHash: latestHash[:],
|
||||
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
|
||||
BlockHash: bytes.Repeat([]byte{0x33}, 32),
|
||||
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
|
||||
GasLimit: 1,
|
||||
BuilderIndex: builderIdx,
|
||||
Slot: slot,
|
||||
Value: 1,
|
||||
ExecutionPayment: 0,
|
||||
BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
|
||||
FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
|
||||
}
|
||||
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
|
||||
sig := signBid(t, sk, bid, state.Fork(), genesis)
|
||||
|
||||
37
beacon-chain/core/gloas/builder_exit.go
Normal file
37
beacon-chain/core/gloas/builder_exit.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
|
||||
"github.com/OffchainLabs/prysm/v7/time/slots"
|
||||
)
|
||||
|
||||
// InitiateBuilderExit initiates the exit of a builder by setting its withdrawable epoch.
|
||||
//
|
||||
// <spec fn="initiate_builder_exit" fork="gloas" hash="3da938d5">
|
||||
// def initiate_builder_exit(state: BeaconState, builder_index: BuilderIndex) -> None:
|
||||
// """
|
||||
// Initiate the exit of the builder with index ``index``.
|
||||
// """
|
||||
// # Return if builder already initiated exit
|
||||
// builder = state.builders[builder_index]
|
||||
// if builder.withdrawable_epoch != FAR_FUTURE_EPOCH:
|
||||
// return
|
||||
//
|
||||
// # Set builder exit epoch
|
||||
// builder.withdrawable_epoch = get_current_epoch(state) + MIN_BUILDER_WITHDRAWABILITY_DELAY
|
||||
// </spec>
|
||||
func InitiateBuilderExit(s state.BeaconState, builderIndex primitives.BuilderIndex) error {
|
||||
builder, err := s.Builder(builderIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Return if builder already initiated exit.
|
||||
if builder.WithdrawableEpoch != params.BeaconConfig().FarFutureEpoch {
|
||||
return nil
|
||||
}
|
||||
currentEpoch := slots.ToEpoch(s.Slot())
|
||||
builder.WithdrawableEpoch = currentEpoch + params.BeaconConfig().MinBuilderWithdrawabilityDelay
|
||||
return s.UpdateBuilderAtIndex(builderIndex, builder)
|
||||
}
|
||||
185
beacon-chain/core/gloas/deposit_request.go
Normal file
185
beacon-chain/core/gloas/deposit_request.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/runtime/version"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func processDepositRequests(ctx context.Context, beaconState state.BeaconState, requests []*enginev1.DepositRequest) error {
|
||||
if len(requests) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, receipt := range requests {
|
||||
if err := processDepositRequest(beaconState, receipt); err != nil {
|
||||
return errors.Wrap(err, "could not apply deposit request")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// processDepositRequest processes the specific deposit request
|
||||
//
|
||||
// <spec fn="process_deposit_request" fork="gloas" hash="0e8b94ab">
|
||||
// def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
|
||||
// # [New in Gloas:EIP7732]
|
||||
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||
// validator_pubkeys = [v.pubkey for v in state.validators]
|
||||
//
|
||||
// # [New in Gloas:EIP7732]
|
||||
// # Regardless of the withdrawal credentials prefix, if a builder/validator
|
||||
// # already exists with this pubkey, apply the deposit to their balance
|
||||
// is_builder = deposit_request.pubkey in builder_pubkeys
|
||||
// is_validator = deposit_request.pubkey in validator_pubkeys
|
||||
// if is_builder or (
|
||||
// is_builder_withdrawal_credential(deposit_request.withdrawal_credentials)
|
||||
// and not is_validator
|
||||
// and not is_pending_validator(state, deposit_request.pubkey)
|
||||
// ):
|
||||
// # Apply builder deposits immediately
|
||||
// apply_deposit_for_builder(
|
||||
// state,
|
||||
// deposit_request.pubkey,
|
||||
// deposit_request.withdrawal_credentials,
|
||||
// deposit_request.amount,
|
||||
// deposit_request.signature,
|
||||
// state.slot,
|
||||
// )
|
||||
// return
|
||||
//
|
||||
// # Add validator deposits to the queue
|
||||
// state.pending_deposits.append(
|
||||
// PendingDeposit(
|
||||
// pubkey=deposit_request.pubkey,
|
||||
// withdrawal_credentials=deposit_request.withdrawal_credentials,
|
||||
// amount=deposit_request.amount,
|
||||
// signature=deposit_request.signature,
|
||||
// slot=state.slot,
|
||||
// )
|
||||
// )
|
||||
// </spec>
|
||||
func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) error {
|
||||
if request == nil {
|
||||
return errors.New("nil deposit request")
|
||||
}
|
||||
|
||||
applied, err := applyBuilderDepositRequest(beaconState, request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not apply builder deposit")
|
||||
}
|
||||
if applied {
|
||||
builderDepositsProcessedTotal.Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{
|
||||
PublicKey: request.Pubkey,
|
||||
WithdrawalCredentials: request.WithdrawalCredentials,
|
||||
Amount: request.Amount,
|
||||
Signature: request.Signature,
|
||||
Slot: beaconState.Slot(),
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "could not append deposit request")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// <spec fn="apply_deposit_for_builder" fork="gloas" hash="e4bc98c7">
|
||||
// def apply_deposit_for_builder(
|
||||
//
|
||||
// state: BeaconState,
|
||||
// pubkey: BLSPubkey,
|
||||
// withdrawal_credentials: Bytes32,
|
||||
// amount: uint64,
|
||||
// signature: BLSSignature,
|
||||
// slot: Slot,
|
||||
//
|
||||
// ) -> None:
|
||||
//
|
||||
// builder_pubkeys = [b.pubkey for b in state.builders]
|
||||
// if pubkey not in builder_pubkeys:
|
||||
// # Verify the deposit signature (proof of possession) which is not checked by the deposit contract
|
||||
// if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
|
||||
// add_builder_to_registry(state, pubkey, withdrawal_credentials, amount, slot)
|
||||
// else:
|
||||
// # Increase balance by deposit amount
|
||||
// builder_index = builder_pubkeys.index(pubkey)
|
||||
// state.builders[builder_index].balance += amount
|
||||
//
|
||||
// </spec>
|
||||
func applyBuilderDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) (bool, error) {
|
||||
if beaconState.Version() < version.Gloas {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
pubkey := bytesutil.ToBytes48(request.Pubkey)
|
||||
idx, isBuilder := beaconState.BuilderIndexByPubkey(pubkey)
|
||||
if isBuilder {
|
||||
if err := beaconState.IncreaseBuilderBalance(idx, request.Amount); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isBuilderPrefix := helpers.IsBuilderWithdrawalCredential(request.WithdrawalCredentials)
|
||||
_, isValidator := beaconState.ValidatorIndexByPubkey(pubkey)
|
||||
if !isBuilderPrefix || isValidator {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
isPending, err := beaconState.IsPendingValidator(request.Pubkey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if isPending {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := applyDepositForNewBuilder(
|
||||
beaconState,
|
||||
request.Pubkey,
|
||||
request.WithdrawalCredentials,
|
||||
request.Amount,
|
||||
request.Signature,
|
||||
); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func applyDepositForNewBuilder(
|
||||
beaconState state.BeaconState,
|
||||
pubkey []byte,
|
||||
withdrawalCredentials []byte,
|
||||
amount uint64,
|
||||
signature []byte,
|
||||
) error {
|
||||
pubkeyBytes := bytesutil.ToBytes48(pubkey)
|
||||
valid, err := helpers.IsValidDepositSignature(ðpb.Deposit_Data{
|
||||
PublicKey: pubkey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
Amount: amount,
|
||||
Signature: signature,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not verify deposit signature")
|
||||
}
|
||||
if !valid {
|
||||
log.WithFields(logrus.Fields{
|
||||
"pubkey": fmt.Sprintf("%x", pubkey),
|
||||
}).Warn("ignoring builder deposit: invalid signature")
|
||||
return nil
|
||||
}
|
||||
|
||||
withdrawalCredBytes := bytesutil.ToBytes32(withdrawalCredentials)
|
||||
return beaconState.AddBuilderFromDeposit(pubkeyBytes, withdrawalCredBytes, amount)
|
||||
}
|
||||
177
beacon-chain/core/gloas/deposit_request_test.go
Normal file
177
beacon-chain/core/gloas/deposit_request_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package gloas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
|
||||
state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
|
||||
stateTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/state/testing"
|
||||
"github.com/OffchainLabs/prysm/v7/config/params"
|
||||
"github.com/OffchainLabs/prysm/v7/crypto/bls"
|
||||
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
|
||||
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
|
||||
"github.com/OffchainLabs/prysm/v7/testing/require"
|
||||
)
|
||||
|
||||
func TestProcessDepositRequests_EmptyAndNil(t *testing.T) {
|
||||
st := newGloasState(t, nil, nil)
|
||||
|
||||
t.Run("empty requests continues", func(t *testing.T) {
|
||||
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("nil request errors", func(t *testing.T) {
|
||||
err := processDepositRequests(t.Context(), st, []*enginev1.DepositRequest{nil})
|
||||
require.ErrorContains(t, "nil deposit request", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProcessDepositRequest_BuilderDepositAddsBuilder(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cred := builderWithdrawalCredentials()
|
||||
pd := stateTesting.GeneratePendingDeposit(t, sk, 1234, cred, 0)
|
||||
req := depositRequestFromPending(pd, 1)
|
||||
|
||||
st := newGloasState(t, nil, nil)
|
||||
err = processDepositRequest(st, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
idx, ok := st.BuilderIndexByPubkey(toBytes48(req.Pubkey))
|
||||
require.Equal(t, true, ok)
|
||||
|
||||
builder, err := st.Builder(idx)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, builder)
|
||||
require.DeepEqual(t, req.Pubkey, builder.Pubkey)
|
||||
require.DeepEqual(t, []byte{cred[0]}, builder.Version)
|
||||
require.DeepEqual(t, cred[12:], builder.ExecutionAddress)
|
||||
require.Equal(t, uint64(1234), uint64(builder.Balance))
|
||||
require.Equal(t, params.BeaconConfig().FarFutureEpoch, builder.WithdrawableEpoch)
|
||||
|
||||
pending, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pending))
|
||||
}
|
||||
|
||||
func TestProcessDepositRequest_ExistingBuilderIncreasesBalance(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
pubkey := sk.PublicKey().Marshal()
|
||||
builders := []*ethpb.Builder{
|
||||
{
|
||||
Pubkey: pubkey,
|
||||
Version: []byte{0},
|
||||
ExecutionAddress: bytes.Repeat([]byte{0x11}, 20),
|
||||
Balance: 5,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
},
|
||||
}
|
||||
st := newGloasState(t, nil, builders)
|
||||
|
||||
cred := validatorWithdrawalCredentials()
|
||||
pd := stateTesting.GeneratePendingDeposit(t, sk, 200, cred, 0)
|
||||
req := depositRequestFromPending(pd, 9)
|
||||
|
||||
err = processDepositRequest(st, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
idx, ok := st.BuilderIndexByPubkey(toBytes48(pubkey))
|
||||
require.Equal(t, true, ok)
|
||||
builder, err := st.Builder(idx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(205), uint64(builder.Balance))
|
||||
|
||||
pending, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(pending))
|
||||
}
|
||||
|
||||
func TestProcessDepositRequest_BuilderDepositWithExistingPendingDepositStaysPending(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
validatorCred := validatorWithdrawalCredentials()
|
||||
builderCred := builderWithdrawalCredentials()
|
||||
existingPending := stateTesting.GeneratePendingDeposit(t, sk, 1234, validatorCred, 0)
|
||||
req := depositRequestFromPending(stateTesting.GeneratePendingDeposit(t, sk, 200, builderCred, 1), 9)
|
||||
|
||||
st := newGloasState(t, nil, nil)
|
||||
require.NoError(t, st.SetPendingDeposits([]*ethpb.PendingDeposit{existingPending}))
|
||||
|
||||
err = processDepositRequest(st, req)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := st.BuilderIndexByPubkey(toBytes48(req.Pubkey))
|
||||
require.Equal(t, false, ok)
|
||||
|
||||
pending, err := st.PendingDeposits()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(pending))
|
||||
require.DeepEqual(t, existingPending.PublicKey, pending[0].PublicKey)
|
||||
require.DeepEqual(t, req.Pubkey, pending[1].PublicKey)
|
||||
require.DeepEqual(t, req.WithdrawalCredentials, pending[1].WithdrawalCredentials)
|
||||
require.Equal(t, req.Amount, pending[1].Amount)
|
||||
}
|
||||
|
||||
func TestApplyDepositForBuilder_InvalidSignatureIgnoresDeposit(t *testing.T) {
|
||||
sk, err := bls.RandKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cred := builderWithdrawalCredentials()
|
||||
st := newGloasState(t, nil, nil)
|
||||
err = applyDepositForNewBuilder(st, sk.PublicKey().Marshal(), cred[:], 100, make([]byte, 96))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := st.BuilderIndexByPubkey(toBytes48(sk.PublicKey().Marshal()))
|
||||
require.Equal(t, false, ok)
|
||||
}
|
||||
|
||||
func newGloasState(t *testing.T, validators []*ethpb.Validator, builders []*ethpb.Builder) state.BeaconState {
|
||||
t.Helper()
|
||||
|
||||
st, err := state_native.InitializeFromProtoGloas(ðpb.BeaconStateGloas{
|
||||
DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex,
|
||||
Validators: validators,
|
||||
Balances: make([]uint64, len(validators)),
|
||||
PendingDeposits: []*ethpb.PendingDeposit{},
|
||||
Builders: builders,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
func depositRequestFromPending(pd *ethpb.PendingDeposit, index uint64) *enginev1.DepositRequest {
|
||||
return &enginev1.DepositRequest{
|
||||
Pubkey: pd.PublicKey,
|
||||
WithdrawalCredentials: pd.WithdrawalCredentials,
|
||||
Amount: pd.Amount,
|
||||
Signature: pd.Signature,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func builderWithdrawalCredentials() [32]byte {
|
||||
var cred [32]byte
|
||||
cred[0] = params.BeaconConfig().BuilderWithdrawalPrefixByte
|
||||
copy(cred[12:], bytes.Repeat([]byte{0x22}, 20))
|
||||
return cred
|
||||
}
|
||||
|
||||
func validatorWithdrawalCredentials() [32]byte {
|
||||
var cred [32]byte
|
||||
cred[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
|
||||
copy(cred[12:], bytes.Repeat([]byte{0x33}, 20))
|
||||
return cred
|
||||
}
|
||||
|
||||
func toBytes48(b []byte) [48]byte {
|
||||
var out [48]byte
|
||||
copy(out[:], b)
|
||||
return out
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user