Compare commits

...

24 Commits

Author SHA1 Message Date
Preston Van Loon
373c9ef7c6 fix stategen fallback when hdiff root lookup misses 2026-02-11 11:51:20 -06:00
Preston Van Loon
f9cf80feaf harden hdiff state lookups with summary and state-root checks 2026-02-11 11:51:20 -06:00
Preston Van Loon
2ec87e23fb fix hdiff diff-chain reconstruction for non-zero offset 2026-02-11 11:51:20 -06:00
Preston Van Loon
03156649e8 hdiff restart-support: validate on startup by default 2026-02-11 11:49:37 -06:00
Preston Van Loon
982c3f8fb7 hdiff restart-support: validate cache coherency 2026-02-11 11:49:37 -06:00
Preston Van Loon
fc51fb28d6 hdiff restart-support: add error types and startup handling 2026-02-11 11:49:37 -06:00
Preston Van Loon
cee0b57601 hdiff restart-support: rehydrate cache on restart 2026-02-11 11:49:37 -06:00
Preston Van Loon
d518377588 hdiff restart-support: populate cache from DB 2026-02-11 11:49:37 -06:00
Preston Van Loon
bb7b4ba0ad hdiff restart-support: add offset loader 2026-02-11 11:49:37 -06:00
Preston Van Loon
25b3ee468f hdiff restart-support: persist and validate exponents metadata 2026-02-11 11:49:37 -06:00
Preston Van Loon
7df09b12a5 hdiff restart-support: add exponents encoding helpers 2026-02-11 11:49:37 -06:00
james-prysm
eaf3aa3e8e simplify get and post block parsing for REST (#16307)
**What type of PR is this?**

 Feature


**What does this PR do? Why is it needed?**

PR is attempts to remove code duplication and process through a map of
configurations for get and post block apis. this should simplify
maintainability.

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-02-10 18:28:55 +00:00
terence
9c49bb484c Add gossip for payload attestation (#16333)
This PR implements gossip validation and subscription for payload
attestation
2026-02-10 16:17:22 +00:00
terence
bb0f70ad60 gloas: add read only wrapper for payload envelope (#16339)
This PR adds read only wrapper for execution payload envelope
2026-02-09 17:23:12 +00:00
satushh
dc66f8872d Close libp2p host (#16313)
**What type of PR is this?**

 Other

**What does this PR do? Why is it needed?**

Close host to save resource

**Which issues(s) does this PR fix?**

Fixes #

**Other notes for review**

**Acknowledgements**

- [ ] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [ ] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [ ] I have added a description with sufficient context for reviewers
to understand this PR.
- [ ] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).

---------

Co-authored-by: Bastin <43618253+Inspector-Butters@users.noreply.github.com>
2026-02-09 14:40:07 +00:00
Preston Van Loon
db2bb5505c db: Copy byte slices that live outside of the view transaction (#16332)
**What type of PR is this?**

Bug fix

**What does this PR do? Why is it needed?**

The bbolt documentation suggests that the byte slices used during the
View transaction should not be used outside of the transaction and
mutating those slices could break things.

**Which issues(s) does this PR fix?**

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-02-07 14:10:07 +00:00
terence
14f01bbc6c gloas: move kzg commitments to bid (#16309)
This PR moves kzg commitments to bid. The rationale behind is captured
in this [issue](https://github.com/ethereum/consensus-specs/issues/4870)
* Moves blob KZG commitments to the earlier point where builder intent
is known
* Removes duplicated commitments from data column sidecars which saves
descent b/w per slot
* Enables nodes to start fetching blobs via getBlobs as soon as the bid
is received
* Slightly increases bid size and may add minor bidding channel latency
but the tradeoff favors lower network load and simpler DA handling
2026-02-06 23:07:54 +00:00
Potuz
c3e74e4a5d Remove unused method in forkchoice (#16337) 2026-02-06 19:21:58 +00:00
Potuz
e7ae6a004b Remove unused method in forkchoice (#16331) 2026-02-06 15:30:50 +00:00
Bastin
862fb2eb4a Fix gen-logs.sh - gitignore bug (#16328)
**What does this PR do? Why is it needed?**
`gen-logs.sh` was skipping `cmd/beacon-chain/execution/` due to a rule
in `.gitignore`.
Added a fix in `gen-logs.sh` to ignore `.gitignore` entries by
specification.
2026-02-05 14:06:42 +00:00
Potuz
bb80a9c832 Remove unused map in forkchoice (#16329)
nodeByPayload was not being used.
2026-02-05 13:25:06 +00:00
Bastin
c1b668a50a Fix logging issue (#16322)
**What does this PR do? Why is it needed?**
This PR, in an attempt to fix the logging issue described in #16314,
does the following:
- Adds a new field `Identifier` to the `WriterHook` struct, and filters
out log entries that have the key `log_target` and the value of the
hook's `Identifier`. For now the identifiers are `ephemeral` and `user`,
differentiating between the user facing terminal/log file, and the
debugger facing ephemeral log file.
- Stores the value of the `--verbosity` and `--log.vmodule` flags in
`io/logs`, so it can be accessed by packages that need to know the
verbosity they're logging with. (note that since #16272 each package can
have a different verbosity, so verbosity is now defined per package
instead of globally)
- Improves the calculation of the global logging level by ignoring the
`ephemeralLogFileVerbosity` when the `--disable-ephemeral-log-file` flag
is enabled.
- Uses these added logic to fix the problem in
`logStateTransitionData()` (described in #16314)

Note: since we're saving this new data in `io/logs`, we should refactor
`prefixFormatter` to read the data from here. but that creates a
circular import error. I will try to fix this and refactor the formatter
in a future PR.

---------

Co-authored-by: Manu NALEPA <enalepa@offchainlabs.com>
2026-02-05 10:24:50 +00:00
Justin Traglia
fab687d96d Improve ethspecify integration (#16304)
**What type of PR is this?**

Documentation

**What does this PR do? Why is it needed?**

* Move the ethspecify config from `/specrefs/.ethspecify` to
`/.ethspecify`.
* This allows developers to use inline specrefs (eg spec functions in
godoc comments).
* To do this, simply add a spec tag and run `ethspecify` to populate it.
* Clean up specref exceptions; organize by upgrade & put items in the
correct section.
* Update a few godoc comments to use the new inline specref feature.
* Update check-specrefs GitHub action so that it enforces up-to-date
godocs.
* Standardize specref naming; requiring a `#fork` tag for everything.
* Add new specrefs (which haven't been implemented yet) which were
missing.

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2026-02-04 18:44:01 +00:00
james-prysm
cf94ccbf72 node fallback cleanup (#16316)
**What type of PR is this?**

 Other

**What does this PR do? Why is it needed?**

Follow up to https://github.com/OffchainLabs/prysm/pull/16215 this pr
improves logging, fixes stuttering in package naming, adds additional
unit tests, and deduplicates fallback node code.

**Which issues(s) does this PR fix?**

fixes a potential race if reconnecting to the same host very quickly
which has a stale connection still.

**Other notes for review**

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
2026-02-04 15:59:42 +00:00
209 changed files with 9200 additions and 3768 deletions

View File

@@ -1,25 +1,39 @@
version: v1.7.0-alpha.1 version: v1.7.0-alpha.2
style: full style: full
specrefs: specrefs:
search_root: .. search_root: .
auto_standardize_names: true
auto_add_missing_entries: true
require_exceptions_have_fork: true
files: files:
- configs.yml - specrefs/configs.yml
- constants.yml - specrefs/constants.yml
- containers.yml - specrefs/containers.yml
- dataclasses.yml - specrefs/dataclasses.yml
- functions.yml - specrefs/functions.yml
- presets.yml - specrefs/presets.yml
exceptions: exceptions:
presets: presets:
# Not implemented: gloas (future fork) # gloas
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas - BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
- MAX_PAYLOAD_ATTESTATIONS#gloas - MAX_PAYLOAD_ATTESTATIONS#gloas
- PTC_SIZE#gloas - PTC_SIZE#gloas
constants: constants:
# Constants in the KZG library # 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 - BLS_MODULUS#deneb
- BYTES_PER_COMMITMENT#deneb - BYTES_PER_COMMITMENT#deneb
- BYTES_PER_FIELD_ELEMENT#deneb - BYTES_PER_FIELD_ELEMENT#deneb
@@ -33,18 +47,9 @@ exceptions:
- PRIMITIVE_ROOT_OF_UNITY#deneb - PRIMITIVE_ROOT_OF_UNITY#deneb
- RANDOM_CHALLENGE_KZG_BATCH_DOMAIN#deneb - RANDOM_CHALLENGE_KZG_BATCH_DOMAIN#deneb
- RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN#fulu - RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN#fulu
# fulu
# Not implemented
- BASIS_POINTS#phase0
- ENDIANNESS#phase0
- MAX_CONCURRENT_REQUESTS#phase0
- PARTICIPATION_FLAG_WEIGHTS#altair
- SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY#bellatrix
- UINT256_MAX#fulu - UINT256_MAX#fulu
- UINT64_MAX#phase0 # gloas
- UINT64_MAX_SQRT#phase0
# Not implemented: gloas (future fork)
- BUILDER_PAYMENT_THRESHOLD_DENOMINATOR#gloas - BUILDER_PAYMENT_THRESHOLD_DENOMINATOR#gloas
- BUILDER_PAYMENT_THRESHOLD_NUMERATOR#gloas - BUILDER_PAYMENT_THRESHOLD_NUMERATOR#gloas
- BUILDER_WITHDRAWAL_PREFIX#gloas - BUILDER_WITHDRAWAL_PREFIX#gloas
@@ -61,61 +66,62 @@ exceptions:
- PTC_TIMELINESS_INDEX#gloas - PTC_TIMELINESS_INDEX#gloas
configs: configs:
# Not implemented: gloas (future fork) # gloas
- AGGREGATE_DUE_BPS_GLOAS#gloas - AGGREGATE_DUE_BPS_GLOAS#gloas
- ATTESTATION_DUE_BPS_GLOAS#gloas - ATTESTATION_DUE_BPS_GLOAS#gloas
- CONTRIBUTION_DUE_BPS_GLOAS#gloas - CONTRIBUTION_DUE_BPS_GLOAS#gloas
- GLOAS_FORK_EPOCH#gloas - GLOAS_FORK_EPOCH#gloas
- GLOAS_FORK_VERSION#gloas - GLOAS_FORK_VERSION#gloas
- MAX_REQUEST_PAYLOADS#gloas - MAX_REQUEST_PAYLOADS#gloas
- MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas
- PAYLOAD_ATTESTATION_DUE_BPS#gloas - PAYLOAD_ATTESTATION_DUE_BPS#gloas
- SYNC_MESSAGE_DUE_BPS_GLOAS#gloas - SYNC_MESSAGE_DUE_BPS_GLOAS#gloas
- MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas
ssz_objects: ssz_objects:
# Not implemented # phase0
- Eth1Block#phase0 - Eth1Block#phase0
- MatrixEntry#fulu # capella
# Not implemented: capella
- LightClientBootstrap#capella - LightClientBootstrap#capella
- LightClientFinalityUpdate#capella - LightClientFinalityUpdate#capella
- LightClientOptimisticUpdate#capella - LightClientOptimisticUpdate#capella
- LightClientUpdate#capella - LightClientUpdate#capella
# fulu
# Not implemented: gloas (future fork) - MatrixEntry#fulu
# gloas
- BeaconBlockBody#gloas - BeaconBlockBody#gloas
- BeaconState#gloas - BeaconState#gloas
- Builder#gloas
- BuilderPendingPayment#gloas - BuilderPendingPayment#gloas
- BuilderPendingWithdrawal#gloas - BuilderPendingWithdrawal#gloas
- DataColumnSidecar#gloas - DataColumnSidecar#gloas
- ExecutionPayloadEnvelope#gloas
- ExecutionPayloadBid#gloas - ExecutionPayloadBid#gloas
- ExecutionPayloadEnvelope#gloas
- ForkChoiceNode#gloas - ForkChoiceNode#gloas
- IndexedPayloadAttestation#gloas - IndexedPayloadAttestation#gloas
- PayloadAttestation#gloas - PayloadAttestation#gloas
- PayloadAttestationData#gloas - PayloadAttestationData#gloas
- PayloadAttestationMessage#gloas - PayloadAttestationMessage#gloas
- SignedExecutionPayloadEnvelope#gloas
- SignedExecutionPayloadBid#gloas
- Builder#gloas
- ProposerPreferences#gloas - ProposerPreferences#gloas
- SignedExecutionPayloadBid#gloas
- SignedExecutionPayloadEnvelope#gloas
- SignedProposerPreferences#gloas - SignedProposerPreferences#gloas
dataclasses: dataclasses:
# Not implemented # phase0
- BlobParameters#fulu
- ExpectedWithdrawals#capella
- ExpectedWithdrawals#electra
- LatestMessage#phase0 - LatestMessage#phase0
- LightClientStore#altair
- OptimisticStore#bellatrix
- Store#phase0 - Store#phase0
# altair
# Not implemented: capella - LightClientStore#altair
# bellatrix
- OptimisticStore#bellatrix
# capella
- ExpectedWithdrawals#capella
- LightClientStore#capella - LightClientStore#capella
# electra
# Not implemented: gloas (future fork) - ExpectedWithdrawals#electra
# fulu
- BlobParameters#fulu
# gloas
- ExpectedWithdrawals#gloas - ExpectedWithdrawals#gloas
- LatestMessage#gloas - LatestMessage#gloas
- Store#gloas - Store#gloas
@@ -140,7 +146,6 @@ exceptions:
- g1_lincomb#deneb - g1_lincomb#deneb
- hash_to_bls_field#deneb - hash_to_bls_field#deneb
- is_power_of_two#deneb - is_power_of_two#deneb
- multi_exp#deneb
- reverse_bits#deneb - reverse_bits#deneb
- validate_kzg_g1#deneb - validate_kzg_g1#deneb
- verify_blob_kzg_proof#deneb - verify_blob_kzg_proof#deneb
@@ -175,7 +180,12 @@ exceptions:
- verify_cell_kzg_proof_batch#fulu - verify_cell_kzg_proof_batch#fulu
- verify_cell_kzg_proof_batch_impl#fulu - verify_cell_kzg_proof_batch_impl#fulu
# Not implemented: phase0 # phase0
- update_proposer_boost_root#phase0
- is_proposer_equivocation#phase0
- record_block_timeliness#phase0
- compute_proposer_score#phase0
- get_attestation_score#phase0
- calculate_committee_fraction#phase0 - calculate_committee_fraction#phase0
- compute_fork_version#phase0 - compute_fork_version#phase0
- compute_pulled_up_tip#phase0 - compute_pulled_up_tip#phase0
@@ -221,8 +231,7 @@ exceptions:
- validate_on_attestation#phase0 - validate_on_attestation#phase0
- validate_target_epoch_against_current_time#phase0 - validate_target_epoch_against_current_time#phase0
- xor#phase0 - xor#phase0
# altair
# Not implemented: altair
- compute_merkle_proof#altair - compute_merkle_proof#altair
- compute_sync_committee_period_at_slot#altair - compute_sync_committee_period_at_slot#altair
- get_contribution_and_proof#altair - get_contribution_and_proof#altair
@@ -244,27 +253,29 @@ exceptions:
- process_sync_committee_contributions#altair - process_sync_committee_contributions#altair
- set_or_append_list#altair - set_or_append_list#altair
- validate_light_client_update#altair - validate_light_client_update#altair
# bellatrix
# Not implemented: bellatrix
- get_execution_payload#bellatrix - get_execution_payload#bellatrix
- is_merge_transition_block#bellatrix - is_merge_transition_block#bellatrix
- is_optimistic_candidate_block#bellatrix - is_optimistic_candidate_block#bellatrix
- latest_verified_ancestor#bellatrix - latest_verified_ancestor#bellatrix
- prepare_execution_payload#bellatrix - prepare_execution_payload#bellatrix
# capella
# Not implemented: capella - apply_withdrawals#capella
- get_balance_after_withdrawals#capella
- get_lc_execution_root#capella - get_lc_execution_root#capella
- get_validators_sweep_withdrawals#capella
- is_valid_light_client_header#capella - is_valid_light_client_header#capella
- prepare_execution_payload#capella - prepare_execution_payload#capella
- process_epoch#capella - process_epoch#capella
- update_next_withdrawal_index#capella
- update_next_withdrawal_validator_index#capella
- upgrade_lc_bootstrap_to_capella#capella - upgrade_lc_bootstrap_to_capella#capella
- upgrade_lc_finality_update_to_capella#capella - upgrade_lc_finality_update_to_capella#capella
- upgrade_lc_header_to_capella#capella - upgrade_lc_header_to_capella#capella
- upgrade_lc_optimistic_update_to_capella#capella - upgrade_lc_optimistic_update_to_capella#capella
- upgrade_lc_store_to_capella#capella - upgrade_lc_store_to_capella#capella
- upgrade_lc_update_to_capella#capella - upgrade_lc_update_to_capella#capella
# deneb
# Not implemented: deneb
- get_lc_execution_root#deneb - get_lc_execution_root#deneb
- is_valid_light_client_header#deneb - is_valid_light_client_header#deneb
- prepare_execution_payload#deneb - prepare_execution_payload#deneb
@@ -274,33 +285,34 @@ exceptions:
- upgrade_lc_optimistic_update_to_deneb#deneb - upgrade_lc_optimistic_update_to_deneb#deneb
- upgrade_lc_store_to_deneb#deneb - upgrade_lc_store_to_deneb#deneb
- upgrade_lc_update_to_deneb#deneb - upgrade_lc_update_to_deneb#deneb
# electra
# Not implemented: electra
- compute_weak_subjectivity_period#electra - compute_weak_subjectivity_period#electra
- current_sync_committee_gindex_at_slot#electra - current_sync_committee_gindex_at_slot#electra
- finalized_root_gindex_at_slot#electra - finalized_root_gindex_at_slot#electra
- get_eth1_vote#electra - get_eth1_vote#electra
- get_lc_execution_root#electra - get_lc_execution_root#electra
- get_pending_partial_withdrawals#electra
- get_validators_sweep_withdrawals#electra
- is_compounding_withdrawal_credential#electra - is_compounding_withdrawal_credential#electra
- is_eligible_for_partial_withdrawals#electra
- is_within_weak_subjectivity_period#electra - is_within_weak_subjectivity_period#electra
- next_sync_committee_gindex_at_slot#electra - next_sync_committee_gindex_at_slot#electra
- normalize_merkle_branch#electra - normalize_merkle_branch#electra
- prepare_execution_payload#electra - prepare_execution_payload#electra
- update_pending_partial_withdrawals#electra
- upgrade_lc_bootstrap_to_electra#electra - upgrade_lc_bootstrap_to_electra#electra
- upgrade_lc_finality_update_to_electra#electra - upgrade_lc_finality_update_to_electra#electra
- upgrade_lc_header_to_electra#electra - upgrade_lc_header_to_electra#electra
- upgrade_lc_optimistic_update_to_electra#electra - upgrade_lc_optimistic_update_to_electra#electra
- upgrade_lc_store_to_electra#electra - upgrade_lc_store_to_electra#electra
- upgrade_lc_update_to_electra#electra - upgrade_lc_update_to_electra#electra
# fulu
# Not implemented: fulu
- compute_matrix#fulu - compute_matrix#fulu
- get_blob_parameters#fulu - get_blob_parameters#fulu
- get_data_column_sidecars_from_block#fulu - get_data_column_sidecars_from_block#fulu
- get_data_column_sidecars_from_column_sidecar#fulu - get_data_column_sidecars_from_column_sidecar#fulu
- recover_matrix#fulu - recover_matrix#fulu
# gloas
# Not implemented: gloas (future fork)
- compute_balance_weighted_acceptance#gloas - compute_balance_weighted_acceptance#gloas
- compute_balance_weighted_selection#gloas - compute_balance_weighted_selection#gloas
- compute_fork_version#gloas - compute_fork_version#gloas
@@ -368,49 +380,42 @@ exceptions:
- verify_execution_payload_bid_signature#gloas - verify_execution_payload_bid_signature#gloas
- add_builder_to_registry#gloas - add_builder_to_registry#gloas
- apply_deposit_for_builder#gloas - apply_deposit_for_builder#gloas
- apply_withdrawals#capella
- apply_withdrawals#gloas - apply_withdrawals#gloas
- can_builder_cover_bid#gloas - can_builder_cover_bid#gloas
- compute_proposer_score#phase0
- convert_builder_index_to_validator_index#gloas - convert_builder_index_to_validator_index#gloas
- convert_validator_index_to_builder_index#gloas - convert_validator_index_to_builder_index#gloas
- get_attestation_score#gloas - get_attestation_score#gloas
- get_attestation_score#phase0 - get_attestation_score#phase0
- get_balance_after_withdrawals#capella - get_balance_after_withdrawals#capella
- get_builder_from_deposit#gloas
- get_builder_withdrawals#gloas - get_builder_withdrawals#gloas
- get_builders_sweep_withdrawals#gloas - get_builders_sweep_withdrawals#gloas
- get_index_for_new_builder#gloas - get_index_for_new_builder#gloas
- get_pending_balance_to_withdraw_for_builder#gloas - get_pending_balance_to_withdraw_for_builder#gloas
- get_pending_partial_withdrawals#electra
- get_proposer_preferences_signature#gloas - get_proposer_preferences_signature#gloas
- get_upcoming_proposal_slots#gloas - get_upcoming_proposal_slots#gloas
- get_validators_sweep_withdrawals#capella
- get_validators_sweep_withdrawals#electra
- initiate_builder_exit#gloas - initiate_builder_exit#gloas
- is_active_builder#gloas - is_active_builder#gloas
- is_builder_index#gloas - is_builder_index#gloas
- is_data_available#gloas
- is_eligible_for_partial_withdrawals#electra - is_eligible_for_partial_withdrawals#electra
- is_head_late#gloas - is_head_late#gloas
- is_head_weak#gloas - is_head_weak#gloas
- is_parent_strong#gloas - is_parent_strong#gloas
- is_proposer_equivocation#phase0
- is_valid_proposal_slot#gloas - is_valid_proposal_slot#gloas
- onboard_builders_from_pending_deposits#gloas
- process_deposit_request#gloas - process_deposit_request#gloas
- process_voluntary_exit#gloas - process_voluntary_exit#gloas
- record_block_timeliness#gloas - record_block_timeliness#gloas
- record_block_timeliness#phase0 - record_block_timeliness#phase0
- verify_data_column_sidecar_kzg_proofs#gloas
- should_apply_proposer_boost#gloas - should_apply_proposer_boost#gloas
- update_builder_pending_withdrawals#gloas - update_builder_pending_withdrawals#gloas
- update_next_withdrawal_builder_index#gloas - update_next_withdrawal_builder_index#gloas
- update_next_withdrawal_index#capella
- update_next_withdrawal_validator_index#capella
- update_payload_expected_withdrawals#gloas - update_payload_expected_withdrawals#gloas
- update_pending_partial_withdrawals#electra
- update_proposer_boost_root#gloas - update_proposer_boost_root#gloas
- update_proposer_boost_root#phase0
presets: presets:
# gloas
- BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas - BUILDER_PENDING_WITHDRAWALS_LIMIT#gloas
- BUILDER_REGISTRY_LIMIT#gloas - BUILDER_REGISTRY_LIMIT#gloas
- MAX_BUILDERS_PER_WITHDRAWALS_SWEEP#gloas - MAX_BUILDERS_PER_WITHDRAWALS_SWEEP#gloas

View File

@@ -12,11 +12,11 @@ jobs:
- name: Check version consistency - name: Check version consistency
run: | run: |
WORKSPACE_VERSION=$(grep 'consensus_spec_version = ' WORKSPACE | sed 's/.*"\(.*\)"/\1/') 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 if [ "$WORKSPACE_VERSION" != "$ETHSPECIFY_VERSION" ]; then
echo "Version mismatch between WORKSPACE and ethspecify" echo "Version mismatch between WORKSPACE and ethspecify"
echo " WORKSPACE: $WORKSPACE_VERSION" echo " WORKSPACE: $WORKSPACE_VERSION"
echo " specrefs/.ethspecify.yml: $ETHSPECIFY_VERSION" echo " .ethspecify.yml: $ETHSPECIFY_VERSION"
exit 1 exit 1
else else
echo "Versions match: $WORKSPACE_VERSION" echo "Versions match: $WORKSPACE_VERSION"
@@ -26,7 +26,7 @@ jobs:
run: python3 -mpip install ethspecify run: python3 -mpip install ethspecify
- name: Update spec references - name: Update spec references
run: ethspecify process --path=specrefs run: ethspecify
- name: Check for differences - name: Check for differences
run: | run: |
@@ -40,4 +40,4 @@ jobs:
fi fi
- name: Check spec references - name: Check spec references
run: ethspecify check --path=specrefs run: ethspecify check

View File

@@ -273,16 +273,16 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz", 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.2"
load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests") load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests")
consensus_spec_tests( consensus_spec_tests(
name = "consensus_spec_tests", name = "consensus_spec_tests",
flavors = { flavors = {
"general": "sha256-j5R3jA7Oo4OSDMTvpMuD+8RomaCByeFSwtfkq6fL0Zg=", "general": "sha256-iGQsGZ1cHah+2CSod9jC3kN8Ku4n6KO0hIwfINrn/po=",
"minimal": "sha256-tdTqByoyswOS4r6OxFmo70y2BP7w1TgEok+gf4cbxB0=", "minimal": "sha256-TgcYt8N8sXSttdHTGvOa+exUZ1zn1UzlAMz0V7i37xc=",
"mainnet": "sha256-5gB4dt6SnSDKzdBc06VedId3NkgvSYyv9n9FRxWKwYI=", "mainnet": "sha256-LnXyiLoJtrvEvbqLDSAAqpLMdN/lXv92SAgYG8fNjCs=",
}, },
version = consensus_spec_version, version = consensus_spec_version,
) )
@@ -298,7 +298,7 @@ filegroup(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
""", """,
integrity = "sha256-J+43DrK1pF658kTXTwMS6zGf4KDjvas++m8w2a8swpg=", integrity = "sha256-Y/67Dg393PksZj5rTFNLntiJ6hNdB7Rxbu5gZE2gebY=",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:], strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version, url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
) )

19
api/fallback/BUILD.bazel Normal file
View 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
View 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,
}).Info("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
}

View 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
View 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")

View File

@@ -25,6 +25,11 @@ type GrpcConnectionProvider interface {
// SwitchHost switches to the endpoint at the given index. // SwitchHost switches to the endpoint at the given index.
// The new connection is created lazily on next CurrentConn() call. // The new connection is created lazily on next CurrentConn() call.
SwitchHost(index int) error 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 closes the current connection.
Close() Close()
} }
@@ -38,6 +43,7 @@ type grpcConnectionProvider struct {
// Current connection state (protected by mutex) // Current connection state (protected by mutex)
currentIndex uint64 currentIndex uint64
conn *grpc.ClientConn conn *grpc.ClientConn
connCounter uint64
mu sync.Mutex mu sync.Mutex
closed bool closed bool
@@ -138,6 +144,7 @@ func (p *grpcConnectionProvider) SwitchHost(index int) error {
p.conn = nil // Clear immediately - new connection created lazily p.conn = nil // Clear immediately - new connection created lazily
p.currentIndex = uint64(index) p.currentIndex = uint64(index)
p.connCounter++
// Close old connection asynchronously to avoid blocking the caller // Close old connection asynchronously to avoid blocking the caller
if oldConn != nil { if oldConn != nil {
@@ -155,6 +162,12 @@ func (p *grpcConnectionProvider) SwitchHost(index int) error {
return nil return nil
} }
func (p *grpcConnectionProvider) ConnectionCounter() uint64 {
p.mu.Lock()
defer p.mu.Unlock()
return p.connCounter
}
func (p *grpcConnectionProvider) Close() { func (p *grpcConnectionProvider) Close() {
p.mu.Lock() p.mu.Lock()
defer p.mu.Unlock() defer p.mu.Unlock()

View File

@@ -4,17 +4,24 @@ import "google.golang.org/grpc"
// MockGrpcProvider implements GrpcConnectionProvider for testing. // MockGrpcProvider implements GrpcConnectionProvider for testing.
type MockGrpcProvider struct { type MockGrpcProvider struct {
MockConn *grpc.ClientConn MockConn *grpc.ClientConn
MockHosts []string MockHosts []string
CurrentIndex int
ConnCounter uint64
} }
func (m *MockGrpcProvider) CurrentConn() *grpc.ClientConn { return m.MockConn } func (m *MockGrpcProvider) CurrentConn() *grpc.ClientConn { return m.MockConn }
func (m *MockGrpcProvider) CurrentHost() string { func (m *MockGrpcProvider) CurrentHost() string {
if len(m.MockHosts) > 0 { if len(m.MockHosts) > 0 {
return m.MockHosts[0] return m.MockHosts[m.CurrentIndex]
} }
return "" return ""
} }
func (m *MockGrpcProvider) Hosts() []string { return m.MockHosts } func (m *MockGrpcProvider) Hosts() []string { return m.MockHosts }
func (m *MockGrpcProvider) SwitchHost(int) error { return nil } func (m *MockGrpcProvider) SwitchHost(idx int) error {
func (m *MockGrpcProvider) Close() {} m.CurrentIndex = idx
m.ConnCounter++
return nil
}
func (m *MockGrpcProvider) ConnectionCounter() uint64 { return m.ConnCounter }
func (m *MockGrpcProvider) Close() {}

View File

@@ -9,13 +9,13 @@ import (
// MockRestProvider implements RestConnectionProvider for testing. // MockRestProvider implements RestConnectionProvider for testing.
type MockRestProvider struct { type MockRestProvider struct {
MockClient *http.Client MockClient *http.Client
MockHandler RestHandler MockHandler Handler
MockHosts []string MockHosts []string
HostIndex int HostIndex int
} }
func (m *MockRestProvider) HttpClient() *http.Client { return m.MockClient } 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 { func (m *MockRestProvider) CurrentHost() string {
if len(m.MockHosts) > 0 { if len(m.MockHosts) > 0 {
return m.MockHosts[m.HostIndex%len(m.MockHosts)] 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) Hosts() []string { return m.MockHosts }
func (m *MockRestProvider) SwitchHost(index int) error { m.HostIndex = index; return nil } func (m *MockRestProvider) SwitchHost(index int) error { m.HostIndex = index; return nil }
// MockRestHandler implements RestHandler for testing. // MockHandler implements Handler for testing.
type MockRestHandler struct { type MockHandler struct {
MockHost string MockHost string
MockClient *http.Client
} }
func (m *MockRestHandler) Get(_ context.Context, _ string, _ any) error { return nil } func (m *MockHandler) Get(_ context.Context, _ string, _ any) error { return nil }
func (m *MockRestHandler) GetStatusCode(_ context.Context, _ string) (int, error) { func (m *MockHandler) GetStatusCode(_ context.Context, _ string) (int, error) {
return http.StatusOK, nil 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 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 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 return nil, nil, nil
} }
func (m *MockRestHandler) HttpClient() *http.Client { return m.MockClient } func (m *MockHandler) Host() string { return m.MockHost }
func (m *MockRestHandler) Host() string { return m.MockHost }
func (m *MockRestHandler) SwitchHost(host string) { m.MockHost = host }

View File

@@ -17,8 +17,8 @@ import (
type RestConnectionProvider interface { type RestConnectionProvider interface {
// HttpClient returns the configured HTTP client with headers, timeout, and optional tracing. // HttpClient returns the configured HTTP client with headers, timeout, and optional tracing.
HttpClient() *http.Client HttpClient() *http.Client
// RestHandler returns the REST handler for making API requests. // Handler returns the REST handler for making API requests.
RestHandler() RestHandler Handler() Handler
// CurrentHost returns the current REST API endpoint URL. // CurrentHost returns the current REST API endpoint URL.
CurrentHost() string CurrentHost() string
// Hosts returns all configured REST API endpoint URLs. // Hosts returns all configured REST API endpoint URLs.
@@ -54,7 +54,7 @@ func WithTracing() RestConnectionProviderOption {
type restConnectionProvider struct { type restConnectionProvider struct {
endpoints []string endpoints []string
httpClient *http.Client httpClient *http.Client
restHandler RestHandler restHandler *handler
currentIndex atomic.Uint64 currentIndex atomic.Uint64
timeout time.Duration timeout time.Duration
headers map[string][]string 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 // 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{ log.WithFields(logrus.Fields{
"endpoints": endpoints, "endpoints": endpoints,
@@ -124,7 +124,7 @@ func (p *restConnectionProvider) HttpClient() *http.Client {
return p.httpClient return p.httpClient
} }
func (p *restConnectionProvider) RestHandler() RestHandler { func (p *restConnectionProvider) Handler() Handler {
return p.restHandler return p.restHandler
} }

View File

@@ -21,32 +21,35 @@ import (
type reqOption func(*http.Request) type reqOption func(*http.Request)
// RestHandler defines the interface for making REST API requests. // Handler defines the interface for making REST API requests.
type RestHandler interface { type Handler interface {
Get(ctx context.Context, endpoint string, resp any) error Get(ctx context.Context, endpoint string, resp any) error
GetStatusCode(ctx context.Context, endpoint string) (int, error) GetStatusCode(ctx context.Context, endpoint string) (int, error)
GetSSZ(ctx context.Context, endpoint string) ([]byte, http.Header, 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 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) PostSSZ(ctx context.Context, endpoint string, headers map[string]string, data *bytes.Buffer) ([]byte, http.Header, error)
HttpClient() *http.Client
Host() string Host() string
SwitchHost(host string)
} }
type restHandler struct { type handler struct {
client http.Client client http.Client
host string host string
reqOverrides []reqOption reqOverrides []reqOption
} }
// newRestHandler returns a RestHandler (internal use) // newHandler returns a *handler for internal use within the rest package.
func newRestHandler(client http.Client, host string) RestHandler { func newHandler(client http.Client, host string) *handler {
return NewRestHandler(client, host) rh := &handler{
client: client,
host: host,
}
rh.appendAcceptOverride()
return rh
} }
// NewRestHandler returns a RestHandler // NewHandler returns a Handler
func NewRestHandler(client http.Client, host string) RestHandler { func NewHandler(client http.Client, host string) Handler {
rh := &restHandler{ rh := &handler{
client: client, client: client,
host: host, 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. // 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 // 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. // 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 != "" { if accept := os.Getenv(params.EnvNameOverrideAccept); accept != "" {
c.reqOverrides = append(c.reqOverrides, func(req *http.Request) { c.reqOverrides = append(c.reqOverrides, func(req *http.Request) {
req.Header.Set("Accept", accept) req.Header.Set("Accept", accept)
@@ -66,18 +69,18 @@ func (c *restHandler) appendAcceptOverride() {
} }
// HttpClient returns the underlying HTTP client of the handler // HttpClient returns the underlying HTTP client of the handler
func (c *restHandler) HttpClient() *http.Client { func (c *handler) HttpClient() *http.Client {
return &c.client return &c.client
} }
// Host returns the underlying HTTP host // Host returns the underlying HTTP host
func (c *restHandler) Host() string { func (c *handler) Host() string {
return c.host return c.host
} }
// Get sends a GET request and decodes the response body as a JSON object into the passed in object. // 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. // 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 url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != 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. // 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 // 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. // (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 url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
@@ -119,7 +122,7 @@ func (c *restHandler) GetStatusCode(ctx context.Context, endpoint string) (int,
return httpResp.StatusCode, nil 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 url := c.host + endpoint
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != 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. // 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. // 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, ctx context.Context,
apiEndpoint string, apiEndpoint string,
headers map[string]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. // 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, ctx context.Context,
apiEndpoint string, apiEndpoint string,
headers map[string]string, headers map[string]string,
@@ -311,6 +314,6 @@ func decodeResp(httpResp *http.Response, resp any) error {
return nil return nil
} }
func (c *restHandler) SwitchHost(host string) { func (c *handler) SwitchHost(host string) {
c.host = host c.host = host
} }

View File

@@ -509,17 +509,17 @@ func (s *SignedBlindedBeaconBlockFulu) SigString() string {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type ExecutionPayloadBid struct { type ExecutionPayloadBid struct {
ParentBlockHash string `json:"parent_block_hash"` ParentBlockHash string `json:"parent_block_hash"`
ParentBlockRoot string `json:"parent_block_root"` ParentBlockRoot string `json:"parent_block_root"`
BlockHash string `json:"block_hash"` BlockHash string `json:"block_hash"`
PrevRandao string `json:"prev_randao"` PrevRandao string `json:"prev_randao"`
FeeRecipient string `json:"fee_recipient"` FeeRecipient string `json:"fee_recipient"`
GasLimit string `json:"gas_limit"` GasLimit string `json:"gas_limit"`
BuilderIndex string `json:"builder_index"` BuilderIndex string `json:"builder_index"`
Slot string `json:"slot"` Slot string `json:"slot"`
Value string `json:"value"` Value string `json:"value"`
ExecutionPayment string `json:"execution_payment"` ExecutionPayment string `json:"execution_payment"`
BlobKzgCommitmentsRoot string `json:"blob_kzg_commitments_root"` BlobKzgCommitments []string `json:"blob_kzg_commitments"`
} }
type SignedExecutionPayloadBid struct { type SignedExecutionPayloadBid struct {

View File

@@ -2939,18 +2939,22 @@ func SignedExecutionPayloadBidFromConsensus(b *eth.SignedExecutionPayloadBid) *S
} }
func ExecutionPayloadBidFromConsensus(b *eth.ExecutionPayloadBid) *ExecutionPayloadBid { 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{ return &ExecutionPayloadBid{
ParentBlockHash: hexutil.Encode(b.ParentBlockHash), ParentBlockHash: hexutil.Encode(b.ParentBlockHash),
ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot), ParentBlockRoot: hexutil.Encode(b.ParentBlockRoot),
BlockHash: hexutil.Encode(b.BlockHash), BlockHash: hexutil.Encode(b.BlockHash),
PrevRandao: hexutil.Encode(b.PrevRandao), PrevRandao: hexutil.Encode(b.PrevRandao),
FeeRecipient: hexutil.Encode(b.FeeRecipient), FeeRecipient: hexutil.Encode(b.FeeRecipient),
GasLimit: fmt.Sprintf("%d", b.GasLimit), GasLimit: fmt.Sprintf("%d", b.GasLimit),
BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex), BuilderIndex: fmt.Sprintf("%d", b.BuilderIndex),
Slot: fmt.Sprintf("%d", b.Slot), Slot: fmt.Sprintf("%d", b.Slot),
Value: fmt.Sprintf("%d", b.Value), Value: fmt.Sprintf("%d", b.Value),
ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment), ExecutionPayment: fmt.Sprintf("%d", b.ExecutionPayment),
BlobKzgCommitmentsRoot: hexutil.Encode(b.BlobKzgCommitmentsRoot), BlobKzgCommitments: blobKzgCommitments,
} }
} }
@@ -3187,22 +3191,30 @@ func (b *ExecutionPayloadBid) ToConsensus() (*eth.ExecutionPayloadBid, error) {
if err != nil { if err != nil {
return nil, server.NewDecodeError(err, "ExecutionPayment") return nil, server.NewDecodeError(err, "ExecutionPayment")
} }
blobKzgCommitmentsRoot, err := bytesutil.DecodeHexWithLength(b.BlobKzgCommitmentsRoot, fieldparams.RootLength) err = slice.VerifyMaxLength(b.BlobKzgCommitments, fieldparams.MaxBlobCommitmentsPerBlock)
if err != nil { 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 &eth.ExecutionPayloadBid{ return &eth.ExecutionPayloadBid{
ParentBlockHash: parentBlockHash, ParentBlockHash: parentBlockHash,
ParentBlockRoot: parentBlockRoot, ParentBlockRoot: parentBlockRoot,
BlockHash: blockHash, BlockHash: blockHash,
PrevRandao: prevRandao, PrevRandao: prevRandao,
FeeRecipient: feeRecipient, FeeRecipient: feeRecipient,
GasLimit: gasLimit, GasLimit: gasLimit,
BuilderIndex: primitives.BuilderIndex(builderIndex), BuilderIndex: primitives.BuilderIndex(builderIndex),
Slot: primitives.Slot(slot), Slot: primitives.Slot(slot),
Value: primitives.Gwei(value), Value: primitives.Gwei(value),
ExecutionPayment: primitives.Gwei(executionPayment), ExecutionPayment: primitives.Gwei(executionPayment),
BlobKzgCommitmentsRoot: blobKzgCommitmentsRoot, BlobKzgCommitments: blobKzgCommitments,
}, nil }, nil
} }

View File

@@ -27,6 +27,7 @@ go_library(
"receive_blob.go", "receive_blob.go",
"receive_block.go", "receive_block.go",
"receive_data_column.go", "receive_data_column.go",
"receive_payload_attestation_message.go",
"service.go", "service.go",
"setup_forkchoice.go", "setup_forkchoice.go",
"tracked_proposer.go", "tracked_proposer.go",
@@ -85,6 +86,7 @@ go_library(
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//io/logs:go_default_library",
"//math:go_default_library", "//math:go_default_library",
"//monitoring/tracing:go_default_library", "//monitoring/tracing:go_default_library",
"//monitoring/tracing/trace:go_default_library", "//monitoring/tracing/trace:go_default_library",

View File

@@ -10,6 +10,7 @@ import (
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types" consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces" "github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil" "github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/io/logs"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/runtime/version" "github.com/OffchainLabs/prysm/v7/runtime/version"
prysmTime "github.com/OffchainLabs/prysm/v7/time" prysmTime "github.com/OffchainLabs/prysm/v7/time"
@@ -87,36 +88,45 @@ 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 { 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()) startTime, err := slots.StartTime(genesis, block.Slot())
if err != nil { if err != nil {
return err return errors.Wrap(err, "failed to get slot start time")
} }
level := log.Logger.GetLevel() 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,
"dataAvailabilityWaitedTime": daWaitedTime,
}
level := logs.PackageVerbosity("beacon-chain/blockchain")
if level >= logrus.DebugLevel { if level >= logrus.DebugLevel {
parentRoot := block.ParentRoot() log.WithFields(moreFields).Info("Synced new block")
lf := logrus.Fields{ return nil
"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")
} 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")
} }
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 return nil
} }

View File

@@ -0,0 +1,19 @@
package blockchain
import (
"context"
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.
func (s *Service) ReceivePayloadAttestationMessage(ctx context.Context, a *ethpb.PayloadAttestationMessage) error {
// TODO: Handle payload attestation message processing once Gloas is fully wired.
return nil
}

View File

@@ -757,6 +757,11 @@ func (c *ChainService) ReceiveDataColumns(dcs []blocks.VerifiedRODataColumn) err
return nil return nil
} }
// ReceivePayloadAttestationMessage implements the same method in the chain service.
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
return nil
}
// DependentRootForEpoch mocks the same method in the chain service // DependentRootForEpoch mocks the same method in the chain service
func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) { func (c *ChainService) DependentRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
return c.TargetRoot, nil return c.TargetRoot, nil

View File

@@ -17,6 +17,7 @@ go_library(
"error.go", "error.go",
"interfaces.go", "interfaces.go",
"log.go", "log.go",
"payload_attestation.go",
"payload_id.go", "payload_id.go",
"proposer_indices.go", "proposer_indices.go",
"proposer_indices_disabled.go", # keep "proposer_indices_disabled.go", # keep
@@ -76,6 +77,7 @@ go_test(
"checkpoint_state_test.go", "checkpoint_state_test.go",
"committee_fuzz_test.go", "committee_fuzz_test.go",
"committee_test.go", "committee_test.go",
"payload_attestation_test.go",
"payload_id_test.go", "payload_id_test.go",
"private_access_test.go", "private_access_test.go",
"proposer_indices_test.go", "proposer_indices_test.go",

View 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
}

View 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))
}

View File

@@ -17,27 +17,56 @@ import (
) )
// ProcessExecutionPayloadBid processes a signed execution payload bid in the Gloas fork. // 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 // <spec fn="process_execution_payload_bid" fork="gloas" hash="823c9f3a">
// bid = signed_bid.message // def process_execution_payload_bid(state: BeaconState, block: BeaconBlock) -> None:
// builder_index = bid.builder_index // signed_bid = block.body.signed_execution_payload_bid
// amount = bid.value // bid = signed_bid.message
// if builder_index == BUILDER_INDEX_SELF_BUILD: // builder_index = bid.builder_index
// assert amount == 0 // amount = bid.value
// assert signed_bid.signature == G2_POINT_AT_INFINITY //
// else: // # For self-builds, amount must be zero regardless of withdrawal credential prefix
// assert is_active_builder(state, builder_index) // if builder_index == BUILDER_INDEX_SELF_BUILD:
// assert can_builder_cover_bid(state, builder_index, amount) // assert amount == 0
// assert verify_execution_payload_bid_signature(state, signed_bid) // assert signed_bid.signature == bls.G2_POINT_AT_INFINITY
// assert bid.slot == block.slot // else:
// assert bid.parent_block_hash == state.latest_block_hash // # Verify that the builder is active
// assert bid.parent_block_root == block.parent_root // assert is_active_builder(state, builder_index)
// assert bid.prev_randao == get_randao_mix(state, get_current_epoch(state)) // # Verify that the builder has funds to cover the bid
// if amount > 0: // assert can_builder_cover_bid(state, builder_index, amount)
// state.builder_pending_payments[...] = BuilderPendingPayment(weight=0, withdrawal=BuilderPendingWithdrawal(fee_recipient=bid.fee_recipient, amount=amount, builder_index=builder_index)) // # Verify that the bid signature is valid
// state.latest_execution_payload_bid = bid // 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 { func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyBeaconBlock) error {
signedBid, err := block.Body().SignedExecutionPayloadBid() signedBid, err := block.Body().SignedExecutionPayloadBid()
if err != nil { if err != nil {
@@ -86,6 +115,12 @@ func ProcessExecutionPayloadBid(st state.BeaconState, block interfaces.ReadOnlyB
} }
} }
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 { if err := validateBidConsistency(st, bid, block); err != nil {
return errors.Wrap(err, "bid consistency validation failed") return errors.Wrap(err, "bid consistency validation failed")
} }

View File

@@ -184,6 +184,28 @@ func signBid(t *testing.T, sk common.SecretKey, bid *ethpb.ExecutionPayloadBid,
return out 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) { func TestProcessExecutionPayloadBid_SelfBuildSuccess(t *testing.T) {
slot := primitives.Slot(12) slot := primitives.Slot(12)
proposerIdx := primitives.ValidatorIndex(0) 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) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32), BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 0, Value: 0,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20), FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
} }
signed := &ethpb.SignedExecutionPayloadBid{ signed := &ethpb.SignedExecutionPayloadBid{
Message: bid, 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{}) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinActivationBalance+1000, randao, latestHash, [48]byte{})
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32), BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 10, Value: 10,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20), FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
} }
signed := &ethpb.SignedExecutionPayloadBid{ signed := &ethpb.SignedExecutionPayloadBid{
Message: bid, Message: bid,
@@ -280,17 +302,17 @@ func TestProcessExecutionPayloadBid_PendingPaymentAndCacheBid(t *testing.T) {
state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey) state := buildGloasState(t, slot, proposerIdx, builderIdx, balance, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32), BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 500_000, Value: 500_000,
ExecutionPayment: 1, ExecutionPayment: 1,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20), FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
@@ -341,17 +363,17 @@ func TestProcessExecutionPayloadBid_BuilderNotActive(t *testing.T) {
state = stateIface.(*state_native.BeaconState) state = stateIface.(*state_native.BeaconState)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32), ParentBlockRoot: bytes.Repeat([]byte{0x03}, 32),
BlockHash: bytes.Repeat([]byte{0x04}, 32), BlockHash: bytes.Repeat([]byte{0x04}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 10, Value: 10,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x05}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x06}, 20), FeeRecipient: bytes.Repeat([]byte{0x06}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -394,17 +416,17 @@ func TestProcessExecutionPayloadBid_CannotCoverBid(t *testing.T) {
state = stateIface.(*state_native.BeaconState) state = stateIface.(*state_native.BeaconState)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32), BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 25, Value: 25,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20), FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) 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) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xCC}, 32),
BlockHash: bytes.Repeat([]byte{0xDD}, 32), BlockHash: bytes.Repeat([]byte{0xDD}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 10, Value: 10,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xEE}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xFF}, 20), FeeRecipient: bytes.Repeat([]byte{0xFF}, 20),
} }
// Use an invalid signature. // Use an invalid signature.
invalidSig := [96]byte{1} invalidSig := [96]byte{1}
@@ -463,6 +485,42 @@ func TestProcessExecutionPayloadBid_InvalidSignature(t *testing.T) {
require.ErrorContains(t, "bid signature validation failed", err) 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 := &ethpb.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 := &ethpb.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) { func TestProcessExecutionPayloadBid_SlotMismatch(t *testing.T) {
slot := primitives.Slot(10) slot := primitives.Slot(10)
builderIdx := primitives.BuilderIndex(1) 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) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32), ParentBlockRoot: bytes.Repeat([]byte{0xAA}, 32),
BlockHash: bytes.Repeat([]byte{0xBB}, 32), BlockHash: bytes.Repeat([]byte{0xBB}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot + 1, // mismatch Slot: slot + 1, // mismatch
Value: 1, Value: 1,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0xCC}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0xDD}, 20), FeeRecipient: bytes.Repeat([]byte{0xDD}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) 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) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch ParentBlockHash: bytes.Repeat([]byte{0x11}, 32), // mismatch
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32), ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32), BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 1, Value: 1,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20), FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) sig := signBid(t, sk, bid, state.Fork(), genesis)
@@ -563,17 +621,17 @@ func TestProcessExecutionPayloadBid_ParentRootMismatch(t *testing.T) {
parentRoot := bytes.Repeat([]byte{0x22}, 32) parentRoot := bytes.Repeat([]byte{0x22}, 32)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: parentRoot, ParentBlockRoot: parentRoot,
BlockHash: bytes.Repeat([]byte{0x33}, 32), BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 1, Value: 1,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20), FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) 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) state := buildGloasState(t, slot, proposerIdx, builderIdx, params.BeaconConfig().MinDepositAmount+1000, randao, latestHash, pubKey)
bid := &ethpb.ExecutionPayloadBid{ bid := &ethpb.ExecutionPayloadBid{
ParentBlockHash: latestHash[:], ParentBlockHash: latestHash[:],
ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32), ParentBlockRoot: bytes.Repeat([]byte{0x22}, 32),
BlockHash: bytes.Repeat([]byte{0x33}, 32), BlockHash: bytes.Repeat([]byte{0x33}, 32),
PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch PrevRandao: bytes.Repeat([]byte{0x01}, 32), // mismatch
GasLimit: 1, GasLimit: 1,
BuilderIndex: builderIdx, BuilderIndex: builderIdx,
Slot: slot, Slot: slot,
Value: 1, Value: 1,
ExecutionPayment: 0, ExecutionPayment: 0,
BlobKzgCommitmentsRoot: bytes.Repeat([]byte{0x44}, 32), BlobKzgCommitments: blobCommitmentsForSlot(slot, 1),
FeeRecipient: bytes.Repeat([]byte{0x55}, 20), FeeRecipient: bytes.Repeat([]byte{0x55}, 20),
} }
genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot()) genesis := bytesutil.ToBytes32(state.GenesisValidatorsRoot())
sig := signBid(t, sk, bid, state.Fork(), genesis) sig := signBid(t, sk, bid, state.Fork(), genesis)

View File

@@ -24,14 +24,21 @@ import (
) )
// ProcessPayloadAttestations validates payload attestations in a block body. // ProcessPayloadAttestations validates payload attestations in a block body.
// Spec v1.7.0-alpha.0 (pseudocode):
// process_payload_attestation(state: BeaconState, payload_attestation: PayloadAttestation):
// //
// data = payload_attestation.data // <spec fn="process_payload_attestation" fork="gloas" hash="f46bf0b0">
// assert data.beacon_block_root == state.latest_block_header.parent_root // def process_payload_attestation(
// assert data.slot + 1 == state.slot // state: BeaconState, payload_attestation: PayloadAttestation
// indexed = get_indexed_payload_attestation(state, data.slot, payload_attestation) // ) -> None:
// assert is_valid_indexed_payload_attestation(state, indexed) // data = payload_attestation.data
//
// # Check that the attestation is for the parent beacon block
// assert data.beacon_block_root == state.latest_block_header.parent_root
// # Check that the attestation is for the previous slot
// assert data.slot + 1 == state.slot
// # Verify signature
// indexed_payload_attestation = get_indexed_payload_attestation(state, payload_attestation)
// assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation)
// </spec>
func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error { func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error {
atts, err := body.PayloadAttestations() atts, err := body.PayloadAttestations()
if err != nil { if err != nil {
@@ -70,7 +77,7 @@ func ProcessPayloadAttestations(ctx context.Context, st state.BeaconState, body
// indexedPayloadAttestation converts a payload attestation into its indexed form. // indexedPayloadAttestation converts a payload attestation into its indexed form.
func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState, att *eth.PayloadAttestation) (*consensus_types.IndexedPayloadAttestation, error) { func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState, att *eth.PayloadAttestation) (*consensus_types.IndexedPayloadAttestation, error) {
committee, err := payloadCommittee(ctx, st, att.Data.Slot) committee, err := PayloadCommittee(ctx, st, att.Data.Slot)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -89,19 +96,26 @@ func indexedPayloadAttestation(ctx context.Context, st state.ReadOnlyBeaconState
}, nil }, nil
} }
// payloadCommittee returns the payload timeliness committee for a given slot for the state. // PayloadCommittee returns the payload timeliness committee for a given slot for the state.
// Spec v1.7.0-alpha.0 (pseudocode):
// get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
// //
// epoch = compute_epoch_at_slot(slot) // <spec fn="get_ptc" fork="gloas" hash="ae15f761">
// seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot)) // def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]:
// indices = [] // """
// committees_per_slot = get_committee_count_per_slot(state, epoch) // Get the payload timeliness committee for the given ``slot``.
// for i in range(committees_per_slot): // """
// committee = get_beacon_committee(state, slot, CommitteeIndex(i)) // epoch = compute_epoch_at_slot(slot)
// indices.extend(committee) // seed = hash(get_seed(state, epoch, DOMAIN_PTC_ATTESTER) + uint_to_bytes(slot))
// return compute_balance_weighted_selection(state, indices, seed, size=PTC_SIZE, shuffle_indices=False) // indices: List[ValidatorIndex] = []
func payloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) { // # Concatenate all committees for this slot in order
// committees_per_slot = get_committee_count_per_slot(state, epoch)
// for i in range(committees_per_slot):
// committee = get_beacon_committee(state, slot, CommitteeIndex(i))
// indices.extend(committee)
// return compute_balance_weighted_selection(
// state, indices, seed, size=PTC_SIZE, shuffle_indices=False
// )
// </spec>
func PayloadCommittee(ctx context.Context, st state.ReadOnlyBeaconState, slot primitives.Slot) ([]primitives.ValidatorIndex, error) {
epoch := slots.ToEpoch(slot) epoch := slots.ToEpoch(slot)
seed, err := ptcSeed(st, epoch, slot) seed, err := ptcSeed(st, epoch, slot)
if err != nil { if err != nil {
@@ -152,17 +166,35 @@ func ptcSeed(st state.ReadOnlyBeaconState, epoch primitives.Epoch, slot primitiv
} }
// selectByBalance selects a balance-weighted subset of input candidates. // selectByBalance selects a balance-weighted subset of input candidates.
// Spec v1.7.0-alpha.0 (pseudocode):
// compute_balance_weighted_selection(state, indices, seed, size, shuffle_indices):
// Note: shuffle_indices is false for PTC.
// //
// total = len(indices); selected = []; i = 0 // <spec fn="compute_balance_weighted_selection" fork="gloas" hash="2c9f1c23">
// while len(selected) < size: // def compute_balance_weighted_selection(
// next = i % total // state: BeaconState,
// if shuffle_indices: next = compute_shuffled_index(next, total, seed) // indices: Sequence[ValidatorIndex],
// if compute_balance_weighted_acceptance(state, indices[next], seed, i): // seed: Bytes32,
// selected.append(indices[next]) // size: uint64,
// i += 1 // shuffle_indices: bool,
// ) -> Sequence[ValidatorIndex]:
// """
// Return ``size`` indices sampled by effective balance, using ``indices``
// as candidates. If ``shuffle_indices`` is ``True``, candidate indices
// are themselves sampled from ``indices`` by shuffling it, otherwise
// ``indices`` is traversed in order.
// """
// total = uint64(len(indices))
// assert total > 0
// selected: List[ValidatorIndex] = []
// i = uint64(0)
// while len(selected) < size:
// next_index = i % total
// if shuffle_indices:
// next_index = compute_shuffled_index(next_index, total, seed)
// candidate_index = indices[next_index]
// if compute_balance_weighted_acceptance(state, candidate_index, seed, i):
// selected.append(candidate_index)
// i += 1
// return selected
// </spec>
func selectByBalanceFill( func selectByBalanceFill(
ctx context.Context, ctx context.Context,
st state.ReadOnlyBeaconState, st state.ReadOnlyBeaconState,
@@ -199,15 +231,22 @@ func selectByBalanceFill(
} }
// acceptByBalance determines if a validator is accepted based on its effective balance. // acceptByBalance determines if a validator is accepted based on its effective balance.
// Spec v1.7.0-alpha.0 (pseudocode):
// compute_balance_weighted_acceptance(state, index, seed, i):
// //
// MAX_RANDOM_VALUE = 2**16 - 1 // <spec fn="compute_balance_weighted_acceptance" fork="gloas" hash="9954dcd0">
// random_bytes = hash(seed + uint_to_bytes(i // 16)) // def compute_balance_weighted_acceptance(
// offset = i % 16 * 2 // state: BeaconState, index: ValidatorIndex, seed: Bytes32, i: uint64
// random_value = bytes_to_uint64(random_bytes[offset:offset+2]) // ) -> bool:
// effective_balance = state.validators[index].effective_balance // """
// return effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value // Return whether to accept the selection of the validator ``index``, with probability
// proportional to its ``effective_balance``, and randomness given by ``seed`` and ``i``.
// """
// MAX_RANDOM_VALUE = 2**16 - 1
// random_bytes = hash(seed + uint_to_bytes(i // 16))
// offset = i % 16 * 2
// random_value = bytes_to_uint64(random_bytes[offset : offset + 2])
// effective_balance = state.validators[index].effective_balance
// return effective_balance * MAX_RANDOM_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value
// </spec>
func acceptByBalance(st state.ReadOnlyBeaconState, idx primitives.ValidatorIndex, seedBuf []byte, hashFunc func([]byte) [32]byte, maxBalance uint64, round uint64) (bool, error) { func acceptByBalance(st state.ReadOnlyBeaconState, idx primitives.ValidatorIndex, seedBuf []byte, hashFunc func([]byte) [32]byte, maxBalance uint64, round uint64) (bool, error) {
// Reuse the seed buffer by overwriting the last 8 bytes with the round counter. // Reuse the seed buffer by overwriting the last 8 bytes with the round counter.
binary.LittleEndian.PutUint64(seedBuf[len(seedBuf)-8:], round/16) binary.LittleEndian.PutUint64(seedBuf[len(seedBuf)-8:], round/16)
@@ -224,16 +263,26 @@ func acceptByBalance(st state.ReadOnlyBeaconState, idx primitives.ValidatorIndex
} }
// validIndexedPayloadAttestation verifies the signature of an indexed payload attestation. // validIndexedPayloadAttestation verifies the signature of an indexed payload attestation.
// Spec v1.7.0-alpha.0 (pseudocode):
// is_valid_indexed_payload_attestation(state: BeaconState, indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
// //
// indices = indexed_payload_attestation.attesting_indices // <spec fn="is_valid_indexed_payload_attestation" fork="gloas" hash="d76e0f89">
// return len(indices) > 0 and indices == sorted(indices) and // def is_valid_indexed_payload_attestation(
// bls.FastAggregateVerify( // state: BeaconState, attestation: IndexedPayloadAttestation
// [state.validators[i].pubkey for i in indices], // ) -> bool:
// compute_signing_root(indexed_payload_attestation.data, get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot)), // """
// indexed_payload_attestation.signature, // Check if ``attestation`` is non-empty, has sorted indices, and has
// ) // a valid aggregate signature.
// """
// # Verify indices are non-empty and sorted
// indices = attestation.attesting_indices
// if len(indices) == 0 or not indices == sorted(indices):
// return False
//
// # Verify aggregate signature
// pubkeys = [state.validators[i].pubkey for i in indices]
// domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot))
// signing_root = compute_signing_root(attestation.data, domain)
// return bls.FastAggregateVerify(pubkeys, signing_root, attestation.signature)
// </spec>
func validIndexedPayloadAttestation(st state.ReadOnlyBeaconState, att *consensus_types.IndexedPayloadAttestation) error { func validIndexedPayloadAttestation(st state.ReadOnlyBeaconState, att *consensus_types.IndexedPayloadAttestation) error {
indices := att.AttestingIndices indices := att.AttestingIndices
if len(indices) == 0 || !slices.IsSorted(indices) { if len(indices) == 0 || !slices.IsSorted(indices) {

View File

@@ -10,17 +10,21 @@ import (
) )
// ProcessBuilderPendingPayments processes the builder pending payments from the previous epoch. // ProcessBuilderPendingPayments processes the builder pending payments from the previous epoch.
// Spec v1.7.0-alpha.0 (pseudocode):
// def process_builder_pending_payments(state: BeaconState) -> None:
// //
// quorum = get_builder_payment_quorum_threshold(state) // <spec fn="process_builder_pending_payments" fork="gloas" hash="10da48dd">
// for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]: // def process_builder_pending_payments(state: BeaconState) -> None:
// if payment.weight >= quorum: // """
// state.builder_pending_withdrawals.append(payment.withdrawal) // Processes the builder pending payments from the previous epoch.
// """
// quorum = get_builder_payment_quorum_threshold(state)
// for payment in state.builder_pending_payments[:SLOTS_PER_EPOCH]:
// if payment.weight >= quorum:
// state.builder_pending_withdrawals.append(payment.withdrawal)
// //
// old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:] // old_payments = state.builder_pending_payments[SLOTS_PER_EPOCH:]
// new_payments = [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)] // new_payments = [BuilderPendingPayment() for _ in range(SLOTS_PER_EPOCH)]
// state.builder_pending_payments = old_payments + new_payments // state.builder_pending_payments = old_payments + new_payments
// </spec>
func ProcessBuilderPendingPayments(state state.BeaconState) error { func ProcessBuilderPendingPayments(state state.BeaconState) error {
quorum, err := builderQuorumThreshold(state) quorum, err := builderQuorumThreshold(state)
if err != nil { if err != nil {
@@ -53,12 +57,16 @@ func ProcessBuilderPendingPayments(state state.BeaconState) error {
} }
// builderQuorumThreshold calculates the quorum threshold for builder payments. // builderQuorumThreshold calculates the quorum threshold for builder payments.
// Spec v1.7.0-alpha.0 (pseudocode):
// def get_builder_payment_quorum_threshold(state: BeaconState) -> uint64:
// //
// per_slot_balance = get_total_active_balance(state) // SLOTS_PER_EPOCH // <spec fn="get_builder_payment_quorum_threshold" fork="gloas" hash="a64b7ffb">
// quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR // def get_builder_payment_quorum_threshold(state: BeaconState) -> uint64:
// return uint64(quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATOR) // """
// Calculate the quorum threshold for builder payments.
// """
// per_slot_balance = get_total_active_balance(state) // SLOTS_PER_EPOCH
// quorum = per_slot_balance * BUILDER_PAYMENT_THRESHOLD_NUMERATOR
// return uint64(quorum // BUILDER_PAYMENT_THRESHOLD_DENOMINATOR)
// </spec>
func builderQuorumThreshold(state state.ReadOnlyBeaconState) (primitives.Gwei, error) { func builderQuorumThreshold(state state.ReadOnlyBeaconState) (primitives.Gwei, error) {
activeBalance, err := helpers.TotalActiveBalance(state) activeBalance, err := helpers.TotalActiveBalance(state)
if err != nil { if err != nil {

View File

@@ -11,16 +11,20 @@ import (
) )
// RemoveBuilderPendingPayment removes the pending builder payment for the proposal slot. // RemoveBuilderPendingPayment removes the pending builder payment for the proposal slot.
// Spec v1.7.0 (pseudocode):
// //
// <spec fn="process_proposer_slashing" fork="gloas" lines="22-32" hash="4da721ef">
// # [New in Gloas:EIP7732]
// # Remove the BuilderPendingPayment corresponding to
// # this proposal if it is still in the 2-epoch window.
// slot = header_1.slot // slot = header_1.slot
// proposal_epoch = compute_epoch_at_slot(slot) // proposal_epoch = compute_epoch_at_slot(slot)
// if proposal_epoch == get_current_epoch(state): // if proposal_epoch == get_current_epoch(state):
// payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH // payment_index = SLOTS_PER_EPOCH + slot % SLOTS_PER_EPOCH
// state.builder_pending_payments[payment_index] = BuilderPendingPayment() // state.builder_pending_payments[payment_index] = BuilderPendingPayment()
// elif proposal_epoch == get_previous_epoch(state): // elif proposal_epoch == get_previous_epoch(state):
// payment_index = slot % SLOTS_PER_EPOCH // payment_index = slot % SLOTS_PER_EPOCH
// state.builder_pending_payments[payment_index] = BuilderPendingPayment() // state.builder_pending_payments[payment_index] = BuilderPendingPayment()
// </spec>
func RemoveBuilderPendingPayment(st state.BeaconState, header *eth.BeaconBlockHeader) error { func RemoveBuilderPendingPayment(st state.BeaconState, header *eth.BeaconBlockHeader) error {
proposalEpoch := slots.ToEpoch(header.Slot) proposalEpoch := slots.ToEpoch(header.Slot)
currentEpoch := time.CurrentEpoch(st) currentEpoch := time.CurrentEpoch(st)

View File

@@ -143,10 +143,11 @@ func ProcessSlot(ctx context.Context, state state.BeaconState) (state.BeaconStat
return nil, err return nil, err
} }
// Spec v1.6.1 (pseudocode): // <spec fn="process_slot" fork="gloas" lines="11-13" hash="62b28839">
// # [New in Gloas:EIP7732] // # [New in Gloas:EIP7732]
// # Unset the next payload availability // # Unset the next payload availability
// state.execution_payload_availability[(state.slot + 1) % SLOTS_PER_HISTORICAL_ROOT] = 0b0 // state.execution_payload_availability[(state.slot + 1) % SLOTS_PER_HISTORICAL_ROOT] = 0b0
// </spec>
if state.Version() >= version.Gloas { if state.Version() >= version.Gloas {
index := uint64((state.Slot() + 1) % params.BeaconConfig().SlotsPerHistoricalRoot) index := uint64((state.Slot() + 1) % params.BeaconConfig().SlotsPerHistoricalRoot)
if err := state.UpdateExecutionPayloadAvailabilityAtIndex(index, 0x0); err != nil { if err := state.UpdateExecutionPayloadAvailabilityAtIndex(index, 0x0); err != nil {

View File

@@ -78,7 +78,7 @@ func newGloasState(t *testing.T, slot primitives.Slot, availability []byte) stat
BlockHash: make([]byte, 32), BlockHash: make([]byte, 32),
PrevRandao: make([]byte, 32), PrevRandao: make([]byte, 32),
FeeRecipient: make([]byte, 20), FeeRecipient: make([]byte, 20),
BlobKzgCommitmentsRoot: make([]byte, 32), BlobKzgCommitments: [][]byte{make([]byte, 48)},
}, },
Eth1Data: &ethpb.Eth1Data{ Eth1Data: &ethpb.Eth1Data{
DepositRoot: make([]byte, 32), DepositRoot: make([]byte, 32),

View File

@@ -2,6 +2,7 @@ package kv
import ( import (
"context" "context"
"slices"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil" "github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
@@ -33,6 +34,9 @@ func (s *Store) LastArchivedRoot(ctx context.Context) [32]byte {
if err := s.db.View(func(tx *bolt.Tx) error { if err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(stateSlotIndicesBucket) bkt := tx.Bucket(stateSlotIndicesBucket)
_, blockRoot = bkt.Cursor().Last() _, blockRoot = bkt.Cursor().Last()
if len(blockRoot) > 0 {
blockRoot = slices.Clone(blockRoot)
}
return nil return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity. }); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err) // lint:nopanic -- View never returns an error. panic(err) // lint:nopanic -- View never returns an error.
@@ -51,6 +55,9 @@ func (s *Store) ArchivedPointRoot(ctx context.Context, slot primitives.Slot) [32
if err := s.db.View(func(tx *bolt.Tx) error { if err := s.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(stateSlotIndicesBucket) bucket := tx.Bucket(stateSlotIndicesBucket)
blockRoot = bucket.Get(bytesutil.SlotToBytesBigEndian(slot)) blockRoot = bucket.Get(bytesutil.SlotToBytesBigEndian(slot))
if len(blockRoot) > 0 {
blockRoot = slices.Clone(blockRoot)
}
return nil return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity. }); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err) // lint:nopanic -- View never returns an error. panic(err) // lint:nopanic -- View never returns an error.

View File

@@ -812,7 +812,10 @@ func (s *Store) FeeRecipientByValidatorID(ctx context.Context, id primitives.Val
var addr []byte var addr []byte
err := s.db.View(func(tx *bolt.Tx) error { err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(feeRecipientBucket) bkt := tx.Bucket(feeRecipientBucket)
addr = bkt.Get(bytesutil.Uint64ToBytesBigEndian(uint64(id))) stored := bkt.Get(bytesutil.Uint64ToBytesBigEndian(uint64(id)))
if len(stored) > 0 {
addr = slices.Clone(stored)
}
// IF the fee recipient is not found in the standard fee recipient bucket, then // IF the fee recipient is not found in the standard fee recipient bucket, then
// check the registration bucket. The fee recipient may be there. // check the registration bucket. The fee recipient may be there.
// This is to resolve imcompatility until we fully migrate to the registration bucket. // This is to resolve imcompatility until we fully migrate to the registration bucket.
@@ -826,7 +829,7 @@ func (s *Store) FeeRecipientByValidatorID(ctx context.Context, id primitives.Val
if err := decode(ctx, enc, reg); err != nil { if err := decode(ctx, enc, reg); err != nil {
return err return err
} }
addr = reg.FeeRecipient addr = slices.Clone(reg.FeeRecipient)
} }
return nil return nil
}) })

View File

@@ -3,6 +3,7 @@ package kv
import ( import (
"context" "context"
"fmt" "fmt"
"slices"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace" "github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@@ -17,7 +18,10 @@ func (s *Store) DepositContractAddress(ctx context.Context) ([]byte, error) {
var addr []byte var addr []byte
if err := s.db.View(func(tx *bolt.Tx) error { if err := s.db.View(func(tx *bolt.Tx) error {
chainInfo := tx.Bucket(chainMetadataBucket) chainInfo := tx.Bucket(chainMetadataBucket)
addr = chainInfo.Get(depositContractAddressKey) stored := chainInfo.Get(depositContractAddressKey)
if len(stored) > 0 {
addr = slices.Clone(stored)
}
return nil return nil
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity. }); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
panic(err) // lint:nopanic -- View never returns an error. panic(err) // lint:nopanic -- View never returns an error.

View File

@@ -26,6 +26,15 @@ var ErrNotFoundMetadataSeqNum = errors.Wrap(ErrNotFound, "metadata sequence numb
// but the database was created without state-diff support. // but the database was created without state-diff support.
var ErrStateDiffIncompatible = errors.New("state-diff feature enabled but database was created without state-diff support") var ErrStateDiffIncompatible = errors.New("state-diff feature enabled but database was created without state-diff support")
// ErrStateDiffCorrupted is returned when state-diff metadata or data is missing or invalid.
var ErrStateDiffCorrupted = errors.New("state-diff database corrupted")
// ErrStateDiffExponentMismatch is returned when configured exponents differ from stored metadata.
var ErrStateDiffExponentMismatch = errors.New("state-diff exponents mismatch")
// ErrStateDiffMissingSnapshot is returned when the offset snapshot is missing.
var ErrStateDiffMissingSnapshot = errors.New("state-diff offset snapshot missing")
var errEmptyBlockSlice = errors.New("[]blocks.ROBlock is empty") var errEmptyBlockSlice = errors.New("[]blocks.ROBlock is empty")
var errIncorrectBlockParent = errors.New("unexpected missing or forked blocks in a []ROBlock") var errIncorrectBlockParent = errors.New("unexpected missing or forked blocks in a []ROBlock")
var errFinalizedChildNotFound = errors.New("unable to find finalized root descending from backfill batch") var errFinalizedChildNotFound = errors.New("unable to find finalized root descending from backfill batch")

View File

@@ -7,9 +7,11 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"slices"
"time" "time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db/iface" "github.com/OffchainLabs/prysm/v7/beacon-chain/db/iface"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/config/features" "github.com/OffchainLabs/prysm/v7/config/features"
"github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -21,6 +23,7 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
prombolt "github.com/prysmaticlabs/prombbolt" prombolt "github.com/prysmaticlabs/prombbolt"
logrus "github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
@@ -223,8 +226,42 @@ func (kv *Store) startStateDiff(ctx context.Context) error {
} }
if hasOffset { if hasOffset {
// Existing state-diff database - restarts not yet supported. storedExponents, err := kv.loadStateDiffExponents()
return errors.New("restarting with existing state-diff database not yet supported") if err != nil {
return fmt.Errorf("%w: state-diff metadata missing or invalid; re-sync required: %v", ErrStateDiffCorrupted, err)
}
currentExponents := flags.Get().StateDiffExponents
if !slices.Equal(storedExponents, currentExponents) {
return errors.Wrapf(
ErrStateDiffExponentMismatch,
"state-diff exponents changed; database incompatible. "+
"Database was initialized with: %v. "+
"Current configuration: %v. "+
"Options: use original exponents (--state-diff-exponents=%s) or delete database and re-sync from genesis/checkpoint.",
storedExponents,
currentExponents,
formatStateDiffExponents(storedExponents),
)
}
offset, err := kv.loadOffset()
if err != nil {
return err
}
cache, err := populateStateDiffCacheFromDB(kv, offset)
if err != nil {
return err
}
kv.stateDiffCache = cache
if flags.Get().StateDiffValidateOnStartup {
if err := validateStateDiffCache(ctx, kv, cache); err != nil {
return err
}
}
log.WithFields(logrus.Fields{
"offset": offset,
"exponents": storedExponents,
}).Info("State-diff cache initialized from existing database")
return nil
} }
// Check if this is a new database (no head block). // Check if this is a new database (no head block).

View File

@@ -3,12 +3,15 @@ package kv
import ( import (
"bytes" "bytes"
"context" "context"
"encoding/binary"
"fmt" "fmt"
"testing" "testing"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/config/features" "github.com/OffchainLabs/prysm/v7/config/features"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" 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/require"
"github.com/OffchainLabs/prysm/v7/testing/util" "github.com/OffchainLabs/prysm/v7/testing/util"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
@@ -27,6 +30,108 @@ func setupDB(t testing.TB) *Store {
return db return db
} }
func TestStartStateDiff_ExponentMismatch(t *testing.T) {
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
defer resetCfg()
setDefaultStateDiffExponents()
store := setupDB(t)
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bolt.ErrBucketNotFound
}
offsetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(offsetBytes, 0)
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
return err
}
encoded, err := encodeStateDiffExponents([]int{20, 10})
if err != nil {
return err
}
return bucket.Put(exponentsKey, encoded)
}))
ctx := t.Context()
err := store.startStateDiff(ctx)
require.ErrorContains(t, "state-diff exponents changed", err)
}
func TestStartStateDiff_MissingOffsetSnapshot(t *testing.T) {
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
defer resetCfg()
setDefaultStateDiffExponents()
store := setupDB(t)
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bolt.ErrBucketNotFound
}
offsetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(offsetBytes, 0)
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
return err
}
encoded, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
if err != nil {
return err
}
return bucket.Put(exponentsKey, encoded)
}))
ctx := t.Context()
err := store.startStateDiff(ctx)
require.ErrorContains(t, "missing offset snapshot", err)
}
func TestStartStateDiff_ValidateOnStartup(t *testing.T) {
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
defer resetCfg()
setDefaultStateDiffExponents()
globalFlags := flags.GlobalFlags{
StateDiffExponents: flags.Get().StateDiffExponents,
StateDiffValidateOnStartup: true,
}
flags.Init(&globalFlags)
store := setupDB(t)
require.NoError(t, store.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bolt.ErrBucketNotFound
}
st, _ := createState(t, 0, version.Phase0)
stateBytes, err := st.MarshalSSZ()
if err != nil {
return err
}
enc, err := addKey(st.Version(), stateBytes)
if err != nil {
return err
}
offsetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(offsetBytes, 0)
if err := bucket.Put(offsetKey, offsetBytes); err != nil {
return err
}
encoded, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
if err != nil {
return err
}
if err := bucket.Put(exponentsKey, encoded); err != nil {
return err
}
key := makeKeyForStateDiffTree(0, 0)
return bucket.Put(key, enc)
}))
err := store.startStateDiff(t.Context())
require.NoError(t, err)
}
func Test_setupBlockStorageType(t *testing.T) { func Test_setupBlockStorageType(t *testing.T) {
ctx := t.Context() ctx := t.Context()
t.Run("fresh database with feature enabled to store full blocks should store full blocks", func(t *testing.T) { t.Run("fresh database with feature enabled to store full blocks should store full blocks", func(t *testing.T) {

View File

@@ -199,7 +199,7 @@ func performValidatorStateMigration(ctx context.Context, bar *progressbar.Progre
func stateBucketKeys(stateBucket *bolt.Bucket) ([][]byte, error) { func stateBucketKeys(stateBucket *bolt.Bucket) ([][]byte, error) {
var keys [][]byte var keys [][]byte
if err := stateBucket.ForEach(func(pubKey, v []byte) error { if err := stateBucket.ForEach(func(pubKey, v []byte) error {
keys = append(keys, pubKey) keys = append(keys, bytes.Clone(pubKey))
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err

View File

@@ -1048,10 +1048,15 @@ func (s *Store) isStateValidatorMigrationOver() (bool, error) {
} }
func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) { func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
slot, err := s.SlotByBlockRoot(ctx, blockRoot) stateSummary, err := s.StateSummary(ctx, blockRoot)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if stateSummary == nil {
return nil, ErrNotFoundState
}
slot := stateSummary.Slot
if uint64(slot) < s.getOffset() { if uint64(slot) < s.getOffset() {
return nil, ErrSlotBeforeOffset return nil, ErrSlotBeforeOffset
@@ -1065,14 +1070,33 @@ func (s *Store) getStateUsingStateDiff(ctx context.Context, blockRoot [32]byte)
return nil, errors.New("state not found") return nil, errors.New("state not found")
} }
blk, err := s.Block(ctx, blockRoot)
if err != nil {
return nil, err
}
if blk != nil && !blk.IsNil() {
stateRoot, err := st.HashTreeRoot(ctx)
if err != nil {
return nil, err
}
if stateRoot != blk.Block().StateRoot() {
return nil, errors.Wrap(ErrNotFoundState, "state root mismatch for block")
}
}
return st, nil return st, nil
} }
func (s *Store) hasStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (bool, error) { func (s *Store) hasStateUsingStateDiff(ctx context.Context, blockRoot [32]byte) (bool, error) {
slot, err := s.SlotByBlockRoot(ctx, blockRoot) stateSummary, err := s.StateSummary(ctx, blockRoot)
if err != nil { if err != nil {
return false, err return false, err
} }
if stateSummary == nil {
return false, nil
}
slot := stateSummary.Slot
if uint64(slot) < s.getOffset() { if uint64(slot) < s.getOffset() {
return false, ErrSlotBeforeOffset return false, ErrSlotBeforeOffset

View File

@@ -2,6 +2,7 @@ package kv
import ( import (
"context" "context"
"slices"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags" "github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
@@ -132,6 +133,9 @@ func (s *Store) saveHdiff(lvl int, anchor, st state.ReadOnlyBeaconState) error {
return err return err
} }
} }
if err := s.stateDiffCache.setLevelHasData(lvl); err != nil {
return err
}
return nil return nil
} }
@@ -171,6 +175,9 @@ func (s *Store) saveFullSnapshot(st state.ReadOnlyBeaconState) error {
if err != nil { if err != nil {
return err return err
} }
if err := s.stateDiffCache.setLevelHasData(0); err != nil {
return err
}
return nil return nil
} }
@@ -187,20 +194,23 @@ func (s *Store) getDiff(lvl int, slot uint64) (hdiff.HdiffBytes, error) {
return bolt.ErrBucketNotFound return bolt.ErrBucketNotFound
} }
buf := append(key, stateSuffix...) buf := append(key, stateSuffix...)
stateDiff = bucket.Get(buf) rawStateDiff := bucket.Get(buf)
if stateDiff == nil { if len(rawStateDiff) == 0 {
return errors.New("state diff not found") return errors.New("state diff not found")
} }
stateDiff = slices.Clone(rawStateDiff)
buf = append(key, validatorSuffix...) buf = append(key, validatorSuffix...)
validatorDiff = bucket.Get(buf) rawValidatorDiff := bucket.Get(buf)
if validatorDiff == nil { if len(rawValidatorDiff) == 0 {
return errors.New("validator diff not found") return errors.New("validator diff not found")
} }
validatorDiff = slices.Clone(rawValidatorDiff)
buf = append(key, balancesSuffix...) buf = append(key, balancesSuffix...)
balancesDiff = bucket.Get(buf) rawBalancesDiff := bucket.Get(buf)
if balancesDiff == nil { if len(rawBalancesDiff) == 0 {
return errors.New("balances diff not found") return errors.New("balances diff not found")
} }
balancesDiff = slices.Clone(rawBalancesDiff)
return nil return nil
}) })
@@ -224,10 +234,11 @@ func (s *Store) getFullSnapshot(slot uint64) (state.BeaconState, error) {
if bucket == nil { if bucket == nil {
return bolt.ErrBucketNotFound return bolt.ErrBucketNotFound
} }
enc = bucket.Get(key) rawEnc := bucket.Get(key)
if enc == nil { if rawEnc == nil {
return errors.New("state not found") return errors.New("state not found")
} }
enc = slices.Clone(rawEnc)
return nil return nil
}) })

View File

@@ -1,19 +1,131 @@
package kv package kv
import ( import (
"context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"sync" "sync"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags" "github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
pkgerrors "github.com/pkg/errors"
"go.etcd.io/bbolt" "go.etcd.io/bbolt"
) )
type stateDiffCache struct { type stateDiffCache struct {
sync.RWMutex sync.RWMutex
anchors []state.ReadOnlyBeaconState anchors []state.ReadOnlyBeaconState
offset uint64 levelsWithData []bool
offset uint64
}
func populateStateDiffCacheFromDB(s *Store, offset uint64) (*stateDiffCache, error) {
cache := &stateDiffCache{
anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1),
levelsWithData: make([]bool, len(flags.Get().StateDiffExponents)),
offset: offset,
}
if err := s.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
for level := range cache.levelsWithData {
if level == 0 {
if bucket.Get(makeKeyForStateDiffTree(0, offset)) != nil {
cache.levelsWithData[level] = true
}
continue
}
cursor := bucket.Cursor()
prefix := []byte{byte(level)}
key, _ := cursor.Seek(prefix)
if key != nil && key[0] == byte(level) {
slot, ok := slotFromStateDiffKey(key)
if !ok {
return ErrStateDiffCorrupted
}
if slot < offset {
return ErrStateDiffCorrupted
}
if level == 0 && slot != offset {
return ErrStateDiffCorrupted
}
if computeLevel(offset, primitives.Slot(slot)) != level {
return ErrStateDiffCorrupted
}
cache.levelsWithData[level] = true
}
}
return nil
}); err != nil {
return nil, err
}
anchor0, err := s.getFullSnapshot(offset)
if err != nil {
return nil, pkgerrors.Wrapf(ErrStateDiffMissingSnapshot, "state diff cache: missing offset snapshot at %d", offset)
}
cache.anchors[0] = anchor0
cache.levelsWithData[0] = true
return cache, nil
}
func validateStateDiffCache(ctx context.Context, s *Store, cache *stateDiffCache) error {
for level, hasData := range cache.levelsWithData {
if !hasData || level == 0 {
continue
}
maxSlot, err := latestSlotForLevel(s, level)
if err != nil {
return err
}
if _, err := s.stateByDiff(ctx, primitives.Slot(maxSlot)); err != nil {
return pkgerrors.Wrapf(ErrStateDiffCorrupted, "state diff validation failed for level %d slot %d: %v", level, maxSlot, err)
}
}
return nil
}
func latestSlotForLevel(s *Store, level int) (uint64, error) {
var maxSlot uint64
found := false
err := s.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
cursor := bucket.Cursor()
prefix := []byte{byte(level)}
for key, _ := cursor.Seek(prefix); key != nil && key[0] == byte(level); key, _ = cursor.Next() {
slot, ok := slotFromStateDiffKey(key)
if !ok {
return ErrStateDiffCorrupted
}
if !found || slot > maxSlot {
maxSlot = slot
found = true
}
}
return nil
})
if err != nil {
return 0, err
}
if !found {
return 0, ErrStateDiffCorrupted
}
return maxSlot, nil
}
func slotFromStateDiffKey(key []byte) (uint64, bool) {
if len(key) < 9 {
return 0, false
}
return binary.LittleEndian.Uint64(key[1:9]), true
} }
func newStateDiffCache(s *Store) (*stateDiffCache, error) { func newStateDiffCache(s *Store) (*stateDiffCache, error) {
@@ -37,8 +149,9 @@ func newStateDiffCache(s *Store) (*stateDiffCache, error) {
} }
return &stateDiffCache{ return &stateDiffCache{
anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1), // -1 because last level doesn't need to be cached anchors: make([]state.ReadOnlyBeaconState, len(flags.Get().StateDiffExponents)-1), // -1 because last level doesn't need to be cached
offset: offset, levelsWithData: make([]bool, len(flags.Get().StateDiffExponents)),
offset: offset,
}, nil }, nil
} }
@@ -58,6 +171,25 @@ func (c *stateDiffCache) setAnchor(level int, anchor state.ReadOnlyBeaconState)
return nil return nil
} }
func (c *stateDiffCache) levelHasData(level int) bool {
c.RLock()
defer c.RUnlock()
if level < 0 || level >= len(c.levelsWithData) {
return false
}
return c.levelsWithData[level]
}
func (c *stateDiffCache) setLevelHasData(level int) error {
c.Lock()
defer c.Unlock()
if level < 0 || level >= len(c.levelsWithData) {
return errors.New("state diff cache: level data index out of range")
}
c.levelsWithData[level] = true
return nil
}
func (c *stateDiffCache) getOffset() uint64 { func (c *stateDiffCache) getOffset() uint64 {
c.RLock() c.RLock()
defer c.RUnlock() defer c.RUnlock()

View File

@@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
statenative "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native" statenative "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native"
@@ -21,9 +22,78 @@ import (
var ( var (
offsetKey = []byte("offset") offsetKey = []byte("offset")
exponentsKey = []byte("exponents")
ErrSlotBeforeOffset = errors.New("slot is before state-diff root offset") ErrSlotBeforeOffset = errors.New("slot is before state-diff root offset")
) )
func encodeStateDiffExponents(exponents []int) ([]byte, error) {
if len(exponents) == 0 {
return nil, errors.New("state diff exponents cannot be empty")
}
if len(exponents) > 255 {
return nil, fmt.Errorf("state diff exponents length %d exceeds max 255", len(exponents))
}
encoded := make([]byte, len(exponents)+1)
encoded[0] = byte(len(exponents))
for i, exp := range exponents {
if exp < 2 || exp > flags.MaxStateDiffExponent {
return nil, fmt.Errorf("state diff exponent %d out of range for encoding", exp)
}
encoded[i+1] = byte(exp)
}
return encoded, nil
}
func decodeStateDiffExponents(encoded []byte) ([]int, error) {
if len(encoded) == 0 {
return nil, errors.New("state diff exponents missing length prefix")
}
count := int(encoded[0])
if count == 0 {
return nil, errors.New("state diff exponents length cannot be zero")
}
if len(encoded) != count+1 {
return nil, fmt.Errorf("state diff exponents length mismatch: expected %d got %d", count, len(encoded)-1)
}
exponents := make([]int, count)
for i := range count {
exponents[i] = int(encoded[i+1])
}
return exponents, nil
}
func formatStateDiffExponents(exponents []int) string {
if len(exponents) == 0 {
return ""
}
parts := make([]string, len(exponents))
for i, exp := range exponents {
parts[i] = fmt.Sprintf("%d", exp)
}
return strings.Join(parts, ",")
}
func (s *Store) loadStateDiffExponents() ([]int, error) {
var encoded []byte
err := s.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
value := bucket.Get(exponentsKey)
if value == nil {
return errors.New("state diff exponents not found")
}
encoded = make([]byte, len(value))
copy(encoded, value)
return nil
})
if err != nil {
return nil, err
}
return decodeStateDiffExponents(encoded)
}
func makeKeyForStateDiffTree(level int, slot uint64) []byte { func makeKeyForStateDiffTree(level int, slot uint64) []byte {
buf := make([]byte, 16) buf := make([]byte, 16)
buf[0] = byte(level) buf[0] = byte(level)
@@ -124,6 +194,29 @@ func (s *Store) getOffset() uint64 {
return s.stateDiffCache.getOffset() return s.stateDiffCache.getOffset()
} }
func (s *Store) loadOffset() (uint64, error) {
var offset uint64
err := s.db.View(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
offsetBytes := bucket.Get(offsetKey)
if offsetBytes == nil {
return errors.New("state diff offset not found")
}
if len(offsetBytes) != 8 {
return fmt.Errorf("state diff offset has invalid length %d", len(offsetBytes))
}
offset = binary.LittleEndian.Uint64(offsetBytes)
return nil
})
if err != nil {
return 0, err
}
return offset, nil
}
// hasStateDiffOffset checks if the state-diff offset has been set in the database. // hasStateDiffOffset checks if the state-diff offset has been set in the database.
// This is used to detect if an existing database has state-diff enabled. // This is used to detect if an existing database has state-diff enabled.
func (s *Store) hasStateDiffOffset() (bool, error) { func (s *Store) hasStateDiffOffset() (bool, error) {
@@ -153,8 +246,13 @@ func (s *Store) initializeStateDiff(slot primitives.Slot, initialState state.Rea
return nil return nil
} }
} }
// Write offset directly to the database (without using cache which doesn't exist yet). exponentsBytes, err := encodeStateDiffExponents(flags.Get().StateDiffExponents)
err := s.db.Update(func(tx *bbolt.Tx) error { if err != nil {
return pkgerrors.Wrap(err, "failed to encode state diff exponents")
}
// Write metadata directly to the database (without using cache which doesn't exist yet).
err = s.db.Update(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket) bucket := tx.Bucket(stateDiffBucket)
if bucket == nil { if bucket == nil {
return bbolt.ErrBucketNotFound return bbolt.ErrBucketNotFound
@@ -162,7 +260,10 @@ func (s *Store) initializeStateDiff(slot primitives.Slot, initialState state.Rea
offsetBytes := make([]byte, 8) offsetBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(offsetBytes, uint64(slot)) binary.LittleEndian.PutUint64(offsetBytes, uint64(slot))
return bucket.Put(offsetKey, offsetBytes) if err := bucket.Put(offsetKey, offsetBytes); err != nil {
return err
}
return bucket.Put(exponentsKey, exponentsBytes)
}) })
if err != nil { if err != nil {
return pkgerrors.Wrap(err, "failed to set offset") return pkgerrors.Wrap(err, "failed to set offset")
@@ -286,15 +387,20 @@ func (s *Store) getBaseAndDiffChain(offset uint64, slot primitives.Slot) (state.
} }
var diffChainItems []diffItem var diffChainItems []diffItem
lastSeenAnchorSlot := baseAnchorSlot lastSeenAnchorRelSlot := baseAnchorSlot - offset
for i, exp := range exponents[1 : lvl+1] { for i, exp := range exponents[1 : lvl+1] {
span := math.PowerOf2(uint64(exp)) span := math.PowerOf2(uint64(exp))
diffSlot := rel / span * span diffSlot := rel / span * span
if diffSlot == lastSeenAnchorSlot { if diffSlot == lastSeenAnchorRelSlot {
continue continue
} }
diffChainItems = append(diffChainItems, diffItem{level: i + 1, slot: diffSlot + offset}) level := i + 1
lastSeenAnchorSlot = diffSlot if s.stateDiffCache != nil && !s.stateDiffCache.levelHasData(level) {
lastSeenAnchorRelSlot = diffSlot
continue
}
diffChainItems = append(diffChainItems, diffItem{level: level, slot: diffSlot + offset})
lastSeenAnchorRelSlot = diffSlot
} }
baseSnapshot, err := s.getFullSnapshot(baseAnchorSlot) baseSnapshot, err := s.getFullSnapshot(baseAnchorSlot)

View File

@@ -9,6 +9,7 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags" "github.com/OffchainLabs/prysm/v7/cmd/beacon-chain/flags"
"github.com/OffchainLabs/prysm/v7/config/features"
"github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/math" "github.com/OffchainLabs/prysm/v7/math"
@@ -34,6 +35,81 @@ func TestStateDiff_LoadOrInitOffset(t *testing.T) {
require.Equal(t, uint64(10), offset) require.Equal(t, uint64(10), offset)
} }
func TestStateDiff_LoadOffset(t *testing.T) {
setDefaultStateDiffExponents()
db := setupDB(t)
_, err := db.loadOffset()
require.ErrorContains(t, "offset not found", err)
err = setOffsetInDB(db, 10)
require.NoError(t, err)
offset, err := db.loadOffset()
require.NoError(t, err)
require.Equal(t, uint64(10), offset)
}
func TestStateDiff_EncodeDecodeExponents(t *testing.T) {
t.Run("roundtrip", func(t *testing.T) {
exponents := []int{21, 18, 16, 13}
encoded, err := encodeStateDiffExponents(exponents)
require.NoError(t, err)
decoded, err := decodeStateDiffExponents(encoded)
require.NoError(t, err)
require.DeepEqual(t, exponents, decoded)
})
t.Run("encode-empty", func(t *testing.T) {
_, err := encodeStateDiffExponents(nil)
require.ErrorContains(t, "cannot be empty", err)
})
t.Run("encode-negative", func(t *testing.T) {
_, err := encodeStateDiffExponents([]int{21, -1})
require.ErrorContains(t, "out of range", err)
})
t.Run("encode-too-large", func(t *testing.T) {
_, err := encodeStateDiffExponents([]int{flags.MaxStateDiffExponent + 1})
require.ErrorContains(t, "out of range", err)
})
t.Run("decode-empty", func(t *testing.T) {
_, err := decodeStateDiffExponents(nil)
require.ErrorContains(t, "missing length prefix", err)
})
t.Run("decode-zero-length", func(t *testing.T) {
_, err := decodeStateDiffExponents([]byte{0})
require.ErrorContains(t, "length cannot be zero", err)
})
t.Run("decode-length-mismatch", func(t *testing.T) {
_, err := decodeStateDiffExponents([]byte{2, 10})
require.ErrorContains(t, "length mismatch", err)
})
}
func TestStateDiff_InitializeStoresExponents(t *testing.T) {
setDefaultStateDiffExponents()
resetCfg := features.InitWithReset(&features.Flags{EnableStateDiff: true})
defer resetCfg()
db := setupDB(t)
st, _ := createState(t, 0, version.Phase0)
require.NoError(t, db.initializeStateDiff(0, st))
stored, err := db.loadStateDiffExponents()
require.NoError(t, err)
require.DeepEqual(t, flags.Get().StateDiffExponents, stored)
}
func TestStateDiff_LoadExponentsMissing(t *testing.T) {
db := setupDB(t)
_, err := db.loadStateDiffExponents()
require.ErrorContains(t, "exponents not found", err)
}
func TestStateDiff_ComputeLevel(t *testing.T) { func TestStateDiff_ComputeLevel(t *testing.T) {
db := setupDB(t) db := setupDB(t)
setDefaultStateDiffExponents() setDefaultStateDiffExponents()
@@ -154,6 +230,124 @@ func TestStateDiff_SaveFullSnapshot(t *testing.T) {
} }
} }
func TestStateDiff_StateByDiff_NonZeroOffsetSkipsRedundantLevelDiff(t *testing.T) {
setStateDiffExponents([]int{6, 5, 4})
db := setupDB(t)
offset := uint64(1000)
require.NoError(t, setOffsetInDB(db, offset))
stOffset, _ := createState(t, primitives.Slot(offset), version.Phase0)
require.NoError(t, db.saveStateByDiff(context.Background(), stOffset))
st32, _ := createState(t, primitives.Slot(offset+32), version.Phase0)
require.NoError(t, db.saveStateByDiff(context.Background(), st32))
st64, _ := createState(t, primitives.Slot(offset+64), version.Phase0)
require.NoError(t, db.saveStateByDiff(context.Background(), st64))
st80, _ := createState(t, primitives.Slot(offset+80), version.Phase0)
require.NoError(t, db.saveStateByDiff(context.Background(), st80))
readSt, err := db.stateByDiff(context.Background(), primitives.Slot(offset+80))
require.NoError(t, err)
stWantSSZ, err := st80.MarshalSSZ()
require.NoError(t, err)
stGotSSZ, err := readSt.MarshalSSZ()
require.NoError(t, err)
require.DeepSSZEqual(t, stWantSSZ, stGotSSZ)
}
func TestStateDiff_PopulateStateDiffCacheFromDB(t *testing.T) {
setDefaultStateDiffExponents()
db := setupDB(t)
_, err := populateStateDiffCacheFromDB(db, 0)
require.ErrorContains(t, "missing offset snapshot", err)
st, _ := createState(t, 0, version.Phase0)
require.NoError(t, setOffsetInDB(db, 0))
require.NoError(t, db.saveStateByDiff(context.Background(), st))
err = db.db.Update(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
key := makeKeyForStateDiffTree(2, math.PowerOf2(16))
return bucket.Put(append(key, stateSuffix...), []byte{1})
})
require.NoError(t, err)
cache, err := populateStateDiffCacheFromDB(db, 0)
require.NoError(t, err)
require.NotNil(t, cache)
require.Equal(t, uint64(0), cache.getOffset())
require.NotNil(t, cache.getAnchor(0))
require.Equal(t, true, cache.levelHasData(0))
require.Equal(t, false, cache.levelHasData(1))
require.Equal(t, true, cache.levelHasData(2))
}
func TestStateDiff_PopulateStateDiffCacheFromDB_InvalidLevelKey(t *testing.T) {
setDefaultStateDiffExponents()
db := setupDB(t)
st, _ := createState(t, 0, version.Phase0)
require.NoError(t, setOffsetInDB(db, 0))
require.NoError(t, db.saveStateByDiff(context.Background(), st))
require.NoError(t, db.db.Update(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
key := makeKeyForStateDiffTree(2, 1)
return bucket.Put(append(key, stateSuffix...), []byte{1})
}))
_, err := populateStateDiffCacheFromDB(db, 0)
require.ErrorIs(t, ErrStateDiffCorrupted, err)
}
func TestStateDiff_GetBaseAndDiffChainSkipsEmptyLevels(t *testing.T) {
setDefaultStateDiffExponents()
db := setupDB(t)
require.NoError(t, setOffsetInDB(db, 0))
st, _ := createState(t, 0, version.Phase0)
require.NoError(t, db.saveFullSnapshot(st))
cache, err := populateStateDiffCacheFromDB(db, 0)
require.NoError(t, err)
cache.levelsWithData[0] = true
cache.levelsWithData[1] = false
cache.levelsWithData[2] = true
db.stateDiffCache = cache
slot := primitives.Slot(math.PowerOf2(18) + math.PowerOf2(16))
key := makeKeyForStateDiffTree(2, uint64(slot))
require.NoError(t, db.db.Update(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(stateDiffBucket)
if bucket == nil {
return bbolt.ErrBucketNotFound
}
if err := bucket.Put(append(key, stateSuffix...), []byte{1}); err != nil {
return err
}
if err := bucket.Put(append(key, validatorSuffix...), []byte{2}); err != nil {
return err
}
return bucket.Put(append(key, balancesSuffix...), []byte{3})
}))
_, diffChain, err := db.getBaseAndDiffChain(0, slot)
require.NoError(t, err)
require.Equal(t, 1, len(diffChain))
}
func TestStateDiff_SaveAndReadFullSnapshot(t *testing.T) { func TestStateDiff_SaveAndReadFullSnapshot(t *testing.T) {
setDefaultStateDiffExponents() setDefaultStateDiffExponents()
@@ -659,8 +853,10 @@ func setOffsetInDB(s *Store, offset uint64) error {
} }
func setDefaultStateDiffExponents() { func setDefaultStateDiffExponents() {
globalFlags := flags.GlobalFlags{ setStateDiffExponents([]int{21, 18, 16, 13, 11, 9, 5})
StateDiffExponents: []int{21, 18, 16, 13, 11, 9, 5}, }
}
func setStateDiffExponents(exponents []int) {
globalFlags := flags.GlobalFlags{StateDiffExponents: exponents}
flags.Init(&globalFlags) flags.Init(&globalFlags)
} }

View File

@@ -2,6 +2,7 @@ package kv
import ( import (
"context" "context"
"slices"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil" "github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace" "github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
@@ -47,7 +48,11 @@ func (s *Store) StateSummary(ctx context.Context, blockRoot [32]byte) (*ethpb.St
} }
var enc []byte var enc []byte
if err := s.db.View(func(tx *bolt.Tx) error { if err := s.db.View(func(tx *bolt.Tx) error {
enc = tx.Bucket(stateSummaryBucket).Get(blockRoot[:]) rawEnc := tx.Bucket(stateSummaryBucket).Get(blockRoot[:])
if len(rawEnc) == 0 {
return nil
}
enc = slices.Clone(rawEnc)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err

View File

@@ -26,7 +26,6 @@ import (
"github.com/OffchainLabs/prysm/v7/testing/assert" "github.com/OffchainLabs/prysm/v7/testing/assert"
"github.com/OffchainLabs/prysm/v7/testing/require" "github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util" "github.com/OffchainLabs/prysm/v7/testing/util"
logTest "github.com/sirupsen/logrus/hooks/test"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
) )
@@ -1349,7 +1348,7 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
readSt, err := db.State(context.Background(), [32]byte{'A'}) readSt, err := db.State(context.Background(), [32]byte{'A'})
require.IsNil(t, readSt) require.IsNil(t, readSt)
require.ErrorContains(t, "neither state summary nor block found", err) require.ErrorIs(t, err, ErrNotFoundState)
}) })
t.Run("Slot not in tree", func(t *testing.T) { t.Run("Slot not in tree", func(t *testing.T) {
@@ -1477,14 +1476,8 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
readSt, err := db.State(context.Background(), r) readSt, err := db.State(context.Background(), r)
require.NoError(t, err) require.ErrorIs(t, err, ErrNotFoundState)
require.NotNil(t, readSt) require.IsNil(t, readSt)
stSSZ, err := st.MarshalSSZ()
require.NoError(t, err)
readStSSZ, err := readSt.MarshalSSZ()
require.NoError(t, err)
require.DeepSSZEqual(t, stSSZ, readStSSZ)
}) })
} }
}) })
@@ -1578,14 +1571,8 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
readSt, err := db.State(context.Background(), r) readSt, err := db.State(context.Background(), r)
require.NoError(t, err) require.ErrorIs(t, err, ErrNotFoundState)
require.NotNil(t, readSt) require.IsNil(t, readSt)
stSSZ, err := st.MarshalSSZ()
require.NoError(t, err)
readStSSZ, err := readSt.MarshalSSZ()
require.NoError(t, err)
require.DeepSSZEqual(t, stSSZ, readStSSZ)
}) })
} }
}) })
@@ -1594,7 +1581,6 @@ func TestStore_CanSaveRetrieveStateUsingStateDiff(t *testing.T) {
func TestStore_HasStateUsingStateDiff(t *testing.T) { func TestStore_HasStateUsingStateDiff(t *testing.T) {
t.Run("No state summary or block", func(t *testing.T) { t.Run("No state summary or block", func(t *testing.T) {
hook := logTest.NewGlobal()
db := setupDB(t) db := setupDB(t)
featCfg := &features.Flags{} featCfg := &features.Flags{}
featCfg.EnableStateDiff = true featCfg.EnableStateDiff = true
@@ -1607,7 +1593,6 @@ func TestStore_HasStateUsingStateDiff(t *testing.T) {
hasSt := db.HasState(t.Context(), [32]byte{'A'}) hasSt := db.HasState(t.Context(), [32]byte{'A'})
require.Equal(t, false, hasSt) require.Equal(t, false, hasSt)
require.LogsContain(t, hook, "neither state summary nor block found")
}) })
t.Run("slot in tree or not", func(t *testing.T) { t.Run("slot in tree or not", func(t *testing.T) {

View File

@@ -6,7 +6,6 @@ go_library(
"doc.go", "doc.go",
"errors.go", "errors.go",
"forkchoice.go", "forkchoice.go",
"last_root.go",
"log.go", "log.go",
"metrics.go", "metrics.go",
"node.go", "node.go",
@@ -51,7 +50,6 @@ go_test(
srcs = [ srcs = [
"ffg_update_test.go", "ffg_update_test.go",
"forkchoice_test.go", "forkchoice_test.go",
"last_root_test.go",
"no_vote_test.go", "no_vote_test.go",
"node_test.go", "node_test.go",
"on_tick_test.go", "on_tick_test.go",

View File

@@ -32,7 +32,6 @@ func New() *ForkChoice {
finalizedCheckpoint: &forkchoicetypes.Checkpoint{}, finalizedCheckpoint: &forkchoicetypes.Checkpoint{},
proposerBoostRoot: [32]byte{}, proposerBoostRoot: [32]byte{},
nodeByRoot: make(map[[fieldparams.RootLength]byte]*Node), nodeByRoot: make(map[[fieldparams.RootLength]byte]*Node),
nodeByPayload: make(map[[fieldparams.RootLength]byte]*Node),
slashedIndices: make(map[primitives.ValidatorIndex]bool), slashedIndices: make(map[primitives.ValidatorIndex]bool),
receivedBlocksLastEpoch: [fieldparams.SlotsPerEpoch]primitives.Slot{}, receivedBlocksLastEpoch: [fieldparams.SlotsPerEpoch]primitives.Slot{},
} }

View File

@@ -1,26 +0,0 @@
package doublylinkedtree
import (
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/time/slots"
)
// LastRoot returns the last canonical block root in the given epoch
func (f *ForkChoice) LastRoot(epoch primitives.Epoch) [32]byte {
head := f.store.headNode
headEpoch := slots.ToEpoch(head.slot)
epochEnd, err := slots.EpochEnd(epoch)
if err != nil {
return [32]byte{}
}
if headEpoch <= epoch {
return head.root
}
for head != nil && head.slot > epochEnd {
head = head.parent
}
if head == nil {
return [32]byte{}
}
return head.root
}

View File

@@ -1,38 +0,0 @@
package doublylinkedtree
import (
"testing"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/testing/require"
)
func TestLastRoot(t *testing.T) {
f := setup(0, 0)
ctx := t.Context()
st, root, err := prepareForkchoiceState(ctx, 1, [32]byte{'1'}, params.BeaconConfig().ZeroHash, [32]byte{'1'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'2'}, [32]byte{'1'}, [32]byte{'2'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 3, [32]byte{'3'}, [32]byte{'1'}, [32]byte{'3'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 32, [32]byte{'4'}, [32]byte{'3'}, [32]byte{'4'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 33, [32]byte{'5'}, [32]byte{'2'}, [32]byte{'5'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
st, root, err = prepareForkchoiceState(ctx, 34, [32]byte{'6'}, [32]byte{'5'}, [32]byte{'6'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
headNode := f.store.nodeByRoot[[32]byte{'6'}]
f.store.headNode = headNode
require.Equal(t, [32]byte{'6'}, f.store.headNode.root)
require.Equal(t, [32]byte{'2'}, f.LastRoot(0))
require.Equal(t, [32]byte{'6'}, f.LastRoot(1))
require.Equal(t, [32]byte{'6'}, f.LastRoot(2))
}

View File

@@ -94,6 +94,5 @@ func (s *Store) removeNodeAndChildren(ctx context.Context, node *Node, invalidRo
s.previousProposerBoostScore = 0 s.previousProposerBoostScore = 0
} }
delete(s.nodeByRoot, node.root) delete(s.nodeByRoot, node.root)
delete(s.nodeByPayload, node.payloadHash)
return invalidRoots, nil return invalidRoots, nil
} }

View File

@@ -113,7 +113,6 @@ func (s *Store) insert(ctx context.Context,
} }
} }
s.nodeByPayload[payloadHash] = n
s.nodeByRoot[root] = n s.nodeByRoot[root] = n
if parent == nil { if parent == nil {
if s.treeRootNode == nil { if s.treeRootNode == nil {
@@ -122,7 +121,6 @@ func (s *Store) insert(ctx context.Context,
s.highestReceivedNode = n s.highestReceivedNode = n
} else { } else {
delete(s.nodeByRoot, root) delete(s.nodeByRoot, root)
delete(s.nodeByPayload, payloadHash)
return nil, errInvalidParentRoot return nil, errInvalidParentRoot
} }
} else { } else {
@@ -191,7 +189,6 @@ func (s *Store) pruneFinalizedNodeByRootMap(ctx context.Context, node, finalized
node.children = nil node.children = nil
delete(s.nodeByRoot, node.root) delete(s.nodeByRoot, node.root)
delete(s.nodeByPayload, node.payloadHash)
return nil return nil
} }
@@ -273,21 +270,6 @@ func (f *ForkChoice) HighestReceivedBlockSlot() primitives.Slot {
return f.store.highestReceivedNode.slot return f.store.highestReceivedNode.slot
} }
// HighestReceivedBlockDelay returns the number of slots that the highest
// received block was late when receiving it. For example, a block was late by 12 slots,
// then this method is expected to return 12.
func (f *ForkChoice) HighestReceivedBlockDelay() primitives.Slot {
n := f.store.highestReceivedNode
if n == nil {
return 0
}
sss, err := slots.SinceSlotStart(n.slot, f.store.genesisTime, n.timestamp)
if err != nil {
return 0
}
return primitives.Slot(uint64(sss/time.Second) / params.BeaconConfig().SecondsPerSlot)
}
// ReceivedBlocksLastEpoch returns the number of blocks received in the last epoch // ReceivedBlocksLastEpoch returns the number of blocks received in the last epoch
func (f *ForkChoice) ReceivedBlocksLastEpoch() (uint64, error) { func (f *ForkChoice) ReceivedBlocksLastEpoch() (uint64, error) {
count := uint64(0) count := uint64(0)

View File

@@ -128,10 +128,9 @@ func TestStore_Insert(t *testing.T) {
// The new node does not have a parent. // The new node does not have a parent.
treeRootNode := &Node{slot: 0, root: indexToHash(0)} treeRootNode := &Node{slot: 0, root: indexToHash(0)}
nodeByRoot := map[[32]byte]*Node{indexToHash(0): treeRootNode} nodeByRoot := map[[32]byte]*Node{indexToHash(0): treeRootNode}
nodeByPayload := map[[32]byte]*Node{indexToHash(0): treeRootNode}
jc := &forkchoicetypes.Checkpoint{Epoch: 0} jc := &forkchoicetypes.Checkpoint{Epoch: 0}
fc := &forkchoicetypes.Checkpoint{Epoch: 0} fc := &forkchoicetypes.Checkpoint{Epoch: 0}
s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode, nodeByPayload: nodeByPayload, justifiedCheckpoint: jc, finalizedCheckpoint: fc, highestReceivedNode: &Node{}} s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode, justifiedCheckpoint: jc, finalizedCheckpoint: fc, highestReceivedNode: &Node{}}
payloadHash := [32]byte{'a'} payloadHash := [32]byte{'a'}
ctx := t.Context() ctx := t.Context()
_, blk, err := prepareForkchoiceState(ctx, 100, indexToHash(100), indexToHash(0), payloadHash, 1, 1) _, blk, err := prepareForkchoiceState(ctx, 100, indexToHash(100), indexToHash(0), payloadHash, 1, 1)
@@ -238,7 +237,6 @@ func TestStore_Prune_NoDanglingBranch(t *testing.T) {
s.finalizedCheckpoint.Root = indexToHash(1) s.finalizedCheckpoint.Root = indexToHash(1)
require.NoError(t, s.prune(t.Context())) require.NoError(t, s.prune(t.Context()))
require.Equal(t, len(s.nodeByRoot), 1) require.Equal(t, len(s.nodeByRoot), 1)
require.Equal(t, len(s.nodeByPayload), 1)
} }
// This test starts with the following branching diagram // This test starts with the following branching diagram
@@ -319,8 +317,6 @@ func TestStore_PruneMapsNodes(t *testing.T) {
s.finalizedCheckpoint.Root = indexToHash(1) s.finalizedCheckpoint.Root = indexToHash(1)
require.NoError(t, s.prune(t.Context())) require.NoError(t, s.prune(t.Context()))
require.Equal(t, len(s.nodeByRoot), 1) require.Equal(t, len(s.nodeByRoot), 1)
require.Equal(t, len(s.nodeByPayload), 1)
} }
func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) { func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
@@ -339,7 +335,6 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(1), count) require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockSlot()) require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockSlot())
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())
// 64 // 64
// Received block last epoch is 1 // Received block last epoch is 1
@@ -352,7 +347,6 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(1), count) require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(64), f.HighestReceivedBlockSlot()) require.Equal(t, primitives.Slot(64), f.HighestReceivedBlockSlot())
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())
// 64 65 // 64 65
// Received block last epoch is 2 // Received block last epoch is 2
@@ -365,7 +359,6 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(2), count) require.Equal(t, uint64(2), count)
require.Equal(t, primitives.Slot(65), f.HighestReceivedBlockSlot()) require.Equal(t, primitives.Slot(65), f.HighestReceivedBlockSlot())
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockDelay())
// 64 65 66 // 64 65 66
// Received block last epoch is 3 // Received block last epoch is 3
@@ -717,17 +710,3 @@ func TestStore_CleanupInserting(t *testing.T) {
require.NotNil(t, f.InsertNode(ctx, st, blk)) require.NotNil(t, f.InsertNode(ctx, st, blk))
require.Equal(t, false, f.HasNode(blk.Root())) require.Equal(t, false, f.HasNode(blk.Root()))
} }
func TestStore_HighestReceivedBlockDelay(t *testing.T) {
f := ForkChoice{
store: &Store{
genesisTime: time.Unix(0, 0),
highestReceivedNode: &Node{
slot: 10,
timestamp: time.Unix(int64(((10 + 12) * params.BeaconConfig().SecondsPerSlot)), 0), // 12 slots late
},
},
}
require.Equal(t, primitives.Slot(12), f.HighestReceivedBlockDelay())
}

View File

@@ -36,7 +36,6 @@ type Store struct {
treeRootNode *Node // the root node of the store tree. treeRootNode *Node // the root node of the store tree.
headNode *Node // last head Node headNode *Node // last head Node
nodeByRoot map[[fieldparams.RootLength]byte]*Node // nodes indexed by roots. nodeByRoot map[[fieldparams.RootLength]byte]*Node // nodes indexed by roots.
nodeByPayload map[[fieldparams.RootLength]byte]*Node // nodes indexed by payload Hash
slashedIndices map[primitives.ValidatorIndex]bool // the list of equivocating validator indices slashedIndices map[primitives.ValidatorIndex]bool // the list of equivocating validator indices
originRoot [fieldparams.RootLength]byte // The genesis block root originRoot [fieldparams.RootLength]byte // The genesis block root
genesisTime time.Time genesisTime time.Time

View File

@@ -67,13 +67,11 @@ type FastGetter interface {
HasNode([32]byte) bool HasNode([32]byte) bool
HighestReceivedBlockSlot() primitives.Slot HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockRoot() [32]byte HighestReceivedBlockRoot() [32]byte
HighestReceivedBlockDelay() primitives.Slot
IsCanonical(root [32]byte) bool IsCanonical(root [32]byte) bool
IsOptimistic(root [32]byte) (bool, error) IsOptimistic(root [32]byte) (bool, error)
IsViableForCheckpoint(*forkchoicetypes.Checkpoint) (bool, error) IsViableForCheckpoint(*forkchoicetypes.Checkpoint) (bool, error)
JustifiedCheckpoint() *forkchoicetypes.Checkpoint JustifiedCheckpoint() *forkchoicetypes.Checkpoint
JustifiedPayloadBlockHash() [32]byte JustifiedPayloadBlockHash() [32]byte
LastRoot(primitives.Epoch) [32]byte
NodeCount() int NodeCount() int
PreviousJustifiedCheckpoint() *forkchoicetypes.Checkpoint PreviousJustifiedCheckpoint() *forkchoicetypes.Checkpoint
ProposerBoost() [fieldparams.RootLength]byte ProposerBoost() [fieldparams.RootLength]byte

View File

@@ -121,13 +121,6 @@ func (ro *ROForkChoice) HighestReceivedBlockRoot() [32]byte {
return ro.getter.HighestReceivedBlockRoot() return ro.getter.HighestReceivedBlockRoot()
} }
// HighestReceivedBlockDelay delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HighestReceivedBlockDelay() primitives.Slot {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.HighestReceivedBlockDelay()
}
// ReceivedBlocksLastEpoch delegates to the underlying forkchoice call, under a lock. // ReceivedBlocksLastEpoch delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) ReceivedBlocksLastEpoch() (uint64, error) { func (ro *ROForkChoice) ReceivedBlocksLastEpoch() (uint64, error) {
ro.l.RLock() ro.l.RLock()
@@ -163,13 +156,6 @@ func (ro *ROForkChoice) Slot(root [32]byte) (primitives.Slot, error) {
return ro.getter.Slot(root) return ro.getter.Slot(root)
} }
// LastRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) LastRoot(e primitives.Epoch) [32]byte {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.LastRoot(e)
}
// DependentRoot delegates to the underlying forkchoice call, under a lock. // DependentRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) { func (ro *ROForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock() ro.l.RLock()

View File

@@ -30,7 +30,6 @@ const (
nodeCountCalled nodeCountCalled
highestReceivedBlockSlotCalled highestReceivedBlockSlotCalled
highestReceivedBlockRootCalled highestReceivedBlockRootCalled
highestReceivedBlockDelayCalled
receivedBlocksLastEpochCalled receivedBlocksLastEpochCalled
weightCalled weightCalled
isOptimisticCalled isOptimisticCalled
@@ -118,11 +117,6 @@ func TestROLocking(t *testing.T) {
call: highestReceivedBlockSlotCalled, call: highestReceivedBlockSlotCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockSlot() }, cb: func(g FastGetter) { g.HighestReceivedBlockSlot() },
}, },
{
name: "highestReceivedBlockDelayCalled",
call: highestReceivedBlockDelayCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockDelay() },
},
{ {
name: "receivedBlocksLastEpochCalled", name: "receivedBlocksLastEpochCalled",
call: receivedBlocksLastEpochCalled, call: receivedBlocksLastEpochCalled,
@@ -148,11 +142,6 @@ func TestROLocking(t *testing.T) {
call: slotCalled, call: slotCalled,
cb: func(g FastGetter) { _, err := g.Slot([32]byte{}); _discard(t, err) }, cb: func(g FastGetter) { _, err := g.Slot([32]byte{}); _discard(t, err) },
}, },
{
name: "lastRootCalled",
call: lastRootCalled,
cb: func(g FastGetter) { g.LastRoot(0) },
},
{ {
name: "targetRootForEpochCalled", name: "targetRootForEpochCalled",
call: targetRootForEpochCalled, call: targetRootForEpochCalled,
@@ -265,11 +254,6 @@ func (ro *mockROForkchoice) HighestReceivedBlockRoot() [32]byte {
return [32]byte{} return [32]byte{}
} }
func (ro *mockROForkchoice) HighestReceivedBlockDelay() primitives.Slot {
ro.calls = append(ro.calls, highestReceivedBlockDelayCalled)
return 0
}
func (ro *mockROForkchoice) ReceivedBlocksLastEpoch() (uint64, error) { func (ro *mockROForkchoice) ReceivedBlocksLastEpoch() (uint64, error) {
ro.calls = append(ro.calls, receivedBlocksLastEpochCalled) ro.calls = append(ro.calls, receivedBlocksLastEpochCalled)
return 0, nil return 0, nil
@@ -295,11 +279,6 @@ func (ro *mockROForkchoice) Slot(_ [32]byte) (primitives.Slot, error) {
return 0, nil return 0, nil
} }
func (ro *mockROForkchoice) LastRoot(_ primitives.Epoch) [32]byte {
ro.calls = append(ro.calls, lastRootCalled)
return [32]byte{}
}
// DependentRoot impoements FastGetter. // DependentRoot impoements FastGetter.
func (ro *mockROForkchoice) DependentRoot(_ primitives.Epoch) ([32]byte, error) { func (ro *mockROForkchoice) DependentRoot(_ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, dependentRootCalled) ro.calls = append(ro.calls, dependentRootCalled)

View File

@@ -550,6 +550,12 @@ func openDB(ctx context.Context, dbPath string, clearer *dbClearer) (*kv.Store,
cfg := features.Get() cfg := features.Get()
cfg.EnableStateDiff = false cfg.EnableStateDiff = false
features.Init(cfg) features.Init(cfg)
} else if errors.Is(err, kv.ErrStateDiffExponentMismatch) {
log.WithError(err).Error("State-diff configuration mismatch; restart aborted. Use the stored exponents or re-sync the database.")
return nil, err
} else if errors.Is(err, kv.ErrStateDiffMissingSnapshot) || errors.Is(err, kv.ErrStateDiffCorrupted) {
log.WithError(err).Error("State-diff database corrupted; restart aborted. Delete database and re-sync from genesis/checkpoint.")
return nil, err
} else if err != nil { } else if err != nil {
return nil, errors.Wrapf(err, "could not create database at %s", dbPath) return nil, errors.Wrapf(err, "could not create database at %s", dbPath)
} }

View File

@@ -25,6 +25,7 @@ var gossipTopicMappings = map[string]func() proto.Message{
LightClientOptimisticUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientOptimisticUpdateAltair{} }, LightClientOptimisticUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientOptimisticUpdateAltair{} },
LightClientFinalityUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientFinalityUpdateAltair{} }, LightClientFinalityUpdateTopicFormat: func() proto.Message { return &ethpb.LightClientFinalityUpdateAltair{} },
DataColumnSubnetTopicFormat: func() proto.Message { return &ethpb.DataColumnSidecar{} }, DataColumnSubnetTopicFormat: func() proto.Message { return &ethpb.DataColumnSidecar{} },
PayloadAttestationMessageTopicFormat: func() proto.Message { return &ethpb.PayloadAttestationMessage{} },
} }
// GossipTopicMappings is a function to return the assigned data type // GossipTopicMappings is a function to return the assigned data type
@@ -144,4 +145,7 @@ func init() {
// Specially handle Fulu objects. // Specially handle Fulu objects.
GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockFulu]()] = BlockSubnetTopicFormat GossipTypeMapping[reflect.TypeFor[*ethpb.SignedBeaconBlockFulu]()] = BlockSubnetTopicFormat
// Payload attestation messages.
GossipTypeMapping[reflect.TypeFor[*ethpb.PayloadAttestationMessage]()] = PayloadAttestationMessageTopicFormat
} }

View File

@@ -138,6 +138,9 @@ func connect(a, b host.Host) error {
func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) { func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{})) h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(p.t, err) require.NoError(p.t, err)
p.t.Cleanup(func() {
require.NoError(p.t, h.Close())
})
if err := connect(h, p.BHost); err != nil { if err := connect(h, p.BHost); err != nil {
p.t.Fatalf("Failed to connect two peers for RPC: %v", err) p.t.Fatalf("Failed to connect two peers for RPC: %v", err)
} }
@@ -169,6 +172,9 @@ func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) { func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) {
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{})) h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(p.t, err) require.NoError(p.t, err)
p.t.Cleanup(func() {
require.NoError(p.t, h.Close())
})
ps, err := pubsub.NewFloodSub(context.Background(), h, ps, err := pubsub.NewFloodSub(context.Background(), h,
pubsub.WithMessageSigning(false), pubsub.WithMessageSigning(false),
pubsub.WithStrictSignatureVerification(false), pubsub.WithStrictSignatureVerification(false),

View File

@@ -46,6 +46,8 @@ const (
GossipLightClientOptimisticUpdateMessage = "light_client_optimistic_update" GossipLightClientOptimisticUpdateMessage = "light_client_optimistic_update"
// GossipDataColumnSidecarMessage is the name for the data column sidecar message type. // GossipDataColumnSidecarMessage is the name for the data column sidecar message type.
GossipDataColumnSidecarMessage = "data_column_sidecar" GossipDataColumnSidecarMessage = "data_column_sidecar"
// GossipPayloadAttestationMessage is the name for the payload attestation message type.
GossipPayloadAttestationMessage = "payload_attestation_message"
// Topic Formats // Topic Formats
// //
@@ -75,6 +77,8 @@ const (
LightClientOptimisticUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientOptimisticUpdateMessage LightClientOptimisticUpdateTopicFormat = GossipProtocolAndDigest + GossipLightClientOptimisticUpdateMessage
// DataColumnSubnetTopicFormat is the topic format for the data column subnet. // DataColumnSubnetTopicFormat is the topic format for the data column subnet.
DataColumnSubnetTopicFormat = GossipProtocolAndDigest + GossipDataColumnSidecarMessage + "_%d" DataColumnSubnetTopicFormat = GossipProtocolAndDigest + GossipDataColumnSidecarMessage + "_%d"
// PayloadAttestationMessageTopicFormat is the topic format for payload attestation messages.
PayloadAttestationMessageTopicFormat = GossipProtocolAndDigest + GossipPayloadAttestationMessage
) )
// topic is a struct representing a single gossipsub topic. // topic is a struct representing a single gossipsub topic.
@@ -141,7 +145,7 @@ func (s *Service) allTopics() []topic {
cfg := params.BeaconConfig() cfg := params.BeaconConfig()
// bellatrix: no special topics; electra: blobs topics handled all together // bellatrix: no special topics; electra: blobs topics handled all together
genesis, altair, capella := cfg.GenesisEpoch, cfg.AltairForkEpoch, cfg.CapellaForkEpoch genesis, altair, capella := cfg.GenesisEpoch, cfg.AltairForkEpoch, cfg.CapellaForkEpoch
deneb, fulu, future := cfg.DenebForkEpoch, cfg.FuluForkEpoch, cfg.FarFutureEpoch deneb, fulu, gloas, future := cfg.DenebForkEpoch, cfg.FuluForkEpoch, cfg.GloasForkEpoch, cfg.FarFutureEpoch
// Templates are starter topics - they have a placeholder digest and the subnet is set to the maximum value // Templates are starter topics - they have a placeholder digest and the subnet is set to the maximum value
// for the subnet (see how this is used in allSubnetsBelow). These are not directly returned by the method, // for the subnet (see how this is used in allSubnetsBelow). These are not directly returned by the method,
// they are copied and modified for each digest where they apply based on the start and end epochs. // they are copied and modified for each digest where they apply based on the start and end epochs.
@@ -158,6 +162,7 @@ func (s *Service) allTopics() []topic {
newTopic(altair, future, empty, GossipLightClientOptimisticUpdateMessage), newTopic(altair, future, empty, GossipLightClientOptimisticUpdateMessage),
newTopic(altair, future, empty, GossipLightClientFinalityUpdateMessage), newTopic(altair, future, empty, GossipLightClientFinalityUpdateMessage),
newTopic(capella, future, empty, GossipBlsToExecutionChangeMessage), newTopic(capella, future, empty, GossipBlsToExecutionChangeMessage),
newTopic(gloas, future, empty, GossipPayloadAttestationMessage),
} }
last := params.GetNetworkScheduleEntry(genesis) last := params.GetNetworkScheduleEntry(genesis)
schedule := []params.NetworkScheduleEntry{last} schedule := []params.NetworkScheduleEntry{last}

View File

@@ -46,14 +46,20 @@ func (b *BeaconState) BuilderPubkey(builderIndex primitives.BuilderIndex) ([fiel
} }
// IsActiveBuilder returns true if the builder placement is finalized and it has not initiated exit. // IsActiveBuilder returns true if the builder placement is finalized and it has not initiated exit.
// Spec v1.7.0-alpha.0 (pseudocode):
// def is_active_builder(state: BeaconState, builder_index: BuilderIndex) -> bool:
// //
// builder = state.builders[builder_index] // <spec fn="is_active_builder" fork="gloas" hash="1a599fb2">
// return ( // def is_active_builder(state: BeaconState, builder_index: BuilderIndex) -> bool:
// builder.deposit_epoch < state.finalized_checkpoint.epoch // """
// and builder.withdrawable_epoch == FAR_FUTURE_EPOCH // Check if the builder at ``builder_index`` is active for the given ``state``.
// ) // """
// builder = state.builders[builder_index]
// return (
// # Placement in builder list is finalized
// builder.deposit_epoch < state.finalized_checkpoint.epoch
// # Has not initiated exit
// and builder.withdrawable_epoch == FAR_FUTURE_EPOCH
// )
// </spec>
func (b *BeaconState) IsActiveBuilder(builderIndex primitives.BuilderIndex) (bool, error) { func (b *BeaconState) IsActiveBuilder(builderIndex primitives.BuilderIndex) (bool, error) {
if b.version < version.Gloas { if b.version < version.Gloas {
return false, errNotSupported("IsActiveBuilder", b.version) return false, errNotSupported("IsActiveBuilder", b.version)
@@ -72,15 +78,18 @@ func (b *BeaconState) IsActiveBuilder(builderIndex primitives.BuilderIndex) (boo
} }
// CanBuilderCoverBid returns true if the builder has enough balance to cover the given bid amount. // CanBuilderCoverBid returns true if the builder has enough balance to cover the given bid amount.
// Spec v1.7.0-alpha.0 (pseudocode):
// def can_builder_cover_bid(state: BeaconState, builder_index: BuilderIndex, bid_amount: Gwei) -> bool:
// //
// builder_balance = state.builders[builder_index].balance // <spec fn="can_builder_cover_bid" fork="gloas" hash="9e3f2d7c">
// pending_withdrawals_amount = get_pending_balance_to_withdraw_for_builder(state, builder_index) // def can_builder_cover_bid(
// min_balance = MIN_DEPOSIT_AMOUNT + pending_withdrawals_amount // state: BeaconState, builder_index: BuilderIndex, bid_amount: Gwei
// if builder_balance < min_balance: // ) -> bool:
// return False // builder_balance = state.builders[builder_index].balance
// return builder_balance - min_balance >= bid_amount // pending_withdrawals_amount = get_pending_balance_to_withdraw_for_builder(state, builder_index)
// min_balance = MIN_DEPOSIT_AMOUNT + pending_withdrawals_amount
// if builder_balance < min_balance:
// return False
// return builder_balance - min_balance >= bid_amount
// </spec>
func (b *BeaconState) CanBuilderCoverBid(builderIndex primitives.BuilderIndex, bidAmount primitives.Gwei) (bool, error) { func (b *BeaconState) CanBuilderCoverBid(builderIndex primitives.BuilderIndex, bidAmount primitives.Gwei) (bool, error) {
if b.version < version.Gloas { if b.version < version.Gloas {
return false, errNotSupported("CanBuilderCoverBid", b.version) return false, errNotSupported("CanBuilderCoverBid", b.version)

View File

@@ -82,20 +82,20 @@ func (b *BeaconState) SetExecutionPayloadBid(h interfaces.ROExecutionPayloadBid)
parentBlockRoot := h.ParentBlockRoot() parentBlockRoot := h.ParentBlockRoot()
blockHash := h.BlockHash() blockHash := h.BlockHash()
randao := h.PrevRandao() randao := h.PrevRandao()
blobKzgCommitmentsRoot := h.BlobKzgCommitmentsRoot() blobKzgCommitments := h.BlobKzgCommitments()
feeRecipient := h.FeeRecipient() feeRecipient := h.FeeRecipient()
b.latestExecutionPayloadBid = &ethpb.ExecutionPayloadBid{ b.latestExecutionPayloadBid = &ethpb.ExecutionPayloadBid{
ParentBlockHash: parentBlockHash[:], ParentBlockHash: parentBlockHash[:],
ParentBlockRoot: parentBlockRoot[:], ParentBlockRoot: parentBlockRoot[:],
BlockHash: blockHash[:], BlockHash: blockHash[:],
PrevRandao: randao[:], PrevRandao: randao[:],
GasLimit: h.GasLimit(), GasLimit: h.GasLimit(),
BuilderIndex: h.BuilderIndex(), BuilderIndex: h.BuilderIndex(),
Slot: h.Slot(), Slot: h.Slot(),
Value: h.Value(), Value: h.Value(),
ExecutionPayment: h.ExecutionPayment(), ExecutionPayment: h.ExecutionPayment(),
BlobKzgCommitmentsRoot: blobKzgCommitmentsRoot[:], BlobKzgCommitments: blobKzgCommitments,
FeeRecipient: feeRecipient[:], FeeRecipient: feeRecipient[:],
} }
b.markFieldAsDirty(types.LatestExecutionPayloadBid) b.markFieldAsDirty(types.LatestExecutionPayloadBid)

View File

@@ -14,17 +14,17 @@ import (
) )
type testExecutionPayloadBid struct { type testExecutionPayloadBid struct {
parentBlockHash [32]byte parentBlockHash [32]byte
parentBlockRoot [32]byte parentBlockRoot [32]byte
blockHash [32]byte blockHash [32]byte
prevRandao [32]byte prevRandao [32]byte
blobKzgCommitmentsRoot [32]byte blobKzgCommitments [][]byte
feeRecipient [20]byte feeRecipient [20]byte
gasLimit uint64 gasLimit uint64
builderIndex primitives.BuilderIndex builderIndex primitives.BuilderIndex
slot primitives.Slot slot primitives.Slot
value primitives.Gwei value primitives.Gwei
executionPayment primitives.Gwei executionPayment primitives.Gwei
} }
func (t testExecutionPayloadBid) ParentBlockHash() [32]byte { return t.parentBlockHash } func (t testExecutionPayloadBid) ParentBlockHash() [32]byte { return t.parentBlockHash }
@@ -40,9 +40,12 @@ func (t testExecutionPayloadBid) Value() primitives.Gwei { return t.value }
func (t testExecutionPayloadBid) ExecutionPayment() primitives.Gwei { func (t testExecutionPayloadBid) ExecutionPayment() primitives.Gwei {
return t.executionPayment return t.executionPayment
} }
func (t testExecutionPayloadBid) BlobKzgCommitmentsRoot() [32]byte { return t.blobKzgCommitmentsRoot } func (t testExecutionPayloadBid) BlobKzgCommitments() [][]byte { return t.blobKzgCommitments }
func (t testExecutionPayloadBid) FeeRecipient() [20]byte { return t.feeRecipient } func (t testExecutionPayloadBid) BlobKzgCommitmentCount() uint64 {
func (t testExecutionPayloadBid) IsNil() bool { return false } return uint64(len(t.blobKzgCommitments))
}
func (t testExecutionPayloadBid) FeeRecipient() [20]byte { return t.feeRecipient }
func (t testExecutionPayloadBid) IsNil() bool { return false }
func TestSetExecutionPayloadBid(t *testing.T) { func TestSetExecutionPayloadBid(t *testing.T) {
t.Run("previous fork returns expected error", func(t *testing.T) { t.Run("previous fork returns expected error", func(t *testing.T) {
@@ -57,7 +60,7 @@ func TestSetExecutionPayloadBid(t *testing.T) {
parentBlockRoot = [32]byte(bytes.Repeat([]byte{0xCD}, 32)) parentBlockRoot = [32]byte(bytes.Repeat([]byte{0xCD}, 32))
blockHash = [32]byte(bytes.Repeat([]byte{0xEF}, 32)) blockHash = [32]byte(bytes.Repeat([]byte{0xEF}, 32))
prevRandao = [32]byte(bytes.Repeat([]byte{0x11}, 32)) prevRandao = [32]byte(bytes.Repeat([]byte{0x11}, 32))
blobRoot = [32]byte(bytes.Repeat([]byte{0x22}, 32)) blobCommitments = [][]byte{bytes.Repeat([]byte{0x22}, 48)}
feeRecipient [20]byte feeRecipient [20]byte
) )
copy(feeRecipient[:], bytes.Repeat([]byte{0x33}, len(feeRecipient))) copy(feeRecipient[:], bytes.Repeat([]byte{0x33}, len(feeRecipient)))
@@ -66,17 +69,17 @@ func TestSetExecutionPayloadBid(t *testing.T) {
dirtyFields: make(map[types.FieldIndex]bool), dirtyFields: make(map[types.FieldIndex]bool),
} }
bid := testExecutionPayloadBid{ bid := testExecutionPayloadBid{
parentBlockHash: parentBlockHash, parentBlockHash: parentBlockHash,
parentBlockRoot: parentBlockRoot, parentBlockRoot: parentBlockRoot,
blockHash: blockHash, blockHash: blockHash,
prevRandao: prevRandao, prevRandao: prevRandao,
blobKzgCommitmentsRoot: blobRoot, blobKzgCommitments: blobCommitments,
feeRecipient: feeRecipient, feeRecipient: feeRecipient,
gasLimit: 123, gasLimit: 123,
builderIndex: 7, builderIndex: 7,
slot: 9, slot: 9,
value: 11, value: 11,
executionPayment: 22, executionPayment: 22,
} }
require.NoError(t, st.SetExecutionPayloadBid(bid)) require.NoError(t, st.SetExecutionPayloadBid(bid))
@@ -86,7 +89,7 @@ func TestSetExecutionPayloadBid(t *testing.T) {
require.DeepEqual(t, parentBlockRoot[:], st.latestExecutionPayloadBid.ParentBlockRoot) require.DeepEqual(t, parentBlockRoot[:], st.latestExecutionPayloadBid.ParentBlockRoot)
require.DeepEqual(t, blockHash[:], st.latestExecutionPayloadBid.BlockHash) require.DeepEqual(t, blockHash[:], st.latestExecutionPayloadBid.BlockHash)
require.DeepEqual(t, prevRandao[:], st.latestExecutionPayloadBid.PrevRandao) require.DeepEqual(t, prevRandao[:], st.latestExecutionPayloadBid.PrevRandao)
require.DeepEqual(t, blobRoot[:], st.latestExecutionPayloadBid.BlobKzgCommitmentsRoot) require.DeepEqual(t, blobCommitments, st.latestExecutionPayloadBid.BlobKzgCommitments)
require.DeepEqual(t, feeRecipient[:], st.latestExecutionPayloadBid.FeeRecipient) require.DeepEqual(t, feeRecipient[:], st.latestExecutionPayloadBid.FeeRecipient)
require.Equal(t, uint64(123), st.latestExecutionPayloadBid.GasLimit) require.Equal(t, uint64(123), st.latestExecutionPayloadBid.GasLimit)
require.Equal(t, primitives.BuilderIndex(7), st.latestExecutionPayloadBid.BuilderIndex) require.Equal(t, primitives.BuilderIndex(7), st.latestExecutionPayloadBid.BuilderIndex)

View File

@@ -6,6 +6,7 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers" "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/time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/db"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state" "github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -122,8 +123,13 @@ func (s *State) StateByRootInitialSync(ctx context.Context, blockRoot [32]byte)
} }
if s.beaconDB.HasState(ctx, blockRoot) { if s.beaconDB.HasState(ctx, blockRoot) {
s, err := s.beaconDB.State(ctx, blockRoot) st, err := s.beaconDB.State(ctx, blockRoot)
return s, errors.Wrap(err, "failed to retrieve init-sync state from db") if err == nil {
return st, nil
}
if !stderrors.Is(err, db.ErrNotFoundState) {
return nil, errors.Wrap(err, "failed to retrieve init-sync state from db")
}
} }
startState, err := s.latestAncestor(ctx, blockRoot) startState, err := s.latestAncestor(ctx, blockRoot)
@@ -213,7 +219,13 @@ func (s *State) loadStateByRoot(ctx context.Context, blockRoot [32]byte) (state.
// Short circuit if the state is already in the DB. // Short circuit if the state is already in the DB.
if s.beaconDB.HasState(ctx, blockRoot) { if s.beaconDB.HasState(ctx, blockRoot) {
return s.beaconDB.State(ctx, blockRoot) st, err := s.beaconDB.State(ctx, blockRoot)
if err == nil {
return st, nil
}
if !stderrors.Is(err, db.ErrNotFoundState) {
return nil, err
}
} }
summary, err := s.stateSummary(ctx, blockRoot) summary, err := s.stateSummary(ctx, blockRoot)

View File

@@ -185,6 +185,71 @@ type testSetupSlots struct {
lastblock primitives.Slot lastblock primitives.Slot
} }
type notFoundOnRootDB struct {
db.NoHeadAccessDatabase
target [32]byte
}
func (d *notFoundOnRootDB) HasState(ctx context.Context, blockRoot [32]byte) bool {
if blockRoot == d.target {
return true
}
return d.NoHeadAccessDatabase.HasState(ctx, blockRoot)
}
func (d *notFoundOnRootDB) State(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error) {
if blockRoot == d.target {
return nil, db.ErrNotFoundState
}
return d.NoHeadAccessDatabase.State(ctx, blockRoot)
}
func TestStateByRoot_FallsBackToReplayOnNotFoundStateFromDirectRead(t *testing.T) {
ctx := t.Context()
beaconDB := testDB.SetupDB(t)
st9, _ := util.DeterministicGenesisState(t, 32)
st9, err := ReplayProcessSlots(ctx, st9, 9)
require.NoError(t, err)
hdr := st9.LatestBlockHeader()
hdrRoot, err := hdr.HashTreeRoot()
require.NoError(t, err)
st10 := st9.Copy()
blk10 := util.NewBeaconBlock()
blk10.Block.Slot = 10
blk10.Block.ParentRoot = hdrRoot[:]
idx10, err := helpers.BeaconProposerIndexAtSlot(ctx, st10, blk10.Block.Slot)
require.NoError(t, err)
blk10.Block.ProposerIndex = idx10
ib10, err := blt.NewSignedBeaconBlock(blk10)
require.NoError(t, err)
st10, err = executeStateTransitionStateGen(ctx, st10, ib10)
require.NoError(t, err)
st10Root, err := st10.HashTreeRoot(ctx)
require.NoError(t, err)
blk10.Block.StateRoot = st10Root[:]
util.SaveBlock(t, ctx, beaconDB, blk10)
require.NoError(t, beaconDB.SaveState(ctx, st9, hdrRoot))
ib10, err = blt.NewSignedBeaconBlock(blk10)
require.NoError(t, err)
rob10, err := blt.NewROBlock(ib10)
require.NoError(t, err)
service := New(&notFoundOnRootDB{NoHeadAccessDatabase: beaconDB, target: rob10.Root()}, doublylinkedtree.New())
got, err := service.StateByRoot(ctx, rob10.Root())
require.NoError(t, err)
gotRoot, err := got.HashTreeRoot(ctx)
require.NoError(t, err)
require.Equal(t, st10Root, gotRoot)
}
func TestLoadStateByRoot(t *testing.T) { func TestLoadStateByRoot(t *testing.T) {
ctx := t.Context() ctx := t.Context()
persistEpochBoundary := func(r testChain, slot primitives.Slot) { persistEpochBoundary := func(r testChain, slot primitives.Slot) {

View File

@@ -47,6 +47,7 @@ go_library(
"subscriber_bls_to_execution_change.go", "subscriber_bls_to_execution_change.go",
"subscriber_data_column_sidecar.go", "subscriber_data_column_sidecar.go",
"subscriber_handlers.go", "subscriber_handlers.go",
"subscriber_payload_attestation.go",
"subscriber_sync_committee_message.go", "subscriber_sync_committee_message.go",
"subscriber_sync_contribution_proof.go", "subscriber_sync_contribution_proof.go",
"subscription_topic_handler.go", "subscription_topic_handler.go",
@@ -58,6 +59,7 @@ go_library(
"validate_bls_to_execution_change.go", "validate_bls_to_execution_change.go",
"validate_data_column.go", "validate_data_column.go",
"validate_light_client.go", "validate_light_client.go",
"validate_payload_attestation.go",
"validate_proposer_slashing.go", "validate_proposer_slashing.go",
"validate_sync_committee_message.go", "validate_sync_committee_message.go",
"validate_sync_contribution_proof.go", "validate_sync_contribution_proof.go",
@@ -114,6 +116,7 @@ go_library(
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/blocks:go_default_library", "//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library", "//consensus-types/interfaces:go_default_library",
"//consensus-types/payload-attestation:go_default_library",
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//consensus-types/wrapper:go_default_library", "//consensus-types/wrapper:go_default_library",
"//container/leaky-bucket:go_default_library", "//container/leaky-bucket:go_default_library",
@@ -212,6 +215,7 @@ go_test(
"validate_bls_to_execution_change_test.go", "validate_bls_to_execution_change_test.go",
"validate_data_column_test.go", "validate_data_column_test.go",
"validate_light_client_test.go", "validate_light_client_test.go",
"validate_payload_attestation_test.go",
"validate_proposer_slashing_test.go", "validate_proposer_slashing_test.go",
"validate_sync_committee_message_test.go", "validate_sync_committee_message_test.go",
"validate_sync_contribution_proof_test.go", "validate_sync_contribution_proof_test.go",
@@ -264,6 +268,7 @@ go_test(
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/blocks:go_default_library", "//consensus-types/blocks:go_default_library",
"//consensus-types/interfaces:go_default_library", "//consensus-types/interfaces:go_default_library",
"//consensus-types/payload-attestation:go_default_library",
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//consensus-types/wrapper:go_default_library", "//consensus-types/wrapper:go_default_library",
"//container/leaky-bucket:go_default_library", "//container/leaky-bucket:go_default_library",

View File

@@ -207,6 +207,13 @@ func WithTrackedValidatorsCache(c *cache.TrackedValidatorsCache) Option {
} }
} }
func WithPayloadAttestationCache(c *cache.PayloadAttestationCache) Option {
return func(s *Service) error {
s.payloadAttestationCache = c
return nil
}
}
// WithSlasherEnabled configures the sync package to support slashing detection. // WithSlasherEnabled configures the sync package to support slashing detection.
func WithSlasherEnabled(enabled bool) Option { func WithSlasherEnabled(enabled bool) Option {
return func(s *Service) error { return func(s *Service) error {

View File

@@ -38,6 +38,7 @@ import (
"github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces" "github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
leakybucket "github.com/OffchainLabs/prysm/v7/container/leaky-bucket" leakybucket "github.com/OffchainLabs/prysm/v7/container/leaky-bucket"
"github.com/OffchainLabs/prysm/v7/crypto/rand" "github.com/OffchainLabs/prysm/v7/crypto/rand"
@@ -121,6 +122,7 @@ type blockchainService interface {
blockchain.FinalizationFetcher blockchain.FinalizationFetcher
blockchain.ForkFetcher blockchain.ForkFetcher
blockchain.AttestationReceiver blockchain.AttestationReceiver
blockchain.PayloadAttestationReceiver
blockchain.TimeFetcher blockchain.TimeFetcher
blockchain.GenesisFetcher blockchain.GenesisFetcher
blockchain.CanonicalFetcher blockchain.CanonicalFetcher
@@ -173,6 +175,7 @@ type Service struct {
verifierWaiter *verification.InitializerWaiter verifierWaiter *verification.InitializerWaiter
newBlobVerifier verification.NewBlobVerifier newBlobVerifier verification.NewBlobVerifier
newColumnsVerifier verification.NewDataColumnsVerifier newColumnsVerifier verification.NewDataColumnsVerifier
newPayloadAttestationVerifier verification.NewPayloadAttestationMsgVerifier
columnSidecarsExecSingleFlight singleflight.Group columnSidecarsExecSingleFlight singleflight.Group
reconstructionSingleFlight singleflight.Group reconstructionSingleFlight singleflight.Group
availableBlocker coverage.AvailableBlocker availableBlocker coverage.AvailableBlocker
@@ -182,6 +185,7 @@ type Service struct {
slasherEnabled bool slasherEnabled bool
lcStore *lightClient.Store lcStore *lightClient.Store
dataColumnLogCh chan dataColumnLogEntry dataColumnLogCh chan dataColumnLogEntry
payloadAttestationCache *cache.PayloadAttestationCache
digestActions perDigestSet digestActions perDigestSet
subscriptionSpawner func(func()) // see Service.spawn for details subscriptionSpawner func(func()) // see Service.spawn for details
} }
@@ -190,15 +194,16 @@ type Service struct {
func NewService(ctx context.Context, opts ...Option) *Service { func NewService(ctx context.Context, opts ...Option) *Service {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
r := &Service{ r := &Service{
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
chainStarted: abool.New(), chainStarted: abool.New(),
cfg: &config{clock: startup.NewClock(time.Unix(0, 0), [32]byte{})}, cfg: &config{clock: startup.NewClock(time.Unix(0, 0), [32]byte{})},
slotToPendingBlocks: gcache.New(pendingBlockExpTime /* exp time */, 0 /* disable janitor */), slotToPendingBlocks: gcache.New(pendingBlockExpTime /* exp time */, 0 /* disable janitor */),
seenPendingBlocks: make(map[[32]byte]bool), seenPendingBlocks: make(map[[32]byte]bool),
blkRootToPendingAtts: make(map[[32]byte][]any), blkRootToPendingAtts: make(map[[32]byte][]any),
dataColumnLogCh: make(chan dataColumnLogEntry, 1000), dataColumnLogCh: make(chan dataColumnLogEntry, 1000),
reconstructionRandGen: rand.NewGenerator(), reconstructionRandGen: rand.NewGenerator(),
payloadAttestationCache: &cache.PayloadAttestationCache{},
} }
for _, opt := range opts { for _, opt := range opts {
@@ -250,6 +255,12 @@ func newDataColumnsVerifierFromInitializer(ini *verification.Initializer) verifi
} }
} }
func newPayloadAttestationMessageFromInitializer(ini *verification.Initializer) verification.NewPayloadAttestationMsgVerifier {
return func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return ini.NewPayloadAttestationMsgVerifier(pa, reqs)
}
}
// Start the regular sync service. // Start the regular sync service.
func (s *Service) Start() { func (s *Service) Start() {
v, err := s.verifierWaiter.WaitForInitializer(s.ctx) v, err := s.verifierWaiter.WaitForInitializer(s.ctx)
@@ -259,6 +270,7 @@ func (s *Service) Start() {
} }
s.newBlobVerifier = newBlobVerifierFromInitializer(v) s.newBlobVerifier = newBlobVerifierFromInitializer(v)
s.newColumnsVerifier = newDataColumnsVerifierFromInitializer(v) s.newColumnsVerifier = newDataColumnsVerifierFromInitializer(v)
s.newPayloadAttestationVerifier = newPayloadAttestationMessageFromInitializer(v)
go s.verifierRoutine() go s.verifierRoutine()
go s.startDiscoveryAndSubscriptions() go s.startDiscoveryAndSubscriptions()

View File

@@ -330,6 +330,18 @@ func (s *Service) registerSubscribers(nse params.NetworkScheduleEntry) bool {
}) })
}) })
} }
// New gossip topic in Gloas.
if params.BeaconConfig().GloasForkEpoch <= nse.Epoch {
s.spawn(func() {
s.subscribe(
p2p.PayloadAttestationMessageTopicFormat,
s.validatePayloadAttestation,
s.payloadAttestationSubscriber,
nse,
)
})
}
return true return true
} }

View File

@@ -0,0 +1,21 @@
package sync
import (
"context"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"google.golang.org/protobuf/proto"
)
func (s *Service) payloadAttestationSubscriber(ctx context.Context, msg proto.Message) error {
a, ok := msg.(*eth.PayloadAttestationMessage)
if !ok {
return errWrongMessage
}
if err := s.payloadAttestationCache.Add(a.Data.Slot, a.ValidatorIndex); err != nil {
return err
}
return s.cfg.chain.ReceivePayloadAttestationMessage(ctx, a)
}

View File

@@ -0,0 +1,131 @@
package sync
import (
"bytes"
"context"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/transition"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
"github.com/OffchainLabs/prysm/v7/encoding/bytesutil"
"github.com/OffchainLabs/prysm/v7/monitoring/tracing/trace"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/time/slots"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/pkg/errors"
)
var (
errAlreadySeenPayloadAttestation = errors.New("payload attestation already seen for validator index")
)
func (s *Service) validatePayloadAttestation(ctx context.Context, pid peer.ID, msg *pubsub.Message) (pubsub.ValidationResult, error) {
if pid == s.cfg.p2p.PeerID() {
return pubsub.ValidationAccept, nil
}
if s.cfg.initialSync.Syncing() {
return pubsub.ValidationIgnore, nil
}
ctx, span := trace.StartSpan(ctx, "sync.validatePayloadAttestation")
defer span.End()
if msg.Topic == nil {
return pubsub.ValidationReject, p2p.ErrInvalidTopic
}
m, err := s.decodePubsubMessage(msg)
if err != nil {
return pubsub.ValidationReject, err
}
att, ok := m.(*eth.PayloadAttestationMessage)
if !ok {
return pubsub.ValidationReject, errWrongMessage
}
pa, err := payloadattestation.NewReadOnly(att)
if err != nil {
return pubsub.ValidationIgnore, err
}
v := s.newPayloadAttestationVerifier(pa, verification.GossipPayloadAttestationMessageRequirements)
// [IGNORE] The message's slot is for the current slot (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance),
// i.e. data.slot == current_slot.
if err := v.VerifyCurrentSlot(); err != nil {
return pubsub.ValidationIgnore, err
}
// [IGNORE] The payload_attestation_message is the first valid message received from the validator with
// index payload_attestation_message.validator_index.
if s.payloadAttestationCache.Seen(pa.Slot(), pa.ValidatorIndex()) {
return pubsub.ValidationIgnore, errAlreadySeenPayloadAttestation
}
// [IGNORE] The message's block data.beacon_block_root has been seen (via gossip or non-gossip sources)
// (a client MAY queue attestation for processing once the block is retrieved. Note a client might want to request payload after).
if err := v.VerifyBlockRootSeen(s.cfg.chain.InForkchoice); err != nil {
// TODO: queue attestation
return pubsub.ValidationIgnore, err
}
// [REJECT] The message's block data.beacon_block_root passes validation.
if err := v.VerifyBlockRootValid(s.hasBadBlock); err != nil {
return pubsub.ValidationReject, err
}
st, err := s.getPtcState(ctx, pa)
if err != nil {
return pubsub.ValidationIgnore, err
}
// [REJECT] The message's validator index is within the payload committee in get_ptc(state, data.slot).
// The state is the head state corresponding to processing the block up to the current slot.
if err := v.VerifyValidatorInPTC(ctx, st); err != nil {
return pubsub.ValidationReject, err
}
// [REJECT] payload_attestation_message.signature is valid with respect to the validator's public key.
if err := v.VerifySignature(st); err != nil {
return pubsub.ValidationReject, err
}
msg.ValidatorData = att
return pubsub.ValidationAccept, nil
}
func (s *Service) getPtcState(ctx context.Context, pa payloadattestation.ROMessage) (state.ReadOnlyBeaconState, error) {
blockRoot := pa.BeaconBlockRoot()
blockSlot := pa.Slot()
blockEpoch := slots.ToEpoch(blockSlot)
headSlot := s.cfg.chain.HeadSlot()
headEpoch := slots.ToEpoch(headSlot)
headRoot, err := s.cfg.chain.HeadRoot(ctx)
if err != nil {
return nil, err
}
if blockEpoch == headEpoch {
if bytes.Equal(blockRoot[:], headRoot) {
return s.cfg.chain.HeadStateReadOnly(ctx)
}
headDependent, err := s.cfg.chain.DependentRootForEpoch(bytesutil.ToBytes32(headRoot), blockEpoch)
if err != nil {
return nil, err
}
blockDependent, err := s.cfg.chain.DependentRootForEpoch(blockRoot, blockEpoch)
if err != nil {
return nil, err
}
if bytes.Equal(headDependent[:], blockDependent[:]) {
return s.cfg.chain.HeadStateReadOnly(ctx)
}
}
headState, err := s.cfg.chain.HeadState(ctx)
if err != nil {
return nil, err
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, blockSlot)
}

View File

@@ -0,0 +1,165 @@
package sync
import (
"bytes"
"context"
"reflect"
"testing"
"time"
mock "github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/cache"
"github.com/OffchainLabs/prysm/v7/beacon-chain/p2p"
p2ptest "github.com/OffchainLabs/prysm/v7/beacon-chain/p2p/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
mockSync "github.com/OffchainLabs/prysm/v7/beacon-chain/sync/initial-sync/testing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/verification"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/require"
"github.com/OffchainLabs/prysm/v7/testing/util"
pubsub "github.com/libp2p/go-libp2p-pubsub"
pb "github.com/libp2p/go-libp2p-pubsub/pb"
"github.com/pkg/errors"
)
func TestValidatePayloadAttestationMessage_IncorrectTopic(t *testing.T) {
ctx := context.Background()
p := p2ptest.NewTestP2P(t)
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0)}
s := &Service{
payloadAttestationCache: &cache.PayloadAttestationCache{},
cfg: &config{chain: chainService, p2p: p, initialSync: &mockSync.Sync{}, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot)}}
msg := util.HydratePayloadAttestation(&ethpb.PayloadAttestation{}) // Using payload attestation for message should fail.
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.PayloadAttestation]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestToTopic(topic, digest)
result, err := s.validatePayloadAttestation(ctx, "", &pubsub.Message{
Message: &pb.Message{
Data: buf.Bytes(),
Topic: &topic,
}})
require.ErrorContains(t, "extraction failed for topic", err)
require.Equal(t, result, pubsub.ValidationReject)
}
func TestValidatePayloadAttestationMessage_ErrorPathsWithMock(t *testing.T) {
tests := []struct {
error error
verifier verification.NewPayloadAttestationMsgVerifier
result pubsub.ValidationResult
}{
{
error: errors.New("incorrect slot"),
verifier: func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{ErrIncorrectPayloadAttSlot: errors.New("incorrect slot")}
},
result: pubsub.ValidationIgnore,
},
{
error: errors.New("block root seen"),
verifier: func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{ErrPayloadAttBlockRootNotSeen: errors.New("block root seen")}
},
result: pubsub.ValidationIgnore,
},
{
error: errors.New("block root invalid"),
verifier: func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{ErrPayloadAttBlockRootInvalid: errors.New("block root invalid")}
},
result: pubsub.ValidationReject,
},
{
error: errors.New("validator not in PTC"),
verifier: func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{ErrIncorrectPayloadAttValidator: errors.New("validator not in PTC")}
},
result: pubsub.ValidationReject,
},
{
error: errors.New("incorrect signature"),
verifier: func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{ErrInvalidMessageSignature: errors.New("incorrect signature")}
},
result: pubsub.ValidationReject,
},
}
for _, tt := range tests {
t.Run(tt.error.Error(), func(t *testing.T) {
ctx := context.Background()
p := p2ptest.NewTestP2P(t)
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0)}
s := &Service{
payloadAttestationCache: &cache.PayloadAttestationCache{},
cfg: &config{chain: chainService, p2p: p, initialSync: &mockSync.Sync{}, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot)}}
s.newPayloadAttestationVerifier = tt.verifier
msg := newPayloadAttestationMessage()
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.PayloadAttestationMessage]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestToTopic(topic, digest)
result, err := s.validatePayloadAttestation(ctx, "", &pubsub.Message{
Message: &pb.Message{
Data: buf.Bytes(),
Topic: &topic,
}})
require.ErrorContains(t, tt.error.Error(), err)
require.Equal(t, result, tt.result)
})
}
}
func TestValidatePayloadAttestationMessage_Accept(t *testing.T) {
ctx := context.Background()
p := p2ptest.NewTestP2P(t)
chainService := &mock.ChainService{Genesis: time.Unix(time.Now().Unix()-int64(params.BeaconConfig().SecondsPerSlot), 0)}
s := &Service{
payloadAttestationCache: &cache.PayloadAttestationCache{},
cfg: &config{chain: chainService, p2p: p, initialSync: &mockSync.Sync{}, clock: startup.NewClock(chainService.Genesis, chainService.ValidatorsRoot)}}
s.newPayloadAttestationVerifier = func(pa payloadattestation.ROMessage, reqs []verification.Requirement) verification.PayloadAttestationMsgVerifier {
return &verification.MockPayloadAttestation{}
}
msg := newPayloadAttestationMessage()
buf := new(bytes.Buffer)
_, err := p.Encoding().EncodeGossip(buf, msg)
require.NoError(t, err)
topic := p2p.GossipTypeMapping[reflect.TypeFor[*ethpb.PayloadAttestationMessage]()]
digest, err := s.currentForkDigest()
require.NoError(t, err)
topic = s.addDigestToTopic(topic, digest)
result, err := s.validatePayloadAttestation(ctx, "", &pubsub.Message{
Message: &pb.Message{
Data: buf.Bytes(),
Topic: &topic,
}})
require.NoError(t, err)
require.Equal(t, result, pubsub.ValidationAccept)
}
func newPayloadAttestationMessage() *ethpb.PayloadAttestationMessage {
return &ethpb.PayloadAttestationMessage{
ValidatorIndex: 0,
Data: util.HydratePayloadAttestationData(&ethpb.PayloadAttestationData{Slot: 1}),
Signature: make([]byte, fieldparams.BLSSignatureLength),
}
}

View File

@@ -15,12 +15,16 @@ go_library(
"log.go", "log.go",
"metrics.go", "metrics.go",
"mock.go", "mock.go",
"payload_attestation.go",
"payload_attestation_mock.go",
"requirements.go",
"result.go", "result.go",
], ],
importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/verification", importpath = "github.com/OffchainLabs/prysm/v7/beacon-chain/verification",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//beacon-chain/blockchain/kzg:go_default_library", "//beacon-chain/blockchain/kzg:go_default_library",
"//beacon-chain/core/gloas:go_default_library",
"//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/peerdas:go_default_library", "//beacon-chain/core/peerdas:go_default_library",
"//beacon-chain/core/signing:go_default_library", "//beacon-chain/core/signing:go_default_library",
@@ -32,6 +36,7 @@ go_library(
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/blocks:go_default_library", "//consensus-types/blocks:go_default_library",
"//consensus-types/payload-attestation:go_default_library",
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
@@ -57,12 +62,14 @@ go_test(
"data_column_test.go", "data_column_test.go",
"filesystem_test.go", "filesystem_test.go",
"initializer_test.go", "initializer_test.go",
"payload_attestation_test.go",
"result_test.go", "result_test.go",
"verification_test.go", "verification_test.go",
], ],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//beacon-chain/blockchain/kzg:go_default_library", "//beacon-chain/blockchain/kzg:go_default_library",
"//beacon-chain/core/helpers:go_default_library",
"//beacon-chain/core/peerdas:go_default_library", "//beacon-chain/core/peerdas:go_default_library",
"//beacon-chain/core/signing:go_default_library", "//beacon-chain/core/signing:go_default_library",
"//beacon-chain/db:go_default_library", "//beacon-chain/db:go_default_library",
@@ -73,8 +80,10 @@ go_test(
"//config/fieldparams:go_default_library", "//config/fieldparams:go_default_library",
"//config/params:go_default_library", "//config/params:go_default_library",
"//consensus-types/blocks:go_default_library", "//consensus-types/blocks:go_default_library",
"//consensus-types/payload-attestation:go_default_library",
"//consensus-types/primitives:go_default_library", "//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library", "//crypto/bls:go_default_library",
"//crypto/bls/common:go_default_library",
"//encoding/bytesutil:go_default_library", "//encoding/bytesutil:go_default_library",
"//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1:go_default_library",
"//runtime/interop:go_default_library", "//runtime/interop:go_default_library",

View File

@@ -14,24 +14,6 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
const (
RequireBlobIndexInBounds Requirement = iota
RequireNotFromFutureSlot
RequireSlotAboveFinalized
RequireValidProposerSignature
RequireSidecarParentSeen
RequireSidecarParentValid
RequireSidecarParentSlotLower
RequireSidecarDescendsFromFinalized
RequireSidecarInclusionProven
RequireSidecarKzgProofVerified
RequireSidecarProposerExpected
// Data columns specific.
RequireValidFields
RequireCorrectSubnet
)
var allBlobSidecarRequirements = []Requirement{ var allBlobSidecarRequirements = []Requirement{
RequireBlobIndexInBounds, RequireBlobIndexInBounds,
RequireNotFromFutureSlot, RequireNotFromFutureSlot,

View File

@@ -12,6 +12,7 @@ import (
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
@@ -86,6 +87,16 @@ func (ini *Initializer) NewDataColumnsVerifier(roDataColumns []blocks.RODataColu
} }
} }
// NewPayloadAttestationMsgVerifier creates a PayloadAttestationMsgVerifier for a single payload attestation message,
// with the given set of requirements.
func (ini *Initializer) NewPayloadAttestationMsgVerifier(pa payloadattestation.ROMessage, reqs []Requirement) *PayloadAttMsgVerifier {
return &PayloadAttMsgVerifier{
sharedResources: ini.shared,
results: newResults(reqs...),
pa: pa,
}
}
// InitializerWaiter provides an Initializer once all dependent resources are ready // InitializerWaiter provides an Initializer once all dependent resources are ready
// via the WaitForInitializer method. // via the WaitForInitializer method.
type InitializerWaiter struct { type InitializerWaiter struct {

View File

@@ -3,8 +3,10 @@ package verification
import ( import (
"context" "context"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams" fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks" "github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
) )
// BlobVerifier defines the methods implemented by the ROBlobVerifier. // BlobVerifier defines the methods implemented by the ROBlobVerifier.
@@ -54,3 +56,18 @@ type DataColumnsVerifier interface {
// NewDataColumnsVerifier is a function signature that can be used to mock a setup where a // NewDataColumnsVerifier is a function signature that can be used to mock a setup where a
// column verifier can be easily initialized. // column verifier can be easily initialized.
type NewDataColumnsVerifier func(dataColumns []blocks.RODataColumn, reqs []Requirement) DataColumnsVerifier type NewDataColumnsVerifier func(dataColumns []blocks.RODataColumn, reqs []Requirement) DataColumnsVerifier
// PayloadAttestationMsgVerifier defines the methods implemented by the ROPayloadAttestation.
type PayloadAttestationMsgVerifier interface {
VerifyCurrentSlot() error
VerifyBlockRootSeen(blockRootSeen func([32]byte) bool) error
VerifyBlockRootValid(func([32]byte) bool) error
VerifyValidatorInPTC(context.Context, state.ReadOnlyBeaconState) error
VerifySignature(state.ReadOnlyBeaconState) error
VerifiedPayloadAttestation() (payloadattestation.VerifiedROMessage, error)
SatisfyRequirement(Requirement)
}
// NewPayloadAttestationMsgVerifier is a function signature that can be used by code that needs to be
// able to mock Initializer.NewPayloadAttestationMsgVerifier without complex setup.
type NewPayloadAttestationMsgVerifier func(pa payloadattestation.ROMessage, reqs []Requirement) PayloadAttestationMsgVerifier

View File

@@ -0,0 +1,177 @@
package verification
import (
"context"
"fmt"
"slices"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/gloas"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
)
// RequirementList defines a list of requirements.
type RequirementList []Requirement
// PayloadAttGossipRequirements defines the list of requirements for gossip payload attestation messages.
var PayloadAttGossipRequirements = []Requirement{
RequireCurrentSlot,
RequireMessageNotSeen,
RequireValidatorInPTC,
RequireBlockRootSeen,
RequireBlockRootValid,
RequireSignatureValid,
}
// GossipPayloadAttestationMessageRequirements is a requirement list for gossip payload attestation messages.
var GossipPayloadAttestationMessageRequirements = RequirementList(PayloadAttGossipRequirements)
var (
ErrIncorrectPayloadAttSlot = errors.New("payload att slot does not match the current slot")
ErrPayloadAttBlockRootNotSeen = errors.New("block root not seen")
ErrPayloadAttBlockRootInvalid = errors.New("block root invalid")
ErrIncorrectPayloadAttValidator = errors.New("validator not present in payload timeliness committee")
ErrInvalidPayloadAttMessage = errors.New("invalid payload attestation message")
)
var _ PayloadAttestationMsgVerifier = &PayloadAttMsgVerifier{}
// PayloadAttMsgVerifier is a read-only verifier for payload attestation messages.
type PayloadAttMsgVerifier struct {
*sharedResources
results *results
pa payloadattestation.ROMessage
}
// VerifyCurrentSlot verifies if the current slot matches the expected slot.
// Represents the following spec verification:
// [IGNORE] data.slot is the current slot.
func (v *PayloadAttMsgVerifier) VerifyCurrentSlot() (err error) {
defer v.record(RequireCurrentSlot, &err)
currentSlot := v.clock.CurrentSlot()
if v.pa.Slot() != currentSlot {
return fmt.Errorf("%w: got %d want %d", ErrIncorrectPayloadAttSlot, v.pa.Slot(), currentSlot)
}
return nil
}
// VerifyBlockRootSeen verifies if the block root has been seen before.
// Represents the following spec verification:
// [IGNORE] The attestation's data.beacon_block_root has been seen (via both gossip and non-gossip sources).
func (v *PayloadAttMsgVerifier) VerifyBlockRootSeen(blockRootSeen func([32]byte) bool) (err error) {
defer v.record(RequireBlockRootSeen, &err)
if blockRootSeen != nil && blockRootSeen(v.pa.BeaconBlockRoot()) {
return nil
}
return fmt.Errorf("%w: root=%#x", ErrPayloadAttBlockRootNotSeen, v.pa.BeaconBlockRoot())
}
// VerifyBlockRootValid verifies if the block root is valid.
// Represents the following spec verification:
// [REJECT] The beacon block with root data.beacon_block_root passes validation.
func (v *PayloadAttMsgVerifier) VerifyBlockRootValid(badBlock func([32]byte) bool) (err error) {
defer v.record(RequireBlockRootValid, &err)
if badBlock != nil && badBlock(v.pa.BeaconBlockRoot()) {
return fmt.Errorf("%w: root=%#x", ErrPayloadAttBlockRootInvalid, v.pa.BeaconBlockRoot())
}
return nil
}
// VerifyValidatorInPTC verifies if the validator is present.
// Represents the following spec verification:
// [REJECT] The validator index is within the payload committee in get_ptc(state, data.slot). For the current's slot head state.
func (v *PayloadAttMsgVerifier) VerifyValidatorInPTC(ctx context.Context, st state.ReadOnlyBeaconState) (err error) {
defer v.record(RequireValidatorInPTC, &err)
ptc, err := gloas.PayloadCommittee(ctx, st, v.pa.Slot())
if err != nil {
return err
}
if slices.Index(ptc, v.pa.ValidatorIndex()) == -1 {
return fmt.Errorf("%w: validatorIndex=%d", ErrIncorrectPayloadAttValidator, v.pa.ValidatorIndex())
}
return nil
}
// VerifySignature verifies the signature of the payload attestation message.
// Represents the following spec verification:
// [REJECT] The signature of payload_attestation_message.signature is valid with respect to the validator index.
func (v *PayloadAttMsgVerifier) VerifySignature(st state.ReadOnlyBeaconState) (err error) {
defer v.record(RequireSignatureValid, &err)
err = validatePayloadAttestationMessageSignature(st, v.pa)
if err != nil {
return err
}
return nil
}
// VerifiedPayloadAttestation returns a verified payload attestation message by checking all requirements.
func (v *PayloadAttMsgVerifier) VerifiedPayloadAttestation() (payloadattestation.VerifiedROMessage, error) {
if v.results.allSatisfied() {
return payloadattestation.NewVerifiedROMessage(v.pa), nil
}
return payloadattestation.VerifiedROMessage{}, ErrInvalidPayloadAttMessage
}
// SatisfyRequirement allows the caller to manually mark a requirement as satisfied.
func (v *PayloadAttMsgVerifier) SatisfyRequirement(req Requirement) {
v.record(req, nil)
}
// ValidatePayloadAttestationMessageSignature verifies the signature of a payload attestation message.
func validatePayloadAttestationMessageSignature(st state.ReadOnlyBeaconState, payloadAtt payloadattestation.ROMessage) error {
val, err := st.ValidatorAtIndex(payloadAtt.ValidatorIndex())
if err != nil {
return fmt.Errorf("validator %d: %w", payloadAtt.ValidatorIndex(), err)
}
pub, err := bls.PublicKeyFromBytes(val.PublicKey)
if err != nil {
return fmt.Errorf("public key: %w", err)
}
s := payloadAtt.Signature()
sig, err := bls.SignatureFromBytes(s[:])
if err != nil {
return fmt.Errorf("signature bytes: %w", err)
}
currentEpoch := slots.ToEpoch(st.Slot())
domain, err := signing.Domain(st.Fork(), currentEpoch, params.BeaconConfig().DomainPTCAttester, st.GenesisValidatorsRoot())
if err != nil {
return fmt.Errorf("domain: %w", err)
}
root, err := payloadAtt.SigningRoot(domain)
if err != nil {
return fmt.Errorf("signing root: %w", err)
}
if !sig.Verify(pub, root[:]) {
return fmt.Errorf("verify signature: %w", signing.ErrSigFailedToVerify)
}
return nil
}
// record records the result of a requirement verification.
func (v *PayloadAttMsgVerifier) record(req Requirement, err *error) {
if err == nil || *err == nil {
v.results.record(req, nil)
return
}
v.results.record(req, *err)
}

View File

@@ -0,0 +1,46 @@
package verification
import (
"context"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
)
type MockPayloadAttestation struct {
ErrIncorrectPayloadAttSlot error
ErrIncorrectPayloadAttValidator error
ErrPayloadAttBlockRootNotSeen error
ErrPayloadAttBlockRootInvalid error
ErrInvalidPayloadAttMessage error
ErrInvalidMessageSignature error
ErrUnsatisfiedRequirement error
}
var _ PayloadAttestationMsgVerifier = &MockPayloadAttestation{}
func (m *MockPayloadAttestation) VerifyCurrentSlot() error {
return m.ErrIncorrectPayloadAttSlot
}
func (m *MockPayloadAttestation) VerifyValidatorInPTC(ctx context.Context, st state.ReadOnlyBeaconState) error {
return m.ErrIncorrectPayloadAttValidator
}
func (m *MockPayloadAttestation) VerifyBlockRootSeen(_ func([32]byte) bool) error {
return m.ErrPayloadAttBlockRootNotSeen
}
func (m *MockPayloadAttestation) VerifyBlockRootValid(func([32]byte) bool) error {
return m.ErrPayloadAttBlockRootInvalid
}
func (m *MockPayloadAttestation) VerifySignature(st state.ReadOnlyBeaconState) (err error) {
return m.ErrInvalidMessageSignature
}
func (m *MockPayloadAttestation) VerifiedPayloadAttestation() (payloadattestation.VerifiedROMessage, error) {
return payloadattestation.VerifiedROMessage{}, nil
}
func (m *MockPayloadAttestation) SatisfyRequirement(req Requirement) {}

View File

@@ -0,0 +1,167 @@
package verification
import (
"bytes"
"context"
"testing"
"time"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/params"
payloadattestation "github.com/OffchainLabs/prysm/v7/consensus-types/payload-attestation"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
"github.com/OffchainLabs/prysm/v7/crypto/bls"
"github.com/OffchainLabs/prysm/v7/crypto/bls/common"
eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"github.com/OffchainLabs/prysm/v7/testing/require"
testutil "github.com/OffchainLabs/prysm/v7/testing/util"
"github.com/OffchainLabs/prysm/v7/time/slots"
)
func TestPayloadAttestationVerifyCurrentSlot(t *testing.T) {
params.SetupTestConfigCleanup(t)
now := time.Unix(1000, 0)
genesis := now.Add(-time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second)
clock := startup.NewClock(genesis, [32]byte{}, startup.WithNower(func() time.Time { return now }))
ini := &Initializer{shared: &sharedResources{clock: clock}}
msg := newPayloadAttestationMessage(primitives.Slot(1), 0, bytes.Repeat([]byte{0x11}, 32))
pa, err := payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v := ini.NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.NoError(t, v.VerifyCurrentSlot())
msg = newPayloadAttestationMessage(primitives.Slot(2), 0, bytes.Repeat([]byte{0x11}, 32))
pa, err = payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v = ini.NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.ErrorIs(t, v.VerifyCurrentSlot(), ErrIncorrectPayloadAttSlot)
}
func TestPayloadAttestationVerifyBlockRootSeenAndValid(t *testing.T) {
params.SetupTestConfigCleanup(t)
ini := &Initializer{shared: &sharedResources{}}
root := bytes.Repeat([]byte{0x22}, 32)
var root32 [32]byte
copy(root32[:], root)
msg := newPayloadAttestationMessage(primitives.Slot(1), 0, root)
pa, err := payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v := ini.NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.NoError(t, v.VerifyBlockRootSeen(func(r [32]byte) bool { return r == root32 }))
require.ErrorIs(t, v.VerifyBlockRootSeen(func([32]byte) bool { return false }), ErrPayloadAttBlockRootNotSeen)
require.NoError(t, v.VerifyBlockRootValid(func([32]byte) bool { return false }))
require.ErrorIs(t, v.VerifyBlockRootValid(func([32]byte) bool { return true }), ErrPayloadAttBlockRootInvalid)
}
func TestPayloadAttestationVerifyValidatorInPTC(t *testing.T) {
setupPayloadAttTestConfig(t)
_, pk := newKey(t)
st := newTestState(t, []*eth.Validator{activeValidator(pk)}, 1)
msg := newPayloadAttestationMessage(primitives.Slot(1), 0, bytes.Repeat([]byte{0x33}, 32))
pa, err := payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v := (&Initializer{shared: &sharedResources{}}).NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.NoError(t, v.VerifyValidatorInPTC(context.Background(), st))
msg = newPayloadAttestationMessage(primitives.Slot(1), 1, bytes.Repeat([]byte{0x33}, 32))
pa, err = payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v = (&Initializer{shared: &sharedResources{}}).NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.ErrorIs(t, v.VerifyValidatorInPTC(context.Background(), st), ErrIncorrectPayloadAttValidator)
}
func TestPayloadAttestationVerifySignature(t *testing.T) {
setupPayloadAttTestConfig(t)
sk, pk := newKey(t)
st := newTestState(t, []*eth.Validator{activeValidator(pk)}, 1)
root := bytes.Repeat([]byte{0x44}, 32)
data := &eth.PayloadAttestationData{
BeaconBlockRoot: root,
Slot: 1,
PayloadPresent: true,
BlobDataAvailable: true,
}
msg := &eth.PayloadAttestationMessage{
ValidatorIndex: 0,
Data: data,
Signature: signPayloadAttestationMessage(t, st, data, sk),
}
pa, err := payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v := (&Initializer{shared: &sharedResources{}}).NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.NoError(t, v.VerifySignature(st))
sk2, _ := newKey(t)
msg.Signature = signPayloadAttestationMessage(t, st, data, sk2)
pa, err = payloadattestation.NewReadOnly(msg)
require.NoError(t, err)
v = (&Initializer{shared: &sharedResources{}}).NewPayloadAttestationMsgVerifier(pa, GossipPayloadAttestationMessageRequirements)
require.ErrorIs(t, v.VerifySignature(st), signing.ErrSigFailedToVerify)
}
func newPayloadAttestationMessage(slot primitives.Slot, idx primitives.ValidatorIndex, root []byte) *eth.PayloadAttestationMessage {
return &eth.PayloadAttestationMessage{
ValidatorIndex: idx,
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: root,
Slot: slot,
PayloadPresent: true,
BlobDataAvailable: true,
},
Signature: []byte{0x01},
}
}
func newTestState(t *testing.T, vals []*eth.Validator, slot primitives.Slot) state.BeaconState {
st, err := testutil.NewBeaconStateGloas()
require.NoError(t, err)
for _, v := range vals {
require.NoError(t, st.AppendValidator(v))
require.NoError(t, st.AppendBalance(v.EffectiveBalance))
}
require.NoError(t, st.SetSlot(slot))
require.NoError(t, helpers.UpdateCommitteeCache(t.Context(), st, slots.ToEpoch(slot)))
return st
}
func setupPayloadAttTestConfig(t *testing.T) {
params.SetupTestConfigCleanup(t)
cfg := params.BeaconConfig().Copy()
cfg.SlotsPerEpoch = 1
cfg.MaxEffectiveBalanceElectra = cfg.MaxEffectiveBalance
params.OverrideBeaconConfig(cfg)
}
func activeValidator(pub []byte) *eth.Validator {
return &eth.Validator{
PublicKey: pub,
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
func newKey(t *testing.T) (common.SecretKey, []byte) {
sk, err := bls.RandKey()
require.NoError(t, err)
return sk, sk.PublicKey().Marshal()
}
func signPayloadAttestationMessage(t *testing.T, st state.ReadOnlyBeaconState, data *eth.PayloadAttestationData, sk common.SecretKey) []byte {
domain, err := signing.Domain(st.Fork(), slots.ToEpoch(st.Slot()), params.BeaconConfig().DomainPTCAttester, st.GenesisValidatorsRoot())
require.NoError(t, err)
root, err := signing.ComputeSigningRoot(data, domain)
require.NoError(t, err)
sig := sk.Sign(root[:])
return sig.Marshal()
}

View File

@@ -0,0 +1,27 @@
package verification
const (
RequireBlobIndexInBounds Requirement = iota
RequireNotFromFutureSlot
RequireSlotAboveFinalized
RequireValidProposerSignature
RequireSidecarParentSeen
RequireSidecarParentValid
RequireSidecarParentSlotLower
RequireSidecarDescendsFromFinalized
RequireSidecarInclusionProven
RequireSidecarKzgProofVerified
RequireSidecarProposerExpected
// Data columns specific.
RequireValidFields
RequireCorrectSubnet
// Payload attestation specific.
RequireCurrentSlot
RequireMessageNotSeen
RequireValidatorInPTC
RequireBlockRootSeen
RequireBlockRootValid
RequireSignatureValid
)

View File

@@ -29,6 +29,22 @@ func (r Requirement) String() string {
return "RequireSidecarKzgProofVerified" return "RequireSidecarKzgProofVerified"
case RequireSidecarProposerExpected: case RequireSidecarProposerExpected:
return "RequireSidecarProposerExpected" return "RequireSidecarProposerExpected"
case RequireValidFields:
return "RequireValidFields"
case RequireCorrectSubnet:
return "RequireCorrectSubnet"
case RequireCurrentSlot:
return "RequireCurrentSlot"
case RequireMessageNotSeen:
return "RequireMessageNotSeen"
case RequireValidatorInPTC:
return "RequireValidatorInPTC"
case RequireBlockRootSeen:
return "RequireBlockRootSeen"
case RequireBlockRootValid:
return "RequireBlockRootValid"
case RequireSignatureValid:
return "RequireSignatureValid"
default: default:
return unknownRequirementName return unknownRequirementName
} }

View File

@@ -61,3 +61,16 @@ func TestAllBlobRequirementsHaveStrings(t *testing.T) {
require.NotEqual(t, unknownRequirementName, allBlobSidecarRequirements[i].String()) require.NotEqual(t, unknownRequirementName, allBlobSidecarRequirements[i].String())
} }
} }
func TestPayloadAttestationRequirementsHaveStrings(t *testing.T) {
blobReqs := make(map[Requirement]struct{}, len(allBlobSidecarRequirements))
for i := range allBlobSidecarRequirements {
blobReqs[allBlobSidecarRequirements[i]] = struct{}{}
}
for i := range PayloadAttGossipRequirements {
req := PayloadAttGossipRequirements[i]
require.NotEqual(t, unknownRequirementName, req.String())
_, overlaps := blobReqs[req]
require.Equal(t, false, overlaps)
}
}

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed a bug where `cmd/beacon-chain/execution` was being ignored by `hack/gen-logs.sh` due to a `.gitignore` rule.

View File

@@ -0,0 +1,3 @@
### Changed
- Fixed the logging issue described in #16314.

View File

@@ -0,0 +1,11 @@
### Ignored
- moved finding healthy node logic to connection provider and other various cleanup on naming.
### Changed
- Improved node fallback logs.
### Fixed
- a potential race condition when switching hosts quickly and reconnecting to same host on an old connection.

View File

@@ -0,0 +1,3 @@
### Ignored
- improving maintainability and deduplication on get and post block parsing.

View File

@@ -0,0 +1,3 @@
### Changed
- Improved integrations with ethspecify so specrefs can be used throughout the codebase.

View File

@@ -0,0 +1,2 @@
### Ignored
- Remove unused `HighestBlockDelay` method in forkchoice.

View File

@@ -0,0 +1,2 @@
### Ignored
- Remove unused method in forkchoice.

View File

@@ -0,0 +1,2 @@
### Ignored
- Remove unused map in forkchoice.

View File

@@ -0,0 +1,3 @@
### Fixed
- Fixed some database slices that were used outside of a read transaction. See [bbolt README](https://github.com/etcd-io/bbolt/blob/7b38172caf8cde993d187be4b8738fbe9266fde8/README.md?plain=1#L852) for more on this caveat.

View File

@@ -0,0 +1,2 @@
### Ignored
- Close opened host in test helpers

View File

@@ -0,0 +1,3 @@
### Added
- Added support for Payload attestation gossip net in gloas

View File

@@ -0,0 +1,3 @@
### Changed
- Moved blob KZG commitments into `ExecutionPayloadBid` and removed them from `ExecutionPayloadEnvelope` for Gloas.

View File

@@ -0,0 +1,3 @@
### Added
- Add read only wrapper for execution payload envelope for gloas

View File

@@ -1,5 +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 execution package execution
import "github.com/sirupsen/logrus" import "github.com/sirupsen/logrus"
var log = logrus.WithField("prefix", "execution") // 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", "cmd/beacon-chain/execution")

View File

@@ -356,6 +356,11 @@ var (
Usage: "A comma-separated list of exponents (of 2) in decreasing order, defining the state diff hierarchy levels. The last exponent must be greater than or equal to 5.", Usage: "A comma-separated list of exponents (of 2) in decreasing order, defining the state diff hierarchy levels. The last exponent must be greater than or equal to 5.",
Value: cli.NewIntSlice(21, 18, 16, 13, 11, 9, 5), Value: cli.NewIntSlice(21, 18, 16, 13, 11, 9, 5),
} }
// StateDiffValidateOnStartup validates state diff data on startup.
StateDiffValidateOnStartup = &cli.BoolFlag{
Name: "disable-hdiff-validate-on-startup",
Usage: "Disables state-diff validation on startup (enabled by default).",
}
// DisableEphemeralLogFile disables the 24 hour debug log file. // DisableEphemeralLogFile disables the 24 hour debug log file.
DisableEphemeralLogFile = &cli.BoolFlag{ DisableEphemeralLogFile = &cli.BoolFlag{
Name: "disable-ephemeral-log-file", Name: "disable-ephemeral-log-file",

View File

@@ -9,24 +9,25 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
const maxStateDiffExponents = 30 const MaxStateDiffExponent = 30
// GlobalFlags specifies all the global flags for the // GlobalFlags specifies all the global flags for the
// beacon node. // beacon node.
type GlobalFlags struct { type GlobalFlags struct {
SubscribeToAllSubnets bool StateDiffValidateOnStartup bool
Supernode bool Supernode bool
SemiSupernode bool
DisableGetBlobsV2 bool DisableGetBlobsV2 bool
MinimumSyncPeers int SemiSupernode bool
MinimumPeersPerSubnet int SubscribeToAllSubnets bool
MaxConcurrentDials int
BlockBatchLimit int
BlockBatchLimitBurstFactor int
BlobBatchLimit int
BlobBatchLimitBurstFactor int BlobBatchLimitBurstFactor int
DataColumnBatchLimit int DataColumnBatchLimit int
BlockBatchLimit int
MaxConcurrentDials int
MinimumPeersPerSubnet int
MinimumSyncPeers int
DataColumnBatchLimitBurstFactor int DataColumnBatchLimitBurstFactor int
BlockBatchLimitBurstFactor int
BlobBatchLimit int
StateDiffExponents []int StateDiffExponents []int
} }
@@ -80,6 +81,7 @@ func ConfigureGlobalFlags(ctx *cli.Context) error {
// State-diff-exponents // State-diff-exponents
cfg.StateDiffExponents = ctx.IntSlice(StateDiffExponents.Name) cfg.StateDiffExponents = ctx.IntSlice(StateDiffExponents.Name)
cfg.StateDiffValidateOnStartup = !ctx.Bool(StateDiffValidateOnStartup.Name)
if features.Get().EnableStateDiff { if features.Get().EnableStateDiff {
if err := validateStateDiffExponents(cfg.StateDiffExponents); err != nil { if err := validateStateDiffExponents(cfg.StateDiffExponents); err != nil {
return err return err
@@ -88,6 +90,9 @@ func ConfigureGlobalFlags(ctx *cli.Context) error {
if ctx.IsSet(StateDiffExponents.Name) { if ctx.IsSet(StateDiffExponents.Name) {
log.Warn("--state-diff-exponents is set but --enable-state-diff is not; the value will be ignored.") log.Warn("--state-diff-exponents is set but --enable-state-diff is not; the value will be ignored.")
} }
if ctx.IsSet(StateDiffValidateOnStartup.Name) {
log.Warn("--disable-hdiff-validate-on-startup is set but --enable-state-diff is not; the value will be ignored.")
}
} }
cfg.BlockBatchLimit = ctx.Int(BlockBatchLimit.Name) cfg.BlockBatchLimit = ctx.Int(BlockBatchLimit.Name)
@@ -132,7 +137,7 @@ func validateStateDiffExponents(exponents []int) error {
if exponents[length-1] < 5 { if exponents[length-1] < 5 {
return errors.New("the last state diff exponent must be at least 5") return errors.New("the last state diff exponent must be at least 5")
} }
prev := maxStateDiffExponents + 1 prev := MaxStateDiffExponent + 1
for _, exp := range exponents { for _, exp := range exponents {
if exp >= prev { if exp >= prev {
return errors.New("state diff exponents must be in strictly decreasing order, and each exponent must be <= 30") return errors.New("state diff exponents must be in strictly decreasing order, and each exponent must be <= 30")

View File

@@ -161,6 +161,7 @@ var appFlags = []cli.Flag{
dasFlags.BlobRetentionEpochFlag, dasFlags.BlobRetentionEpochFlag,
flags.BatchVerifierLimit, flags.BatchVerifierLimit,
flags.StateDiffExponents, flags.StateDiffExponents,
flags.StateDiffValidateOnStartup,
flags.DisableEphemeralLogFile, flags.DisableEphemeralLogFile,
} }
@@ -188,8 +189,8 @@ func before(ctx *cli.Context) error {
return errors.Wrap(err, "failed to parse log vmodule") return errors.Wrap(err, "failed to parse log vmodule")
} }
// set the global logging level to allow for the highest verbosity requested // set the global logging level and data
logs.SetLoggingLevel(max(verbosityLevel, maxLevel)) logs.SetLoggingLevelAndData(verbosityLevel, vmodule, maxLevel, ctx.Bool(flags.DisableEphemeralLogFile.Name))
format := ctx.String(cmd.LogFormat.Name) format := ctx.String(cmd.LogFormat.Name)
switch format { switch format {
@@ -210,6 +211,7 @@ func before(ctx *cli.Context) error {
Formatter: formatter, Formatter: formatter,
Writer: os.Stderr, Writer: os.Stderr,
AllowedLevels: logrus.AllLevels[:max(verbosityLevel, maxLevel)+1], AllowedLevels: logrus.AllLevels[:max(verbosityLevel, maxLevel)+1],
Identifier: logs.LogTargetUser,
}) })
case "fluentd": case "fluentd":
f := joonix.NewFormatter() f := joonix.NewFormatter()

View File

@@ -75,6 +75,7 @@ var appHelpFlagGroups = []flagGroup{
flags.RPCPort, flags.RPCPort,
flags.BatchVerifierLimit, flags.BatchVerifierLimit,
flags.StateDiffExponents, flags.StateDiffExponents,
flags.StateDiffValidateOnStartup,
}, },
}, },
{ {

View File

@@ -164,8 +164,8 @@ func main() {
return errors.Wrap(err, "failed to parse log vmodule") return errors.Wrap(err, "failed to parse log vmodule")
} }
// set the global logging level to allow for the highest verbosity requested // set the global logging level and data
logs.SetLoggingLevel(max(maxLevel, verbosityLevel)) logs.SetLoggingLevelAndData(verbosityLevel, vmodule, maxLevel, ctx.Bool(flags.DisableEphemeralLogFile.Name))
logFileName := ctx.String(cmd.LogFileName.Name) logFileName := ctx.String(cmd.LogFileName.Name)
@@ -188,6 +188,7 @@ func main() {
Formatter: formatter, Formatter: formatter,
Writer: os.Stderr, Writer: os.Stderr,
AllowedLevels: logrus.AllLevels[:max(verbosityLevel, maxLevel)+1], AllowedLevels: logrus.AllLevels[:max(verbosityLevel, maxLevel)+1],
Identifier: logs.LogTargetUser,
}) })
case "fluentd": case "fluentd":
f := joonix.NewFormatter() f := joonix.NewFormatter()

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library", name = "go_default_library",
srcs = [ srcs = [
"execution.go", "execution.go",
"execution_payload_envelope.go",
"factory.go", "factory.go",
"get_payload.go", "get_payload.go",
"getters.go", "getters.go",
@@ -45,6 +46,7 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
srcs = [ srcs = [
"execution_payload_envelope_test.go",
"execution_test.go", "execution_test.go",
"factory_test.go", "factory_test.go",
"getters_test.go", "getters_test.go",

View File

@@ -0,0 +1,131 @@
package blocks
import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
field_params "github.com/OffchainLabs/prysm/v7/config/fieldparams"
consensus_types "github.com/OffchainLabs/prysm/v7/consensus-types"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
enginev1 "github.com/OffchainLabs/prysm/v7/proto/engine/v1"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"google.golang.org/protobuf/proto"
)
type signedExecutionPayloadEnvelope struct {
s *ethpb.SignedExecutionPayloadEnvelope
}
type executionPayloadEnvelope struct {
p *ethpb.ExecutionPayloadEnvelope
}
// WrappedROSignedExecutionPayloadEnvelope wraps a signed execution payload envelope proto in a read-only interface.
func WrappedROSignedExecutionPayloadEnvelope(s *ethpb.SignedExecutionPayloadEnvelope) (interfaces.ROSignedExecutionPayloadEnvelope, error) {
w := signedExecutionPayloadEnvelope{s: s}
if w.IsNil() {
return nil, consensus_types.ErrNilObjectWrapped
}
return w, nil
}
// WrappedROExecutionPayloadEnvelope wraps an execution payload envelope proto in a read-only interface.
func WrappedROExecutionPayloadEnvelope(p *ethpb.ExecutionPayloadEnvelope) (interfaces.ROExecutionPayloadEnvelope, error) {
w := &executionPayloadEnvelope{p: p}
if w.IsNil() {
return nil, consensus_types.ErrNilObjectWrapped
}
return w, nil
}
// Envelope returns the execution payload envelope as a read-only interface.
func (s signedExecutionPayloadEnvelope) Envelope() (interfaces.ROExecutionPayloadEnvelope, error) {
return WrappedROExecutionPayloadEnvelope(s.s.Message)
}
// Signature returns the BLS signature as a 96-byte array.
func (s signedExecutionPayloadEnvelope) Signature() [field_params.BLSSignatureLength]byte {
return [field_params.BLSSignatureLength]byte(s.s.Signature)
}
// IsNil reports whether the signed envelope or its contents are invalid.
func (s signedExecutionPayloadEnvelope) IsNil() bool {
if s.s == nil {
return true
}
if len(s.s.Signature) != field_params.BLSSignatureLength {
return true
}
if len(s.s.Message.BeaconBlockRoot) != field_params.RootLength {
return true
}
if len(s.s.Message.StateRoot) != field_params.RootLength {
return true
}
if s.s.Message.ExecutionRequests == nil {
return true
}
if s.s.Message.Payload == nil {
return true
}
w := executionPayloadEnvelope{p: s.s.Message}
return w.IsNil()
}
// SigningRoot computes the signing root for the signed envelope with the provided domain.
func (s signedExecutionPayloadEnvelope) SigningRoot(domain []byte) (root [32]byte, err error) {
return signing.ComputeSigningRoot(s.s.Message, domain)
}
// Proto returns the underlying protobuf message.
func (s signedExecutionPayloadEnvelope) Proto() proto.Message {
return s.s
}
// IsNil reports whether the envelope or its required fields are invalid.
func (p *executionPayloadEnvelope) IsNil() bool {
if p.p == nil {
return true
}
if p.p.Payload == nil {
return true
}
if len(p.p.BeaconBlockRoot) != field_params.RootLength {
return true
}
return false
}
// IsBlinded reports whether the envelope contains a blinded payload.
func (p *executionPayloadEnvelope) IsBlinded() bool {
return false
}
// Execution returns the execution payload as a read-only interface.
func (p *executionPayloadEnvelope) Execution() (interfaces.ExecutionData, error) {
return WrappedExecutionPayloadDeneb(p.p.Payload)
}
// ExecutionRequests returns the execution requests attached to the envelope.
func (p *executionPayloadEnvelope) ExecutionRequests() *enginev1.ExecutionRequests {
return ethpb.CopyExecutionRequests(p.p.ExecutionRequests)
}
// BuilderIndex returns the proposer/builder index for the envelope.
func (p *executionPayloadEnvelope) BuilderIndex() primitives.BuilderIndex {
return p.p.BuilderIndex
}
// BeaconBlockRoot returns the beacon block root referenced by the envelope.
func (p *executionPayloadEnvelope) BeaconBlockRoot() [field_params.RootLength]byte {
return [field_params.RootLength]byte(p.p.BeaconBlockRoot)
}
// Slot returns the slot of the envelope.
func (p *executionPayloadEnvelope) Slot() primitives.Slot {
return p.p.Slot
}
// StateRoot returns the state root carried by the envelope.
func (p *executionPayloadEnvelope) StateRoot() [field_params.RootLength]byte {
return [field_params.RootLength]byte(p.p.StateRoot)
}

Some files were not shown because too many files have changed in this diff Show More