Compare commits

...

64 Commits

Author SHA1 Message Date
terence tsao
779aa6e347 Add builder 2024-09-28 12:05:05 -07:00
terence tsao
f31ca9eb26 Local proposer end to end 2024-09-28 10:15:08 -07:00
Potuz
c4550d75bd fix PayloadEnvelopeStateTransition test 2024-09-26 17:31:35 -03:00
terence tsao
1df3d0c7a3 Fix compute field roots with hasher 2024-09-26 12:50:45 -07:00
Potuz
f9bde3e097 export random execution request 2024-09-26 15:56:16 -03:00
Potuz
294a24616b fix build 2024-09-26 10:38:53 -03:00
terence
edd736fb9f Prysm rpc: submit signed execution payload header (#14441) 2024-09-25 16:46:13 -03:00
terence
13b469858d Update attestor and aggregator respect epbs intervals (#14454) 2024-09-25 16:46:13 -03:00
Potuz
70110ecb86 Provide a helper to compute the state root after a payload envelope (#14450) 2024-09-25 16:46:13 -03:00
Potuz
4cfafdf861 Fix pubkeyToIndex usage 2024-09-25 16:46:13 -03:00
terence
7f4091e334 Prysm rpc: Get payload attestation data (#14380) 2024-09-25 16:46:13 -03:00
Potuz
5addd5b3e9 Handle execution payload insertion in forkchoice (#14422) 2024-09-25 16:46:13 -03:00
Potuz
9abe898784 Add GetPTCVote helpers (#14420) 2024-09-25 16:46:13 -03:00
terence
391f6afab3 Add wait until PTC duty helper function (#14419)
Add wait until PTC duty
2024-09-25 16:46:13 -03:00
terence
1f62a72068 Prysm rpc: submit execution payload envelope (#14395) 2024-09-25 16:46:13 -03:00
Potuz
5d0549c2e0 Remove Changelog workflow 2024-09-25 16:46:13 -03:00
terence
fc794f48fb Prysm rpc: Submit payload attestation data (#14381) 2024-09-25 16:46:13 -03:00
Potuz
aaa60012e1 Receive ptc message (#14394)
* Handle incoming ptc attestation messages in the chain package

* fix double import

* remove unused error
2024-09-25 16:46:13 -03:00
Potuz
c21203ec78 Add ePBS fork schedule to config (#14383) 2024-09-25 16:46:13 -03:00
Potuz
a8a642a7b1 [ePBS] implement UpdateVotesOnPayloadAttestation (#14308) 2024-09-25 16:46:13 -03:00
Potuz
15250d7d9c Signed execution payload header for sync (#14363)
* Signed execution payload header for sync

* Use RO state

* SignedExecutionPayloadHeader by hash and root

* Fix execution headers cache
2024-09-25 16:46:13 -03:00
Potuz
8792b33b75 Refactor currentlySyncingPayload cache (#14350) 2024-09-25 16:46:13 -03:00
JihoonSong
4948035d7f Add unit tests of ExecutionPayloadEnvelope verification (#14373)
* Correct requirement list of EnvelopeVerifier

* Add unit tests of ExecutionPayloadEnvelope verification
2024-09-25 16:46:13 -03:00
terence
8cda3f0589 Enable validator client to sign execution payload envelope (#14346)
* Enable validator client to sign execution payload envelope

* Update comment

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

---------

Co-authored-by: JihoonSong <jihoonsong@users.noreply.github.com>
2024-09-25 16:46:13 -03:00
Potuz
e7d840e843 Sync changes to process execution payload envelopes 2024-09-25 16:46:13 -03:00
Md Amaan
625308d7c5 Process withdrawal (#14297)
* process_withdrawal_fn and isParentfull test

* suggestions applied

* minor change

* removed

* lint

* lint fix

* removed Latestheader

* test added with nil error

* tests passing

* IsParentNode Test added

* lint

* fix test

* updated godoc

* fix in godoc

* comment removed

* fixed braces

* removed var

* removed var

* Update beacon-chain/core/blocks/withdrawals.go

* Update beacon-chain/core/blocks/withdrawals_test.go

* gazelle

* test added and removed previous changes in Testprocesswithdrawal

* added check for nil state

* decrease chromatic complexity

---------

Co-authored-by: Potuz <potuz@potuz.net>
Co-authored-by: Potuz <potuz@prysmaticlabs.com>
2024-09-25 16:46:13 -03:00
Potuz
523422ed84 Enable validator client to sign execution header (#14333)
* Enable validator client to sign execution header

* Update proto/prysm/v1alpha1/validator-client/keymanager.proto

---------

Co-authored-by: Potuz <potuz@prysmaticlabs.com>
2024-09-25 16:46:13 -03:00
terence
2efb3065be Initialize payload att message verfier in sync (#14323) 2024-09-25 16:46:13 -03:00
terence
dc538b9696 Add getter for payload attestation cache (#14328)
* Add getter for payload attestation cache

* Check against status

* Feedback #1
2024-09-25 16:46:13 -03:00
Potuz
ca32889e1a Payload Attestation Sync package changes (#13989)
* Payload Attestation Sync package changes

* With verifier

* change idx back to uint64

* subscribe to topic

* add back error

---------

Co-authored-by: terence tsao <terence@prysmaticlabs.com>
2024-09-25 16:46:13 -03:00
Potuz
a1b6059051 Process Execution Payload Envelope in Chain Service (#14295)
Adds the processing of execution payload envelope
Corrects the protos for attestations and slashings in Electra versions
Adds generators of full blocks for Electra
2024-09-25 16:46:09 -03:00
Md Amaan
3a61df4a96 Indexed paylaod attestation test (#14299)
* test-added

* nil check fix

* randomized inputs

* hardcoded inputs

* suggestions applied

* minor-typo fixed

* deleted
2024-09-25 16:44:09 -03:00
JihoonSong
bdabbef300 Add execution_payload and payload_attestation_message topics (#14304)
* Add `execution_payload` and `payload_attestation_message` topics

* Set `SourcePubkey` to 48 bytes long

* Add randomly populated `PayloadAttestationMessage` object

* Add tests for `execution_payload` and `payload_attestation_message` topics
2024-09-25 16:44:09 -03:00
terence
c6362fa5a6 Broadcast signed execution payload header to peer (#14300) 2024-09-25 16:44:09 -03:00
terence
5688afc6a4 Read only payload attestation message with Verifier (#14222)
* Read only payload attestation message with verifier

* Payload attestation tests (#14242)

* Payload attestation in verification package

* Feedback #1

---------

Co-authored-by: Md Amaan <114795592+Redidacove@users.noreply.github.com>
2024-09-25 16:44:09 -03:00
Potuz
f0fe5bbab1 Allow nodes with and without payload in forkchoice (#14288)
* Allow nodes with and without payload in forkchoice

    This PR takes care of adding nodes to forkchoice that may or may not
    have a corresponding payload. The rationale is as follows

    - The node structure is kept almost the same as today.
    - A zero payload hash is considered as if the node was empty (except for
      the tree root)
    - When inserting a node we check what the right parent node would be
      depending on whether the parent had a payload or not.
    - For pre-epbs forks all nodes are full, no logic changes except a new
      steps to gather the parent hash that is needed for block insertion.

    This PR had to change some core consensus types and interfaces.
    - It removed the ROBlockEPBS interface and added the corresponding ePBS
      fields to the ReadOnlyBeaconBlockBody
    - It moved the setters and getters to epbs dedicated files.

    It also added a checker for `IsParentFull` on forkchoice that simply
    checks for the parent hash of the parent node.

* review
2024-09-25 16:43:55 -03:00
Potuz
107c1f9e04 Use BeaconCommittees helper to get the ptc (#14286) 2024-09-25 16:42:00 -03:00
JihoonSong
42be4ee3f5 Add payload attestation helper functions (#14258)
* Add `IndexedPayloadAttestation` container

* Add `GetPayloadAttestingIndices` and its unit test

* Add `GetIndexedPayloadAttestation` and its unit test

* Add `is_valid_indexed_payload_attestation` and its unit test

* Create a smaller set of validators for faster unit test

* Pass context to `GetPayloadTimelinessCommittee`

* Iterate `ValidatorsReadOnly` instead of copying all validators
2024-09-25 16:42:00 -03:00
Potuz
dbd9141eca Use slot for latest message in forkchoice (#14279) 2024-09-25 16:42:00 -03:00
Potuz
68fdcf9776 Ensure epbs state getters & setters check versions (#14276)
* Ensure EPBS state getters and setters check versions

* Rename to LatestExecutionPayloadHeaderEPBS

* Add minimal beacon state
2024-09-25 16:41:55 -03:00
JihoonSong
872269c7cb Add remove_flag and its unit test (#14260)
* Add `remove_flag` and its unit test

* Add a test case trying to remove a flag that is not set
2024-09-25 16:41:14 -03:00
JihoonSong
e60c5322ef Modify get_ptc function to follow the Python spec (#14256)
* Modify `get_ptc` function to follow the Python spec

* Assign PTC members from the beginning of beacon committee array
2024-09-25 16:41:14 -03:00
Potuz
e62ee87911 Remove inclusion list from epbs (#14188) 2024-09-25 16:41:09 -03:00
Potuz
19a7c0b8c3 Enable validator client to submit payload attestation message (#14064) 2024-09-25 16:38:37 -03:00
Potuz
1eba10c0e2 Add PTC assignment support for Duty endpoint (#14032) 2024-09-25 16:38:37 -03:00
Potuz
fa559e30dd use Keymanager() in validator client 2024-09-25 16:38:29 -03:00
Potuz
b42cc45e44 Change gwei math to primitives package for ePBS state 2024-09-25 16:37:46 -03:00
terence
ac127c95c8 Fix GetPayloadTimelinessCommittee to return correct PTC size (#14012) 2024-09-25 16:31:53 -03:00
Potuz
c30f94d4be Add ePBS to db (#13971)
* Add ePBS to db
2024-09-25 16:30:26 -03:00
Potuz
099efc1f3c Add EPBS slashing params 2024-09-25 16:23:55 -03:00
Potuz
33e33978e3 Implement get_ptc
This implements a helper to get the ptc committee from a state. It uses
the cached beacon committees if possible

It also implements a helper to compute the largest power of two of a
uint64 and a helper to test for nil payload attestation messages
2024-09-25 16:23:55 -03:00
Potuz
ff952dae4e Add ePBS to state (#13926) 2024-09-25 16:23:49 -03:00
Potuz
440b173feb Add testing utility methods to return randomly populated ePBS objects 2024-09-25 16:01:15 -03:00
Potuz
f126d64c96 Add ePBS stuff to consensus-types: block 2024-09-25 16:01:09 -03:00
Potuz
8f170f97ec Helper for Payload Attestation Signing (#13901) 2024-09-25 15:55:25 -03:00
Potuz
da3159dd6a ePBS configuration constants 2024-09-25 15:54:05 -03:00
Potuz
31ae744237 Add ePBS beacon state proto 2024-09-25 15:54:01 -03:00
Potuz
3b2d29dce4 Add protos for ePBS except state 2024-09-25 15:51:53 -03:00
Potuz
ddafedc268 Implement consensus-specs/3875 (#14458)
* WIP

- beacon-chain builds

* pass blockchain tests

* pass beacon-chain/execution tests

* Passing RPC tests

* fix building

* add changelog

* fix linters

* Spectests

* copy requests on Copy()

* Fix tests

* Fix config test

* fix verification tests

* add aliases for Electra types

* double import and unskip spectests

* Remove unnecessary comment
2024-09-25 17:06:52 +00:00
Nishant Das
7e5738bfcd Update Pubsub Library To Use Gossipsub 1.2 (#14428)
* Update Libp2p Packages

* Update CHANGELOG

* Finally Fix All Tests

* Fix Build

* Fix Build

* Fix TestP2P Connection Initialization

* Fix TestP2P Host Options

* Fix Test By Removing WaitGroup
2024-09-24 16:15:19 +00:00
Preston Van Loon
315c05b351 async/event: Use geth's implementation of event/feed.go (#14362)
* Use geth's event

* run gazelle
2024-09-23 19:57:04 +00:00
Jun Song
3662cf6009 Add Electra case for GetBeaconStateV2 (#14466)
* Add Electra case for GetBeaconStateV2

* Add CHANGELOG.md entry

---------

Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com>
2024-09-20 21:20:13 +00:00
james-prysm
98d8b50b0e deflake pushsettings test - custom pattern matcher (#14453)
* adding custom matcher

* changelog

* removing unneeded map

* fixing merge conflict
2024-09-20 21:15:47 +00:00
Jun Song
1a1cc25bd1 Deprecate pb.gw.go files (#14464)
* Clean pb.gw.go files

* Deprecate pb.gw.go file support
2024-09-20 17:18:17 +00:00
335 changed files with 24427 additions and 9154 deletions

View File

@@ -1,33 +0,0 @@
name: CI
on:
pull_request:
branches:
- develop
jobs:
changed_files:
runs-on: ubuntu-latest
name: Check CHANGELOG.md
steps:
- uses: actions/checkout@v4
- name: changelog modified
id: changelog-modified
uses: tj-actions/changed-files@v45
with:
files: CHANGELOG.md
- name: List all changed files
env:
ALL_CHANGED_FILES: ${{ steps.changelog-modified.outputs.all_changed_files }}
run: |
if [[ ${ALL_CHANGED_FILES[*]} =~ (^|[[:space:]])"CHANGELOG.md"($|[[:space:]]) ]];
then
echo "CHANGELOG.md was modified.";
exit 0;
else
echo "CHANGELOG.md was not modified.";
echo "Please see CHANGELOG.md and follow the instructions to add your changes to that file."
echo "In some rare scenarios, a changelog entry is not required and this CI check can be ignored."
exit 1;
fi

View File

@@ -26,7 +26,6 @@ approval_rules:
only_changed_files:
paths:
- "*pb.go"
- "*pb.gw.go"
- "*.bazel"
options:
ignore_commits_by:
@@ -69,7 +68,6 @@ approval_rules:
changed_files:
ignore:
- "*pb.go"
- "*pb.gw.go"
- "*.bazel"
options:
ignore_commits_by:

View File

@@ -14,6 +14,8 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Light client support: Add light client database changes.
- Light client support: Implement capella and deneb changes.
- Light client support: Implement `BlockToLightClientHeaderXXX` functions upto Deneb
- GetBeaconStateV2: add Electra case.
- Implement [consensus-specs/3875](https://github.com/ethereum/consensus-specs/pull/3875)
### Changed
@@ -37,6 +39,7 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- Updated k8s-io/client-go to v0.30.4 and k8s-io/apimachinery to v0.30.4
- Migrated tracing library from opencensus to opentelemetry for both the beacon node and validator.
- Refactored light client code to make it more readable and make future PRs easier.
- Updated Libp2p Dependencies to allow prysm to use gossipsub v1.2 .
### Deprecated
- `--disable-grpc-gateway` flag is deprecated due to grpc gateway removal.
@@ -58,9 +61,10 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
- validator registration log changed to debug, and the frequency of validator registration calls are reduced
- Core: Fix process effective balance update to safe copy validator for Electra.
- `== nil` checks before calling `IsNil()` on interfaces to prevent panics.
- Core: Fixed slash processing causing extra hashing
- Core: Fixed extra allocations when processing slashings
- Core: Fixed slash processing causing extra hashing.
- Core: Fixed extra allocations when processing slashings.
- remove unneeded container in blob sidecar ssz response
- Testing: added custom matcher for better push settings testing.
### Security
@@ -2673,7 +2677,7 @@ on your validators.
**Beacon chain node**
| Metric | Description | References |
|--------------------------------------------------|-------------------------------------------------------------------------------------------------------|------------|
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ---------- |
| `p2p_message_ignored_validation_total` | Count of messages that were ignored in validation | |
| `beacon_current_active_validators` | Current total active validators | |
| `beacon_processed_deposits_total` | Total number of deposits processed | |
@@ -2724,9 +2728,9 @@ on your validators.
#### Changed Metrics
**Beacon chain node**
| Metric | Old Name | Description | References |
|-----------------------|----------------------|------------------------------------------------------|------------|
| `beacon_reorgs_total` | `beacon_reorg_total` | Count the number of times a beacon chain has a reorg | |
| Metric | Old Name | Description | References |
| --------------------- | -------------------- | ---------------------------------------------------- | ---------- |
| `beacon_reorgs_total` | `beacon_reorg_total` | Count the number of times a beacon chain has a reorg | |
### Deprecated

View File

@@ -227,7 +227,7 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
)
consensus_spec_version = "v1.5.0-alpha.5"
consensus_spec_version = "v1.5.0-alpha.6"
bls_test_version = "v0.1.1"
@@ -243,7 +243,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-R9vG5HEL5eGMOAmbkKfJ2jfelNqL5V0xBUPiXOiGM6U=",
integrity = "sha256-M7u/Ot/Vzorww+dFbHp0cxLyM2mezJjijCzq+LY3uvs=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/general.tar.gz" % consensus_spec_version,
)
@@ -259,7 +259,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-AEIiEOlf1XuxoRMCsN+kgJMo4LrS05+biTA1p/7Ro00=",
integrity = "sha256-deOSeLRsmHXvkRp8n2bs3HXdkGUJWWqu8KFM/QABbZg=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/minimal.tar.gz" % consensus_spec_version,
)
@@ -275,7 +275,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-LH/Xr20yrJRYnbpjRGupMWTIOWt3cpxZJWXgThwVDsk=",
integrity = "sha256-Zz7YCf6XVf57nzSEGq9ToflJFHM0lAGwhd18l9Rf3hA=",
url = "https://github.com/ethereum/consensus-spec-tests/releases/download/%s/mainnet.tar.gz" % consensus_spec_version,
)
@@ -290,7 +290,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-mlytz4MPjKh0DwV7FMiAtnRbJw9B6o78/x66/vmnYc8=",
integrity = "sha256-BoXckDxXnDcEmAjg/dQgf/tLiJsb6CT0aZvmWHFijrY=",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
)

View File

@@ -365,6 +365,7 @@ type BeaconBlockBodyElectra struct {
ExecutionPayload *ExecutionPayloadElectra `json:"execution_payload"`
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
}
type BlindedBeaconBlockElectra struct {
@@ -403,6 +404,7 @@ type BlindedBeaconBlockBodyElectra struct {
ExecutionPayloadHeader *ExecutionPayloadHeaderElectra `json:"execution_payload_header"`
BLSToExecutionChanges []*SignedBLSToExecutionChange `json:"bls_to_execution_changes"`
BlobKzgCommitments []string `json:"blob_kzg_commitments"`
ExecutionRequests *ExecutionRequests `json:"execution_requests"`
}
type SignedBeaconBlockHeaderContainer struct {
@@ -514,6 +516,8 @@ type ExecutionPayloadDeneb struct {
ExcessBlobGas string `json:"excess_blob_gas"`
}
type ExecutionPayloadElectra = ExecutionPayloadDeneb
type ExecutionPayloadHeaderDeneb struct {
ParentHash string `json:"parent_hash"`
FeeRecipient string `json:"fee_recipient"`
@@ -534,48 +538,10 @@ type ExecutionPayloadHeaderDeneb struct {
ExcessBlobGas string `json:"excess_blob_gas"`
}
type ExecutionPayloadElectra struct {
ParentHash string `json:"parent_hash"`
FeeRecipient string `json:"fee_recipient"`
StateRoot string `json:"state_root"`
ReceiptsRoot string `json:"receipts_root"`
LogsBloom string `json:"logs_bloom"`
PrevRandao string `json:"prev_randao"`
BlockNumber string `json:"block_number"`
GasLimit string `json:"gas_limit"`
GasUsed string `json:"gas_used"`
Timestamp string `json:"timestamp"`
ExtraData string `json:"extra_data"`
BaseFeePerGas string `json:"base_fee_per_gas"`
BlockHash string `json:"block_hash"`
Transactions []string `json:"transactions"`
Withdrawals []*Withdrawal `json:"withdrawals"`
BlobGasUsed string `json:"blob_gas_used"`
ExcessBlobGas string `json:"excess_blob_gas"`
DepositRequests []*DepositRequest `json:"deposit_requests"`
WithdrawalRequests []*WithdrawalRequest `json:"withdrawal_requests"`
ConsolidationRequests []*ConsolidationRequest `json:"consolidation_requests"`
}
type ExecutionPayloadHeaderElectra = ExecutionPayloadHeaderDeneb
type ExecutionPayloadHeaderElectra struct {
ParentHash string `json:"parent_hash"`
FeeRecipient string `json:"fee_recipient"`
StateRoot string `json:"state_root"`
ReceiptsRoot string `json:"receipts_root"`
LogsBloom string `json:"logs_bloom"`
PrevRandao string `json:"prev_randao"`
BlockNumber string `json:"block_number"`
GasLimit string `json:"gas_limit"`
GasUsed string `json:"gas_used"`
Timestamp string `json:"timestamp"`
ExtraData string `json:"extra_data"`
BaseFeePerGas string `json:"base_fee_per_gas"`
BlockHash string `json:"block_hash"`
TransactionsRoot string `json:"transactions_root"`
WithdrawalsRoot string `json:"withdrawals_root"`
BlobGasUsed string `json:"blob_gas_used"`
ExcessBlobGas string `json:"excess_blob_gas"`
DepositRequestsRoot string `json:"deposit_requests_root"`
WithdrawalRequestsRoot string `json:"withdrawal_requests_root"`
ConsolidationRequestsRoot string `json:"consolidation_requests_root"`
type ExecutionRequests struct {
Deposits []*DepositRequest `json:"deposits"`
Withdrawals []*WithdrawalRequest `json:"withdrawals"`
Consolidations []*ConsolidationRequest `json:"consolidations"`
}

View File

@@ -2088,27 +2088,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
}
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionPayload.DepositRequests))
for i, d := range b.Body.ExecutionPayload.DepositRequests {
if b.Body.ExecutionRequests == nil {
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExequtionRequests")
}
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
for i, d := range b.Body.ExecutionRequests.Deposits {
depositRequests[i], err = d.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.DepositRequests[%d]", i))
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
}
}
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionPayload.WithdrawalRequests))
for i, w := range b.Body.ExecutionPayload.WithdrawalRequests {
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
for i, w := range b.Body.ExecutionRequests.Withdrawals {
withdrawalRequests[i], err = w.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.WithdrawalRequests[%d]", i))
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
}
}
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionPayload.ConsolidationRequests))
for i, c := range b.Body.ExecutionPayload.ConsolidationRequests {
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
for i, c := range b.Body.ExecutionRequests.Consolidations {
consolidationRequests[i], err = c.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionPayload.ConsolidationRequests[%d]", i))
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
}
}
@@ -2151,29 +2155,31 @@ func (b *BeaconBlockElectra) ToConsensus() (*eth.BeaconBlockElectra, error) {
SyncCommitteeSignature: syncCommitteeSig,
},
ExecutionPayload: &enginev1.ExecutionPayloadElectra{
ParentHash: payloadParentHash,
FeeRecipient: payloadFeeRecipient,
StateRoot: payloadStateRoot,
ReceiptsRoot: payloadReceiptsRoot,
LogsBloom: payloadLogsBloom,
PrevRandao: payloadPrevRandao,
BlockNumber: payloadBlockNumber,
GasLimit: payloadGasLimit,
GasUsed: payloadGasUsed,
Timestamp: payloadTimestamp,
ExtraData: payloadExtraData,
BaseFeePerGas: payloadBaseFeePerGas,
BlockHash: payloadBlockHash,
Transactions: txs,
Withdrawals: withdrawals,
BlobGasUsed: payloadBlobGasUsed,
ExcessBlobGas: payloadExcessBlobGas,
DepositRequests: depositRequests,
WithdrawalRequests: withdrawalRequests,
ConsolidationRequests: consolidationRequests,
ParentHash: payloadParentHash,
FeeRecipient: payloadFeeRecipient,
StateRoot: payloadStateRoot,
ReceiptsRoot: payloadReceiptsRoot,
LogsBloom: payloadLogsBloom,
PrevRandao: payloadPrevRandao,
BlockNumber: payloadBlockNumber,
GasLimit: payloadGasLimit,
GasUsed: payloadGasUsed,
Timestamp: payloadTimestamp,
ExtraData: payloadExtraData,
BaseFeePerGas: payloadBaseFeePerGas,
BlockHash: payloadBlockHash,
Transactions: txs,
Withdrawals: withdrawals,
BlobGasUsed: payloadBlobGasUsed,
ExcessBlobGas: payloadExcessBlobGas,
},
BlsToExecutionChanges: blsChanges,
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequests: &enginev1.ExecutionRequests{
Deposits: depositRequests,
Withdrawals: withdrawalRequests,
Consolidations: consolidationRequests,
},
},
}, nil
}
@@ -2383,17 +2389,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
if err != nil {
return nil, server.NewDecodeError(err, "Body.ExecutionPayload.ExcessBlobGas")
}
payloadDepositRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.DepositRequestsRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.DepositRequestsRoot")
if b.Body.ExecutionRequests == nil {
return nil, server.NewDecodeError(errors.New("nil execution requests"), "Body.ExecutionRequests")
}
payloadWithdrawalRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.WithdrawalRequestsRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.WithdrawalRequestsRoot")
depositRequests := make([]*enginev1.DepositRequest, len(b.Body.ExecutionRequests.Deposits))
for i, d := range b.Body.ExecutionRequests.Deposits {
depositRequests[i], err = d.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Deposits[%d]", i))
}
}
payloadConsolidationRequestsRoot, err := bytesutil.DecodeHexWithLength(b.Body.ExecutionPayloadHeader.ConsolidationRequestsRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "Body.ExecutionPayloadHeader.ConsolidationRequestsRoot")
withdrawalRequests := make([]*enginev1.WithdrawalRequest, len(b.Body.ExecutionRequests.Withdrawals))
for i, w := range b.Body.ExecutionRequests.Withdrawals {
withdrawalRequests[i], err = w.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Withdrawals[%d]", i))
}
}
consolidationRequests := make([]*enginev1.ConsolidationRequest, len(b.Body.ExecutionRequests.Consolidations))
for i, c := range b.Body.ExecutionRequests.Consolidations {
consolidationRequests[i], err = c.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Body.ExecutionRequests.Consolidations[%d]", i))
}
}
blsChanges, err := SignedBLSChangesToConsensus(b.Body.BLSToExecutionChanges)
@@ -2436,29 +2456,31 @@ func (b *BlindedBeaconBlockElectra) ToConsensus() (*eth.BlindedBeaconBlockElectr
SyncCommitteeSignature: syncCommitteeSig,
},
ExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
ParentHash: payloadParentHash,
FeeRecipient: payloadFeeRecipient,
StateRoot: payloadStateRoot,
ReceiptsRoot: payloadReceiptsRoot,
LogsBloom: payloadLogsBloom,
PrevRandao: payloadPrevRandao,
BlockNumber: payloadBlockNumber,
GasLimit: payloadGasLimit,
GasUsed: payloadGasUsed,
Timestamp: payloadTimestamp,
ExtraData: payloadExtraData,
BaseFeePerGas: payloadBaseFeePerGas,
BlockHash: payloadBlockHash,
TransactionsRoot: payloadTxsRoot,
WithdrawalsRoot: payloadWithdrawalsRoot,
BlobGasUsed: payloadBlobGasUsed,
ExcessBlobGas: payloadExcessBlobGas,
DepositRequestsRoot: payloadDepositRequestsRoot,
WithdrawalRequestsRoot: payloadWithdrawalRequestsRoot,
ConsolidationRequestsRoot: payloadConsolidationRequestsRoot,
ParentHash: payloadParentHash,
FeeRecipient: payloadFeeRecipient,
StateRoot: payloadStateRoot,
ReceiptsRoot: payloadReceiptsRoot,
LogsBloom: payloadLogsBloom,
PrevRandao: payloadPrevRandao,
BlockNumber: payloadBlockNumber,
GasLimit: payloadGasLimit,
GasUsed: payloadGasUsed,
Timestamp: payloadTimestamp,
ExtraData: payloadExtraData,
BaseFeePerGas: payloadBaseFeePerGas,
BlockHash: payloadBlockHash,
TransactionsRoot: payloadTxsRoot,
WithdrawalsRoot: payloadWithdrawalsRoot,
BlobGasUsed: payloadBlobGasUsed,
ExcessBlobGas: payloadExcessBlobGas,
},
BlsToExecutionChanges: blsChanges,
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequests: &enginev1.ExecutionRequests{
Deposits: depositRequests,
Withdrawals: withdrawalRequests,
Consolidations: consolidationRequests,
},
},
}, nil
}
@@ -2963,10 +2985,19 @@ func BlindedBeaconBlockElectraFromConsensus(b *eth.BlindedBeaconBlockElectra) (*
ExecutionPayloadHeader: payload,
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
},
}, nil
}
func ExecutionRequestsFromConsensus(er *enginev1.ExecutionRequests) *ExecutionRequests {
return &ExecutionRequests{
Deposits: DepositRequestsFromConsensus(er.Deposits),
Withdrawals: WithdrawalRequestsFromConsensus(er.Withdrawals),
Consolidations: ConsolidationRequestsFromConsensus(er.Consolidations),
}
}
func SignedBlindedBeaconBlockElectraFromConsensus(b *eth.SignedBlindedBeaconBlockElectra) (*SignedBlindedBeaconBlockElectra, error) {
block, err := BlindedBeaconBlockElectraFromConsensus(b.Message)
if err != nil {
@@ -3009,6 +3040,7 @@ func BeaconBlockElectraFromConsensus(b *eth.BeaconBlockElectra) (*BeaconBlockEle
ExecutionPayload: payload,
BLSToExecutionChanges: SignedBLSChangesFromConsensus(b.Body.BlsToExecutionChanges),
BlobKzgCommitments: blobKzgCommitments,
ExecutionRequests: ExecutionRequestsFromConsensus(b.Body.ExecutionRequests),
},
}, nil
}
@@ -3112,39 +3144,7 @@ func ExecutionPayloadDenebFromConsensus(payload *enginev1.ExecutionPayloadDeneb)
}, nil
}
func ExecutionPayloadElectraFromConsensus(payload *enginev1.ExecutionPayloadElectra) (*ExecutionPayloadElectra, error) {
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
if err != nil {
return nil, err
}
transactions := make([]string, len(payload.Transactions))
for i, tx := range payload.Transactions {
transactions[i] = hexutil.Encode(tx)
}
return &ExecutionPayloadElectra{
ParentHash: hexutil.Encode(payload.ParentHash),
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
StateRoot: hexutil.Encode(payload.StateRoot),
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
LogsBloom: hexutil.Encode(payload.LogsBloom),
PrevRandao: hexutil.Encode(payload.PrevRandao),
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
ExtraData: hexutil.Encode(payload.ExtraData),
BaseFeePerGas: baseFeePerGas,
BlockHash: hexutil.Encode(payload.BlockHash),
Transactions: transactions,
Withdrawals: WithdrawalsFromConsensus(payload.Withdrawals),
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
DepositRequests: DepositRequestsFromConsensus(payload.DepositRequests),
WithdrawalRequests: WithdrawalRequestsFromConsensus(payload.WithdrawalRequests),
ConsolidationRequests: ConsolidationRequestsFromConsensus(payload.ConsolidationRequests),
}, nil
}
var ExecutionPayloadElectraFromConsensus = ExecutionPayloadDenebFromConsensus
func ExecutionPayloadHeaderFromConsensus(payload *enginev1.ExecutionPayloadHeader) (*ExecutionPayloadHeader, error) {
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
@@ -3222,32 +3222,4 @@ func ExecutionPayloadHeaderDenebFromConsensus(payload *enginev1.ExecutionPayload
}, nil
}
func ExecutionPayloadHeaderElectraFromConsensus(payload *enginev1.ExecutionPayloadHeaderElectra) (*ExecutionPayloadHeaderElectra, error) {
baseFeePerGas, err := sszBytesToUint256String(payload.BaseFeePerGas)
if err != nil {
return nil, err
}
return &ExecutionPayloadHeaderElectra{
ParentHash: hexutil.Encode(payload.ParentHash),
FeeRecipient: hexutil.Encode(payload.FeeRecipient),
StateRoot: hexutil.Encode(payload.StateRoot),
ReceiptsRoot: hexutil.Encode(payload.ReceiptsRoot),
LogsBloom: hexutil.Encode(payload.LogsBloom),
PrevRandao: hexutil.Encode(payload.PrevRandao),
BlockNumber: fmt.Sprintf("%d", payload.BlockNumber),
GasLimit: fmt.Sprintf("%d", payload.GasLimit),
GasUsed: fmt.Sprintf("%d", payload.GasUsed),
Timestamp: fmt.Sprintf("%d", payload.Timestamp),
ExtraData: hexutil.Encode(payload.ExtraData),
BaseFeePerGas: baseFeePerGas,
BlockHash: hexutil.Encode(payload.BlockHash),
TransactionsRoot: hexutil.Encode(payload.TransactionsRoot),
WithdrawalsRoot: hexutil.Encode(payload.WithdrawalsRoot),
BlobGasUsed: fmt.Sprintf("%d", payload.BlobGasUsed),
ExcessBlobGas: fmt.Sprintf("%d", payload.ExcessBlobGas),
DepositRequestsRoot: hexutil.Encode(payload.DepositRequestsRoot),
WithdrawalRequestsRoot: hexutil.Encode(payload.WithdrawalRequestsRoot),
ConsolidationRequestsRoot: hexutil.Encode(payload.ConsolidationRequestsRoot),
}, nil
}
var ExecutionPayloadHeaderElectraFromConsensus = ExecutionPayloadHeaderDenebFromConsensus

View File

@@ -674,7 +674,7 @@ func BeaconStateElectraFromConsensus(st beaconState.BeaconState) (*BeaconStateEl
if err != nil {
return nil, err
}
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderElectra)
srcPayload, ok := execData.Proto().(*enginev1.ExecutionPayloadHeaderDeneb)
if !ok {
return nil, errPayloadHeaderNotFound
}

View File

@@ -8,22 +8,20 @@ go_library(
],
importpath = "github.com/prysmaticlabs/prysm/v5/async/event",
visibility = ["//visibility:public"],
deps = ["//time/mclock:go_default_library"],
deps = [
"//time/mclock:go_default_library",
"@com_github_ethereum_go_ethereum//event:go_default_library",
],
)
go_test(
name = "go_default_test",
size = "small",
srcs = [
"example_feed_test.go",
"example_scope_test.go",
"example_subscription_test.go",
"feed_test.go",
"subscription_test.go",
],
embed = [":go_default_library"],
deps = [
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
],
deps = ["//testing/require:go_default_library"],
)

View File

@@ -1,73 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package event_test
import (
"fmt"
"github.com/prysmaticlabs/prysm/v5/async/event"
)
func ExampleFeed_acknowledgedEvents() {
// This example shows how the return value of Send can be used for request/reply
// interaction between event consumers and producers.
var feed event.Feed
type ackedEvent struct {
i int
ack chan<- struct{}
}
// Consumers wait for events on the feed and acknowledge processing.
done := make(chan struct{})
defer close(done)
for i := 0; i < 3; i++ {
ch := make(chan ackedEvent, 100)
sub := feed.Subscribe(ch)
go func() {
defer sub.Unsubscribe()
for {
select {
case ev := <-ch:
fmt.Println(ev.i) // "process" the event
ev.ack <- struct{}{}
case <-done:
return
}
}
}()
}
// The producer sends values of type ackedEvent with increasing values of i.
// It waits for all consumers to acknowledge before sending the next event.
for i := 0; i < 3; i++ {
acksignal := make(chan struct{})
n := feed.Send(ackedEvent{i, acksignal})
for ack := 0; ack < n; ack++ {
<-acksignal
}
}
// Output:
// 0
// 0
// 0
// 1
// 1
// 1
// 2
// 2
// 2
}

View File

@@ -14,241 +14,11 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package event contains an event feed implementation for process communication.
package event
import (
"errors"
"reflect"
"slices"
"sync"
geth_event "github.com/ethereum/go-ethereum/event"
)
var errBadChannel = errors.New("event: Subscribe argument does not have sendable channel type")
// Feed implements one-to-many subscriptions where the carrier of events is a channel.
// Values sent to a Feed are delivered to all subscribed channels simultaneously.
//
// Feeds can only be used with a single type. The type is determined by the first Send or
// Subscribe operation. Subsequent calls to these methods panic if the type does not
// match.
//
// The zero value is ready to use.
type Feed struct {
once sync.Once // ensures that init only runs once
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
removeSub chan interface{} // interrupts Send
sendCases caseList // the active set of select cases used by Send
// The inbox holds newly subscribed channels until they are added to sendCases.
mu sync.Mutex
inbox caseList
etype reflect.Type
}
// This is the index of the first actual subscription channel in sendCases.
// sendCases[0] is a SelectRecv case for the removeSub channel.
const firstSubSendCase = 1
type feedTypeError struct {
got, want reflect.Type
op string
}
func (e feedTypeError) Error() string {
return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String()
}
func (f *Feed) init() {
f.removeSub = make(chan interface{})
f.sendLock = make(chan struct{}, 1)
f.sendLock <- struct{}{}
f.sendCases = caseList{{Chan: reflect.ValueOf(f.removeSub), Dir: reflect.SelectRecv}}
}
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel
// until the subscription is canceled. All channels added must have the same element type.
//
// The channel should have ample buffer space to avoid blocking other subscribers.
// Slow subscribers are not dropped.
func (f *Feed) Subscribe(channel interface{}) Subscription {
f.once.Do(f.init)
chanval := reflect.ValueOf(channel)
chantyp := chanval.Type()
if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 {
panic(errBadChannel)
}
sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)}
f.mu.Lock()
defer f.mu.Unlock()
if !f.typecheck(chantyp.Elem()) {
panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)})
}
// Add the select case to the inbox.
// The next Send will add it to f.sendCases.
cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval}
f.inbox = append(f.inbox, cas)
return sub
}
// note: callers must hold f.mu
func (f *Feed) typecheck(typ reflect.Type) bool {
if f.etype == nil {
f.etype = typ
return true
}
// In the event the feed's type is an actual interface, we
// perform an interface conformance check here.
if f.etype.Kind() == reflect.Interface && typ.Implements(f.etype) {
return true
}
return f.etype == typ
}
func (f *Feed) remove(sub *feedSub) {
// Delete from inbox first, which covers channels
// that have not been added to f.sendCases yet.
ch := sub.channel.Interface()
f.mu.Lock()
index := f.inbox.find(ch)
if index != -1 {
f.inbox = f.inbox.delete(index)
f.mu.Unlock()
return
}
f.mu.Unlock()
select {
case f.removeSub <- ch:
// Send will remove the channel from f.sendCases.
case <-f.sendLock:
// No Send is in progress, delete the channel now that we have the send lock.
f.sendCases = f.sendCases.delete(f.sendCases.find(ch))
f.sendLock <- struct{}{}
}
}
// Send delivers to all subscribed channels simultaneously.
// It returns the number of subscribers that the value was sent to.
func (f *Feed) Send(value interface{}) (nsent int) {
rvalue := reflect.ValueOf(value)
f.once.Do(f.init)
<-f.sendLock
// Add new cases from the inbox after taking the send lock.
f.mu.Lock()
f.sendCases = append(f.sendCases, f.inbox...)
f.inbox = nil
if !f.typecheck(rvalue.Type()) {
f.sendLock <- struct{}{}
f.mu.Unlock()
panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype})
}
f.mu.Unlock()
// Set the sent value on all channels.
for i := firstSubSendCase; i < len(f.sendCases); i++ {
f.sendCases[i].Send = rvalue
}
// Send until all channels except removeSub have been chosen. 'cases' tracks a prefix
// of sendCases. When a send succeeds, the corresponding case moves to the end of
// 'cases' and it shrinks by one element.
cases := f.sendCases
for {
// Fast path: try sending without blocking before adding to the select set.
// This should usually succeed if subscribers are fast enough and have free
// buffer space.
for i := firstSubSendCase; i < len(cases); i++ {
if cases[i].Chan.TrySend(rvalue) {
nsent++
cases = cases.deactivate(i)
i--
}
}
if len(cases) == firstSubSendCase {
break
}
// Select on all the receivers, waiting for them to unblock.
chosen, recv, _ := reflect.Select(cases)
if chosen == 0 /* <-f.removeSub */ {
index := f.sendCases.find(recv.Interface())
f.sendCases = f.sendCases.delete(index)
if index >= 0 && index < len(cases) {
// Shrink 'cases' too because the removed case was still active.
cases = f.sendCases[:len(cases)-1]
}
} else {
cases = cases.deactivate(chosen)
nsent++
}
}
// Forget about the sent value and hand off the send lock.
for i := firstSubSendCase; i < len(f.sendCases); i++ {
f.sendCases[i].Send = reflect.Value{}
}
f.sendLock <- struct{}{}
return nsent
}
type feedSub struct {
feed *Feed
channel reflect.Value
errOnce sync.Once
err chan error
}
// Unsubscribe remove feed subscription.
func (sub *feedSub) Unsubscribe() {
sub.errOnce.Do(func() {
sub.feed.remove(sub)
close(sub.err)
})
}
// Err returns error channel.
func (sub *feedSub) Err() <-chan error {
return sub.err
}
type caseList []reflect.SelectCase
// find returns the index of a case containing the given channel.
func (cs caseList) find(channel interface{}) int {
return slices.IndexFunc(cs, func(selectCase reflect.SelectCase) bool {
return selectCase.Chan.Interface() == channel
})
}
// delete removes the given case from cs.
func (cs caseList) delete(index int) caseList {
return append(cs[:index], cs[index+1:]...)
}
// deactivate moves the case at index into the non-accessible portion of the cs slice.
func (cs caseList) deactivate(index int) caseList {
last := len(cs) - 1
cs[index], cs[last] = cs[last], cs[index]
return cs[:last]
}
// func (cs caseList) String() string {
// s := "["
// for i, cas := range cs {
// if i != 0 {
// s += ", "
// }
// switch cas.Dir {
// case reflect.SelectSend:
// s += fmt.Sprintf("%v<-", cas.Chan.Interface())
// case reflect.SelectRecv:
// s += fmt.Sprintf("<-%v", cas.Chan.Interface())
// }
// }
// return s + "]"
// }
// Feed is a re-export of the go-ethereum event feed.
type Feed = geth_event.Feed

View File

@@ -1,509 +0,0 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package event
import (
"fmt"
"reflect"
"sync"
"testing"
"time"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
)
func TestFeedPanics(t *testing.T) {
{
var f Feed
f.Send(2)
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
// Validate it doesn't deadlock.
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
}
{
var f Feed
ch := make(chan int)
f.Subscribe(ch)
want := feedTypeError{op: "Send", got: reflect.TypeOf(uint64(0)), want: reflect.TypeOf(0)}
assert.NoError(t, checkPanic(want, func() { f.Send(uint64(2)) }))
}
{
var f Feed
f.Send(2)
want := feedTypeError{op: "Subscribe", got: reflect.TypeOf(make(chan uint64)), want: reflect.TypeOf(make(chan<- int))}
assert.NoError(t, checkPanic(want, func() { f.Subscribe(make(chan uint64)) }))
}
{
var f Feed
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(make(<-chan int)) }))
}
{
var f Feed
assert.NoError(t, checkPanic(errBadChannel, func() { f.Subscribe(0) }))
}
}
func checkPanic(want error, fn func()) (err error) {
defer func() {
panicResult := recover()
if panicResult == nil {
err = fmt.Errorf("didn't panic")
} else if !reflect.DeepEqual(panicResult, want) {
err = fmt.Errorf("panicked with wrong error: got %q, want %q", panicResult, want)
}
}()
fn()
return nil
}
func TestFeed(t *testing.T) {
var feed Feed
var done, subscribed sync.WaitGroup
subscriber := func(i int) {
defer done.Done()
subchan := make(chan int)
sub := feed.Subscribe(subchan)
timeout := time.NewTimer(2 * time.Second)
subscribed.Done()
select {
case v := <-subchan:
if v != 1 {
t.Errorf("%d: received value %d, want 1", i, v)
}
case <-timeout.C:
t.Errorf("%d: receive timeout", i)
}
sub.Unsubscribe()
select {
case _, ok := <-sub.Err():
if ok {
t.Errorf("%d: error channel not closed after unsubscribe", i)
}
case <-timeout.C:
t.Errorf("%d: unsubscribe timeout", i)
}
}
const n = 1000
done.Add(n)
subscribed.Add(n)
for i := 0; i < n; i++ {
go subscriber(i)
}
subscribed.Wait()
if nsent := feed.Send(1); nsent != n {
t.Errorf("first send delivered %d times, want %d", nsent, n)
}
if nsent := feed.Send(2); nsent != 0 {
t.Errorf("second send delivered %d times, want 0", nsent)
}
done.Wait()
}
func TestFeedSubscribeSameChannel(t *testing.T) {
var (
feed Feed
done sync.WaitGroup
ch = make(chan int)
sub1 = feed.Subscribe(ch)
sub2 = feed.Subscribe(ch)
_ = feed.Subscribe(ch)
)
expectSends := func(value, n int) {
if nsent := feed.Send(value); nsent != n {
t.Errorf("send delivered %d times, want %d", nsent, n)
}
done.Done()
}
expectRecv := func(wantValue, n int) {
for i := 0; i < n; i++ {
if v := <-ch; v != wantValue {
t.Errorf("received %d, want %d", v, wantValue)
}
}
}
done.Add(1)
go expectSends(1, 3)
expectRecv(1, 3)
done.Wait()
sub1.Unsubscribe()
done.Add(1)
go expectSends(2, 2)
expectRecv(2, 2)
done.Wait()
sub2.Unsubscribe()
done.Add(1)
go expectSends(3, 1)
expectRecv(3, 1)
done.Wait()
}
func TestFeedSubscribeBlockedPost(_ *testing.T) {
var (
feed Feed
nsends = 2000
ch1 = make(chan int)
ch2 = make(chan int)
wg sync.WaitGroup
)
defer wg.Wait()
feed.Subscribe(ch1)
wg.Add(nsends)
for i := 0; i < nsends; i++ {
go func() {
feed.Send(99)
wg.Done()
}()
}
sub2 := feed.Subscribe(ch2)
defer sub2.Unsubscribe()
// We're done when ch1 has received N times.
// The number of receives on ch2 depends on scheduling.
for i := 0; i < nsends; {
select {
case <-ch1:
i++
case <-ch2:
}
}
}
func TestFeedUnsubscribeBlockedPost(_ *testing.T) {
var (
feed Feed
nsends = 200
chans = make([]chan int, 2000)
subs = make([]Subscription, len(chans))
bchan = make(chan int)
bsub = feed.Subscribe(bchan)
wg sync.WaitGroup
)
for i := range chans {
chans[i] = make(chan int, nsends)
}
// Queue up some Sends. None of these can make progress while bchan isn't read.
wg.Add(nsends)
for i := 0; i < nsends; i++ {
go func() {
feed.Send(99)
wg.Done()
}()
}
// Subscribe the other channels.
for i, ch := range chans {
subs[i] = feed.Subscribe(ch)
}
// Unsubscribe them again.
for _, sub := range subs {
sub.Unsubscribe()
}
// Unblock the Sends.
bsub.Unsubscribe()
wg.Wait()
}
// Checks that unsubscribing a channel during Send works even if that
// channel has already been sent on.
func TestFeedUnsubscribeSentChan(_ *testing.T) {
var (
feed Feed
ch1 = make(chan int)
ch2 = make(chan int)
sub1 = feed.Subscribe(ch1)
sub2 = feed.Subscribe(ch2)
wg sync.WaitGroup
)
defer sub2.Unsubscribe()
wg.Add(1)
go func() {
feed.Send(0)
wg.Done()
}()
// Wait for the value on ch1.
<-ch1
// Unsubscribe ch1, removing it from the send cases.
sub1.Unsubscribe()
// Receive ch2, finishing Send.
<-ch2
wg.Wait()
// Send again. This should send to ch2 only, so the wait group will unblock
// as soon as a value is received on ch2.
wg.Add(1)
go func() {
feed.Send(0)
wg.Done()
}()
<-ch2
wg.Wait()
}
func TestFeedUnsubscribeFromInbox(t *testing.T) {
var (
feed Feed
ch1 = make(chan int)
ch2 = make(chan int)
sub1 = feed.Subscribe(ch1)
sub2 = feed.Subscribe(ch1)
sub3 = feed.Subscribe(ch2)
)
assert.Equal(t, 3, len(feed.inbox))
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
sub1.Unsubscribe()
sub2.Unsubscribe()
sub3.Unsubscribe()
assert.Equal(t, 0, len(feed.inbox), "Inbox is non-empty after unsubscribe")
assert.Equal(t, 1, len(feed.sendCases), "sendCases is non-empty after unsubscribe")
}
func BenchmarkFeedSend1000(b *testing.B) {
var (
done sync.WaitGroup
feed Feed
nsubs = 1000
)
subscriber := func(ch <-chan int) {
for i := 0; i < b.N; i++ {
<-ch
}
done.Done()
}
done.Add(nsubs)
for i := 0; i < nsubs; i++ {
ch := make(chan int, 200)
feed.Subscribe(ch)
go subscriber(ch)
}
// The actual benchmark.
b.ResetTimer()
for i := 0; i < b.N; i++ {
if feed.Send(i) != nsubs {
panic("wrong number of sends")
}
}
b.StopTimer()
done.Wait()
}
func TestFeed_Send(t *testing.T) {
tests := []struct {
name string
evFeed *Feed
testSetup func(fd *Feed, t *testing.T, o interface{})
obj interface{}
expectPanic bool
}{
{
name: "normal struct",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeedWithPointer, 1)
fd.Subscribe(testChan)
},
obj: testFeedWithPointer{
a: new(uint64),
b: new(string),
},
expectPanic: false,
},
{
name: "un-implemented interface",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeedIface, 1)
fd.Subscribe(testChan)
},
obj: testFeedWithPointer{
a: new(uint64),
b: new(string),
},
expectPanic: true,
},
{
name: "semi-implemented interface",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeedIface, 1)
fd.Subscribe(testChan)
},
obj: testFeed2{
a: 0,
b: "",
c: []byte{'A'},
},
expectPanic: true,
},
{
name: "fully-implemented interface",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeedIface)
// Make it unbuffered to allow message to
// pass through
go func() {
a := <-testChan
if !reflect.DeepEqual(a, o) {
t.Errorf("Got = %v, want = %v", a, o)
}
}()
fd.Subscribe(testChan)
},
obj: testFeed{
a: 0,
b: "",
},
expectPanic: false,
},
{
name: "fully-implemented interface with additional methods",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeedIface)
// Make it unbuffered to allow message to
// pass through
go func() {
a := <-testChan
if !reflect.DeepEqual(a, o) {
t.Errorf("Got = %v, want = %v", a, o)
}
}()
fd.Subscribe(testChan)
},
obj: testFeed3{
a: 0,
b: "",
c: []byte{'A'},
d: []byte{'B'},
},
expectPanic: false,
},
{
name: "concrete types implementing the same interface",
evFeed: new(Feed),
testSetup: func(fd *Feed, t *testing.T, o interface{}) {
testChan := make(chan testFeed, 1)
// Make it unbuffered to allow message to
// pass through
go func() {
a := <-testChan
if !reflect.DeepEqual(a, o) {
t.Errorf("Got = %v, want = %v", a, o)
}
}()
fd.Subscribe(testChan)
},
obj: testFeed3{
a: 0,
b: "",
c: []byte{'A'},
d: []byte{'B'},
},
expectPanic: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
defer func() {
if r := recover(); r != nil {
if !tt.expectPanic {
t.Errorf("panic triggered when unexpected: %v", r)
}
} else {
if tt.expectPanic {
t.Error("panic not triggered when expected")
}
}
}()
tt.testSetup(tt.evFeed, t, tt.obj)
if gotNsent := tt.evFeed.Send(tt.obj); gotNsent != 1 {
t.Errorf("Send() = %v, want %v", gotNsent, 1)
}
})
}
}
// The following objects below are a collection of different
// struct types to test with.
type testFeed struct {
a uint64
b string
}
func (testFeed) method1() {
}
func (testFeed) method2() {
}
type testFeedWithPointer struct {
a *uint64
b *string
}
type testFeed2 struct {
a uint64
b string
c []byte
}
func (testFeed2) method1() {
}
type testFeed3 struct {
a uint64
b string
c, d []byte
}
func (testFeed3) method1() {
}
func (testFeed3) method2() {
}
func (testFeed3) method3() {
}
type testFeedIface interface {
method1()
method2()
}

View File

@@ -6,6 +6,7 @@ go_library(
"chain_info.go",
"chain_info_forkchoice.go",
"currently_syncing_block.go",
"currently_syncing_execution_payload_envelope.go",
"defragment.go",
"error.go",
"execution_engine.go",
@@ -25,6 +26,8 @@ go_library(
"receive_attestation.go",
"receive_blob.go",
"receive_block.go",
"receive_execution_payload_envelope.go",
"receive_payload_attestation_message.go",
"service.go",
"tracked_proposer.go",
"weak_subjectivity_checks.go",
@@ -43,6 +46,7 @@ go_library(
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/altair:go_default_library",
"//beacon-chain/core/blocks:go_default_library",
"//beacon-chain/core/epbs:go_default_library",
"//beacon-chain/core/epoch/precompute:go_default_library",
"//beacon-chain/core/feed:go_default_library",
"//beacon-chain/core/feed/state:go_default_library",
@@ -97,6 +101,7 @@ go_library(
"@com_github_prometheus_client_golang//prometheus:go_default_library",
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_x_sync//errgroup:go_default_library",
],
)
@@ -109,6 +114,7 @@ go_test(
"chain_info_norace_test.go",
"chain_info_test.go",
"checktags_test.go",
"epbs_test.go",
"error_test.go",
"execution_engine_test.go",
"forkchoice_update_execution_test.go",
@@ -124,6 +130,7 @@ go_test(
"process_block_test.go",
"receive_attestation_test.go",
"receive_block_test.go",
"receive_execution_payload_envelope_test.go",
"service_norace_test.go",
"service_test.go",
"setup_test.go",
@@ -177,6 +184,7 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",

View File

@@ -42,7 +42,7 @@ type ForkchoiceFetcher interface {
GetProposerHead() [32]byte
SetForkChoiceGenesisTime(uint64)
UpdateHead(context.Context, primitives.Slot)
HighestReceivedBlockSlot() primitives.Slot
HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte)
ReceivedBlocksLastEpoch() (uint64, error)
InsertNode(context.Context, state.BeaconState, [32]byte) error
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
@@ -50,6 +50,8 @@ type ForkchoiceFetcher interface {
ProposerBoost() [32]byte
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
GetPTCVote(root [32]byte) primitives.PTCStatus
Weight(root [32]byte) (uint64, error)
}
// TimeFetcher retrieves the Ethereum consensus data that's related to time.
@@ -118,6 +120,12 @@ type OptimisticModeFetcher interface {
IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error)
}
// ExecutionPayloadFetcher defines a common interface that returns forkchoice
// information about payload block hashes
type ExecutionPayloadFetcher interface {
HashInForkchoice([32]byte) bool
}
// FinalizedCheckpt returns the latest finalized checkpoint from chain store.
func (s *Service) FinalizedCheckpt() *ethpb.Checkpoint {
s.cfg.ForkChoiceStore.RLock()
@@ -399,6 +407,14 @@ func (s *Service) InForkchoice(root [32]byte) bool {
return s.cfg.ForkChoiceStore.HasNode(root)
}
// HashInForkchoice returns true if the given payload block hash is found in
// forkchoice
func (s *Service) HashInForkchoice(hash [32]byte) bool {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.HasHash(hash)
}
// IsOptimisticForRoot takes the root as argument instead of the current head
// and returns true if it is optimistic.
func (s *Service) IsOptimisticForRoot(ctx context.Context, root [32]byte) (bool, error) {

View File

@@ -5,6 +5,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
@@ -29,11 +30,11 @@ func (s *Service) SetForkChoiceGenesisTime(timestamp uint64) {
s.cfg.ForkChoiceStore.SetGenesisTime(timestamp)
}
// HighestReceivedBlockSlot returns the corresponding value from forkchoice
func (s *Service) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot returns the corresponding value from forkchoice
func (s *Service) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlot()
return s.cfg.ForkChoiceStore.HighestReceivedBlockSlotRoot()
}
// ReceivedBlocksLastEpoch returns the corresponding value from forkchoice
@@ -99,3 +100,30 @@ func (s *Service) ParentRoot(root [32]byte) ([32]byte, error) {
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.ParentRoot(root)
}
// GetPTCVote wraps a call to the corresponding method in forkchoice and checks
// the currently syncing status
// Warning: this method will return the current PTC status regardless of
// timeliness. A client MUST call this method when about to submit a PTC
// attestation, that is exactly at the threshold to submit the attestation.
func (s *Service) GetPTCVote(root [32]byte) primitives.PTCStatus {
s.cfg.ForkChoiceStore.RLock()
f := s.cfg.ForkChoiceStore.GetPTCVote()
s.cfg.ForkChoiceStore.RUnlock()
if f != primitives.PAYLOAD_ABSENT {
return f
}
f, isSyncing := s.payloadBeingSynced.isSyncing(root)
if isSyncing {
return f
}
return primitives.PAYLOAD_ABSENT
}
// insertPayloadEnvelope wraps a locked call to the corresponding method in
// forkchoice
func (s *Service) insertPayloadEnvelope(envelope interfaces.ROExecutionPayloadEnvelope) error {
s.cfg.ForkChoiceStore.Lock()
defer s.cfg.ForkChoiceStore.Unlock()
return s.cfg.ForkChoiceStore.InsertPayloadEnvelope(envelope)
}

View File

@@ -0,0 +1,36 @@
package blockchain
import (
"sync"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
type currentlySyncingPayload struct {
sync.Mutex
roots map[[32]byte]primitives.PTCStatus
}
func (b *currentlySyncingPayload) set(envelope interfaces.ROExecutionPayloadEnvelope) {
b.Lock()
defer b.Unlock()
if envelope.PayloadWithheld() {
b.roots[envelope.BeaconBlockRoot()] = primitives.PAYLOAD_WITHHELD
} else {
b.roots[envelope.BeaconBlockRoot()] = primitives.PAYLOAD_PRESENT
}
}
func (b *currentlySyncingPayload) unset(root [32]byte) {
b.Lock()
defer b.Unlock()
delete(b.roots, root)
}
func (b *currentlySyncingPayload) isSyncing(root [32]byte) (status primitives.PTCStatus, isSyncing bool) {
b.Lock()
defer b.Unlock()
status, isSyncing = b.roots[root]
return
}

View File

@@ -0,0 +1,18 @@
package blockchain
import (
"testing"
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestServiceGetPTCVote(t *testing.T) {
c := &currentlySyncingPayload{roots: make(map[[32]byte]primitives.PTCStatus)}
s := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}, payloadBeingSynced: c}
r := [32]byte{'r'}
require.Equal(t, primitives.PAYLOAD_ABSENT, s.GetPTCVote(r))
c.roots[r] = primitives.PAYLOAD_WITHHELD
require.Equal(t, primitives.PAYLOAD_WITHHELD, s.GetPTCVote(r))
}

View File

@@ -30,6 +30,9 @@ var (
ErrNotCheckpoint = errors.New("not a checkpoint in forkchoice")
// ErrNilHead is returned when no head is present in the blockchain service.
ErrNilHead = errors.New("nil head")
// errInvalidValidatorIndex is returned when a validator index is
// invalid or unexpected
errInvalidValidatorIndex = errors.New("invalid validator index")
)
var errMaxBlobsExceeded = errors.New("Expected commitments in block exceeds MAX_BLOBS_PER_BLOCK")

View File

@@ -2,7 +2,6 @@ package blockchain
import (
"context"
"crypto/sha256"
"fmt"
"github.com/ethereum/go-ethereum/common"
@@ -28,8 +27,6 @@ import (
"github.com/sirupsen/logrus"
)
const blobCommitmentVersionKZG uint8 = 0x01
var defaultLatestValidHash = bytesutil.PadTo([]byte{0xff}, 32)
// notifyForkchoiceUpdate signals execution engine the fork choice updates. Execution engine should:
@@ -402,13 +399,7 @@ func kzgCommitmentsToVersionedHashes(body interfaces.ReadOnlyBeaconBlockBody) ([
versionedHashes := make([]common.Hash, len(commitments))
for i, commitment := range commitments {
versionedHashes[i] = ConvertKzgCommitmentToVersionedHash(commitment)
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(commitment)
}
return versionedHashes, nil
}
func ConvertKzgCommitmentToVersionedHash(commitment []byte) common.Hash {
versionedHash := sha256.Sum256(commitment)
versionedHash[0] = blobCommitmentVersionKZG
return versionedHash
}

View File

@@ -1056,8 +1056,8 @@ func TestService_removeInvalidBlockAndState(t *testing.T) {
require.NoError(t, service.removeInvalidBlockAndState(ctx, [][32]byte{r1, r2}))
require.Equal(t, false, service.hasBlock(ctx, r1))
require.Equal(t, false, service.hasBlock(ctx, r2))
require.Equal(t, false, service.chainHasBlock(ctx, r1))
require.Equal(t, false, service.chainHasBlock(ctx, r2))
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r1))
require.Equal(t, false, service.cfg.BeaconDB.HasStateSummary(ctx, r2))
has, err := service.cfg.StateGen.HasState(ctx, r1)

View File

@@ -182,6 +182,10 @@ var (
Name: "chain_service_processing_milliseconds",
Help: "Total time to call a chain service in ReceiveBlock()",
})
executionEngineProcessingTime = promauto.NewSummary(prometheus.SummaryOpts{
Name: "execution_engine_processing_milliseconds",
Help: "Total time to process an execution payload envelope in ReceiveExecutionPayloadEnvelope()",
})
dataAvailWaitedTime = promauto.NewSummary(prometheus.SummaryOpts{
Name: "da_waited_time_milliseconds",
Help: "Total time spent waiting for a data availability check in ReceiveBlock()",

View File

@@ -1,6 +1,8 @@
package blockchain
import (
"sync"
"github.com/prysmaticlabs/prysm/v5/async/event"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
statefeed "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/feed/state"
@@ -69,6 +71,22 @@ func WithDepositCache(c cache.DepositCache) Option {
}
}
// WithPayloadAttestationCache for payload attestation cache.
func WithPayloadAttestationCache(c *cache.PayloadAttestationCache) Option {
return func(s *Service) error {
s.cfg.PayloadAttestationCache = c
return nil
}
}
// WithPayloadEnvelopeCache for payload envelope cache.
func WithPayloadEnvelopeCache(c *sync.Map) Option {
return func(s *Service) error {
s.cfg.PayloadEnvelopeCache = c
return nil
}
}
// WithPayloadIDCache for payload ID cache.
func WithPayloadIDCache(c *cache.PayloadIDCache) Option {
return func(s *Service) error {

View File

@@ -97,7 +97,7 @@ func (s *Service) OnAttestation(ctx context.Context, a ethpb.Att, disparity time
// We assume trusted attestation in this function has verified signature.
// Update forkchoice store with the new attestation for updating weight.
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Target.Epoch)
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indexedAtt.GetAttestingIndices(), bytesutil.ToBytes32(a.GetData().BeaconBlockRoot), a.GetData().Slot)
return nil
}

View File

@@ -375,7 +375,7 @@ func (s *Service) handleBlockAttestations(ctx context.Context, blk interfaces.Re
}
r := bytesutil.ToBytes32(a.GetData().BeaconBlockRoot)
if s.cfg.ForkChoiceStore.HasNode(r) {
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Target.Epoch)
s.cfg.ForkChoiceStore.ProcessAttestation(ctx, indices, r, a.GetData().Slot)
} else if err := s.cfg.AttPool.SaveBlockAttestation(a); err != nil {
return err
}

View File

@@ -177,7 +177,7 @@ func (s *Service) processAttestations(ctx context.Context, disparity time.Durati
}
hasState := s.cfg.BeaconDB.HasStateSummary(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
hasBlock := s.hasBlock(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
hasBlock := s.chainHasBlock(ctx, bytesutil.ToBytes32(a.GetData().BeaconBlockRoot))
if !(hasState && hasBlock) {
continue
}

View File

@@ -39,18 +39,30 @@ var epochsSinceFinalityExpandCache = primitives.Epoch(4)
// BlockReceiver interface defines the methods of chain service for receiving and processing new blocks.
type BlockReceiver interface {
ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte, avs das.AvailabilityStore) error
ReceiveExecutionPayloadEnvelope(ctx context.Context, env interfaces.ROExecutionPayloadEnvelope, avs das.AvailabilityStore) error
ReceiveBlockBatch(ctx context.Context, blocks []blocks.ROBlock, avs das.AvailabilityStore) error
HasBlock(ctx context.Context, root [32]byte) bool
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
BlockBeingSynced([32]byte) bool
}
// PayloadAttestationReceiver defines methods of the chain service for receiving
// and processing new payload attestations and payload attestation messages
type PayloadAttestationReceiver interface {
ReceivePayloadAttestationMessage(ctx context.Context, a *ethpb.PayloadAttestationMessage) error
}
// BlobReceiver interface defines the methods of chain service for receiving new
// blobs
type BlobReceiver interface {
ReceiveBlob(context.Context, blocks.VerifiedROBlob) error
}
// ExecutionPayloadReceiver interface defines the methods of chain service for receiving `ROExecutionPayloadEnvelope`.
type ExecutionPayloadReceiver interface {
ReceiveExecutionPayloadEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error
}
// SlashingReceiver interface defines the methods of chain service for receiving validated slashing over the wire.
type SlashingReceiver interface {
ReceiveAttesterSlashing(ctx context.Context, slashing ethpb.AttSlashing)

View File

@@ -0,0 +1,152 @@
package blockchain
import (
"context"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
"golang.org/x/sync/errgroup"
)
// ReceiveExecutionPayloadEnvelope is a function that defines the operations (minus pubsub)
// that are performed on a received execution payload envelope. The operations consist of:
// 1. Validate the payload, apply state transition.
// 2. Apply fork choice to the processed payload
// 3. Save latest head info
func (s *Service) ReceiveExecutionPayloadEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error {
receivedTime := time.Now()
root := envelope.BeaconBlockRoot()
s.payloadBeingSynced.set(envelope)
defer s.payloadBeingSynced.unset(root)
preState, err := s.getPayloadEnvelopePrestate(ctx, envelope)
if err != nil {
return errors.Wrap(err, "could not get prestate")
}
eg, _ := errgroup.WithContext(ctx)
var postState state.BeaconState
eg.Go(func() error {
if err := epbs.ValidatePayloadStateTransition(ctx, preState, envelope); err != nil {
return errors.Wrap(err, "failed to validate consensus state transition function")
}
return nil
})
var isValidPayload bool
eg.Go(func() error {
var err error
isValidPayload, err = s.validateExecutionOnEnvelope(ctx, envelope)
if err != nil {
return errors.Wrap(err, "could not notify the engine of the new payload")
}
return nil
})
if err := eg.Wait(); err != nil {
return err
}
_ = isValidPayload
_ = postState
daStartTime := time.Now()
// TODO: Add DA check
daWaitedTime := time.Since(daStartTime)
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
// TODO: Add Head update, cache handling, postProcessing
if err := s.insertPayloadEnvelope(envelope); err != nil {
return errors.Wrap(err, "could not insert payload to forkchoice")
}
timeWithoutDaWait := time.Since(receivedTime) - daWaitedTime
executionEngineProcessingTime.Observe(float64(timeWithoutDaWait.Milliseconds()))
return nil
}
// notifyNewPayload signals execution engine on a new payload.
// It returns true if the EL has returned VALID for the block
func (s *Service) notifyNewEnvelope(ctx context.Context, envelope interfaces.ROExecutionPayloadEnvelope) (bool, error) {
ctx, span := trace.StartSpan(ctx, "blockChain.notifyNewPayload")
defer span.End()
payload, err := envelope.Execution()
if err != nil {
return false, errors.Wrap(err, "could not get execution payload")
}
versionedHashes := envelope.VersionedHashes()
root := envelope.BeaconBlockRoot()
parentRoot, err := s.ParentRoot(root)
if err != nil {
return false, errors.Wrap(err, "could not get parent block root")
}
pr := common.Hash(parentRoot)
lastValidHash, err := s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &pr)
switch {
case err == nil:
newPayloadValidNodeCount.Inc()
return true, nil
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
newPayloadOptimisticNodeCount.Inc()
log.WithFields(logrus.Fields{
"payloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(payload.BlockHash())),
}).Info("Called new payload with optimistic block")
return false, nil
case errors.Is(err, execution.ErrInvalidPayloadStatus):
lvh := bytesutil.ToBytes32(lastValidHash)
return false, invalidBlock{
error: ErrInvalidPayload,
lastValidHash: lvh,
}
default:
return false, errors.WithMessage(ErrUndefinedExecutionEngineError, err.Error())
}
}
// validateExecutionOnEnvelope notifies the engine of the incoming execution payload and returns true if the payload is valid
func (s *Service) validateExecutionOnEnvelope(ctx context.Context, e interfaces.ROExecutionPayloadEnvelope) (bool, error) {
isValidPayload, err := s.notifyNewEnvelope(ctx, e)
if err == nil {
return isValidPayload, nil
}
blockRoot := e.BeaconBlockRoot()
parentRoot, rootErr := s.ParentRoot(blockRoot)
if rootErr != nil {
return false, errors.Wrap(rootErr, "could not get parent block root")
}
s.cfg.ForkChoiceStore.Lock()
err = s.handleInvalidExecutionError(ctx, err, blockRoot, parentRoot)
s.cfg.ForkChoiceStore.Unlock()
return false, err
}
func (s *Service) getPayloadEnvelopePrestate(ctx context.Context, e interfaces.ROExecutionPayloadEnvelope) (state.BeaconState, error) {
ctx, span := trace.StartSpan(ctx, "blockChain.getPayloadEnvelopePreState")
defer span.End()
// Verify incoming payload has a valid pre state.
root := e.BeaconBlockRoot()
// Verify the referred block is known to forkchoice
if !s.InForkchoice(root) {
return nil, errors.New("Cannot import execution payload envelope for unknown block")
}
if err := s.verifyBlkPreState(ctx, root); err != nil {
return nil, errors.Wrap(err, "could not verify payload prestate")
}
preState, err := s.cfg.StateGen.StateByRoot(ctx, root)
if err != nil {
return nil, errors.Wrap(err, "could not get pre state")
}
if preState == nil || preState.IsNil() {
return nil, errors.Wrap(err, "nil pre state")
}
return preState, nil
}

View File

@@ -0,0 +1,99 @@
package blockchain
import (
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/cache"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
mockExecution "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
)
func Test_getPayloadEnvelopePrestate(t *testing.T) {
service, tr := minimalTestService(t)
ctx, fcs := tr.ctx, tr.fcs
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
p := random.ExecutionPayloadEnvelope(t)
p.BeaconBlockRoot = service.originBlockRoot[:]
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
_, err = service.getPayloadEnvelopePrestate(ctx, e)
require.NoError(t, err)
}
func Test_notifyNewEnvelope(t *testing.T) {
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
ctx, fcs := tr.ctx, tr.fcs
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
p := random.ExecutionPayloadEnvelope(t)
p.BeaconBlockRoot = service.originBlockRoot[:]
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
engine := &mockExecution.EngineClient{}
service.cfg.ExecutionEngineCaller = engine
isValidPayload, err := service.notifyNewEnvelope(ctx, e)
require.NoError(t, err)
require.Equal(t, true, isValidPayload)
}
func Test_validateExecutionOnEnvelope(t *testing.T) {
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
ctx, fcs := tr.ctx, tr.fcs
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
p := random.ExecutionPayloadEnvelope(t)
p.BeaconBlockRoot = service.originBlockRoot[:]
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
engine := &mockExecution.EngineClient{}
service.cfg.ExecutionEngineCaller = engine
isValidPayload, err := service.validateExecutionOnEnvelope(ctx, e)
require.NoError(t, err)
require.Equal(t, true, isValidPayload)
}
func Test_ReceiveExecutionPayloadEnvelope(t *testing.T) {
service, tr := minimalTestService(t, WithPayloadIDCache(cache.NewPayloadIDCache()))
ctx, fcs := tr.ctx, tr.fcs
gs, _ := util.DeterministicGenesisStateEpbs(t, 32)
require.NoError(t, service.saveGenesisData(ctx, gs))
require.NoError(t, fcs.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Root: service.originBlockRoot}))
post := gs.Copy()
p := &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
ParentHash: make([]byte, 32),
BlockHash: make([]byte, 32),
},
BeaconBlockRoot: service.originBlockRoot[:],
BlobKzgCommitments: make([][]byte, 0),
StateRoot: make([]byte, 32),
}
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
das := &das.MockAvailabilityStore{}
blockHeader := post.LatestBlockHeader()
prevStateRoot, err := post.HashTreeRoot(ctx)
require.NoError(t, err)
blockHeader.StateRoot = prevStateRoot[:]
require.NoError(t, post.SetLatestBlockHeader(blockHeader))
stRoot, err := post.HashTreeRoot(ctx)
require.NoError(t, err)
p.StateRoot = stRoot[:]
engine := &mockExecution.EngineClient{}
service.cfg.ExecutionEngineCaller = engine
require.NoError(t, service.ReceiveExecutionPayloadEnvelope(ctx, e, das))
}

View File

@@ -0,0 +1,33 @@
package blockchain
import (
"context"
"slices"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
func (s *Service) ReceivePayloadAttestationMessage(ctx context.Context, a *eth.PayloadAttestationMessage) error {
if err := helpers.ValidateNilPayloadAttestationMessage(a); err != nil {
return err
}
root := [32]byte(a.Data.BeaconBlockRoot)
st, err := s.HeadStateReadOnly(ctx)
if err != nil {
return err
}
ptc, err := helpers.GetPayloadTimelinessCommittee(ctx, st, a.Data.Slot)
if err != nil {
return err
}
idx := slices.Index(ptc, a.ValidatorIndex)
if idx == -1 {
return errInvalidValidatorIndex
}
if s.cfg.PayloadAttestationCache.Seen(root, uint64(primitives.ValidatorIndex(idx))) {
return nil
}
return s.cfg.PayloadAttestationCache.Add(a, uint64(idx))
}

View File

@@ -65,6 +65,7 @@ type Service struct {
syncComplete chan struct{}
blobNotifiers *blobNotifierMap
blockBeingSynced *currentlySyncingBlock
payloadBeingSynced *currentlySyncingPayload
blobStorage *filesystem.BlobStorage
lastPublishedLightClientEpoch primitives.Epoch
}
@@ -75,6 +76,8 @@ type config struct {
ChainStartFetcher execution.ChainStartFetcher
BeaconDB db.HeadAccessDatabase
DepositCache cache.DepositCache
PayloadAttestationCache *cache.PayloadAttestationCache
PayloadEnvelopeCache *sync.Map
PayloadIDCache *cache.PayloadIDCache
TrackedValidatorsCache *cache.TrackedValidatorsCache
AttPool attestations.Pool
@@ -179,6 +182,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
blobNotifiers: bn,
cfg: &config{},
blockBeingSynced: &currentlySyncingBlock{roots: make(map[[32]byte]struct{})},
payloadBeingSynced: &currentlySyncingPayload{roots: make(map[[32]byte]primitives.PTCStatus)},
}
for _, opt := range opts {
if err := opt(srv); err != nil {
@@ -544,7 +548,7 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon
// 2.) Check DB.
// Checking 1.) is ten times faster than checking 2.)
// this function requires a lock in forkchoice
func (s *Service) hasBlock(ctx context.Context, root [32]byte) bool {
func (s *Service) chainHasBlock(ctx context.Context, root [32]byte) bool {
if s.cfg.ForkChoiceStore.HasNode(root) {
return true
}

View File

@@ -382,8 +382,8 @@ func TestHasBlock_ForkChoiceAndDB_DoublyLinkedTree(t *testing.T) {
require.NoError(t, err)
require.NoError(t, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, r))
assert.Equal(t, false, s.hasBlock(ctx, [32]byte{}), "Should not have block")
assert.Equal(t, true, s.hasBlock(ctx, r), "Should have block")
assert.Equal(t, false, s.chainHasBlock(ctx, [32]byte{}), "Should not have block")
assert.Equal(t, true, s.chainHasBlock(ctx, r), "Should have block")
}
func TestServiceStop_SaveCachedBlocks(t *testing.T) {

View File

@@ -3,7 +3,10 @@ load("@prysm//tools/go:def.bzl", "go_library")
go_library(
name = "go_default_library",
testonly = True,
srcs = ["mock.go"],
srcs = [
"mock.go",
"mock_epbs.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/blockchain/testing",
visibility = [
"//beacon-chain:__subpackages__",

View File

@@ -37,44 +37,50 @@ var ErrNilState = errors.New("nil state")
// ChainService defines the mock interface for testing
type ChainService struct {
NotFinalized bool
Optimistic bool
ValidAttestation bool
ValidatorsRoot [32]byte
PublicKey [fieldparams.BLSPubkeyLength]byte
FinalizedCheckPoint *ethpb.Checkpoint
CurrentJustifiedCheckPoint *ethpb.Checkpoint
PreviousJustifiedCheckPoint *ethpb.Checkpoint
Slot *primitives.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect.
Balance *precompute.Balance
CanonicalRoots map[[32]byte]bool
Fork *ethpb.Fork
ETH1Data *ethpb.Eth1Data
InitSyncBlockRoots map[[32]byte]bool
DB db.Database
State state.BeaconState
Block interfaces.ReadOnlySignedBeaconBlock
VerifyBlkDescendantErr error
stateNotifier statefeed.Notifier
BlocksReceived []interfaces.ReadOnlySignedBeaconBlock
SyncCommitteeIndices []primitives.CommitteeIndex
blockNotifier blockfeed.Notifier
opNotifier opfeed.Notifier
Root []byte
SyncCommitteeDomain []byte
SyncSelectionProofDomain []byte
SyncContributionProofDomain []byte
SyncCommitteePubkeys [][]byte
Genesis time.Time
ForkChoiceStore forkchoice.ForkChoicer
ReceiveBlockMockErr error
OptimisticCheckRootReceived [32]byte
FinalizedRoots map[[32]byte]bool
OptimisticRoots map[[32]byte]bool
BlockSlot primitives.Slot
SyncingRoot [32]byte
Blobs []blocks.VerifiedROBlob
TargetRoot [32]byte
NotFinalized bool
Optimistic bool
ValidAttestation bool
ValidatorsRoot [32]byte
PublicKey [fieldparams.BLSPubkeyLength]byte
FinalizedCheckPoint *ethpb.Checkpoint
CurrentJustifiedCheckPoint *ethpb.Checkpoint
PreviousJustifiedCheckPoint *ethpb.Checkpoint
Slot *primitives.Slot // Pointer because 0 is a useful value, so checking against it can be incorrect.
Balance *precompute.Balance
CanonicalRoots map[[32]byte]bool
Fork *ethpb.Fork
ETH1Data *ethpb.Eth1Data
InitSyncBlockRoots map[[32]byte]bool
DB db.Database
State state.BeaconState
Block interfaces.ReadOnlySignedBeaconBlock
ExecutionPayloadEnvelope interfaces.ROExecutionPayloadEnvelope
VerifyBlkDescendantErr error
stateNotifier statefeed.Notifier
BlocksReceived []interfaces.ReadOnlySignedBeaconBlock
SyncCommitteeIndices []primitives.CommitteeIndex
blockNotifier blockfeed.Notifier
opNotifier opfeed.Notifier
Root []byte
SyncCommitteeDomain []byte
SyncSelectionProofDomain []byte
SyncContributionProofDomain []byte
SyncCommitteePubkeys [][]byte
Genesis time.Time
ForkChoiceStore forkchoice.ForkChoicer
ReceiveBlockMockErr error
ReceiveEnvelopeMockErr error
OptimisticCheckRootReceived [32]byte
FinalizedRoots map[[32]byte]bool
OptimisticRoots map[[32]byte]bool
BlockSlot primitives.Slot
SyncingRoot [32]byte
Blobs []blocks.VerifiedROBlob
TargetRoot [32]byte
HighestReceivedSlot primitives.Slot
HighestReceivedRoot [32]byte
PayloadStatus primitives.PTCStatus
ReceivePayloadAttestationMessageErr error
}
func (s *ChainService) Ancestor(ctx context.Context, root []byte, slot primitives.Slot) ([]byte, error) {
@@ -567,12 +573,12 @@ func (s *ChainService) ReceivedBlocksLastEpoch() (uint64, error) {
return 0, nil
}
// HighestReceivedBlockSlot mocks the same method in the chain service
func (s *ChainService) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot mocks the same method in the chain service
func (s *ChainService) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
if s.ForkChoiceStore != nil {
return s.ForkChoiceStore.HighestReceivedBlockSlot()
return s.ForkChoiceStore.HighestReceivedBlockSlotRoot()
}
return 0
return s.HighestReceivedSlot, s.HighestReceivedRoot
}
// InsertNode mocks the same method in the chain service
@@ -632,3 +638,17 @@ func (c *ChainService) ReceiveBlob(_ context.Context, b blocks.VerifiedROBlob) e
func (c *ChainService) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
return c.TargetRoot, nil
}
// HashInForkchoice mocks the same method in the chain service
func (c *ChainService) HashInForkchoice([32]byte) bool {
return false
}
// ReceivePayloadAttestationMessage mocks the same method in the chain service
func (c *ChainService) ReceivePayloadAttestationMessage(_ context.Context, _ *ethpb.PayloadAttestationMessage) error {
return c.ReceivePayloadAttestationMessageErr
}
func (c *ChainService) GetPTCVote(root [32]byte) primitives.PTCStatus {
return c.PayloadStatus
}

View File

@@ -0,0 +1,25 @@
package testing
import (
"context"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/das"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
)
// ReceiveExecutionPayloadEnvelope mocks the method in chain service.
func (s *ChainService) ReceiveExecutionPayloadEnvelope(ctx context.Context, env interfaces.ROExecutionPayloadEnvelope, _ das.AvailabilityStore) error {
if s.ReceiveBlockMockErr != nil {
return s.ReceiveBlockMockErr
}
if s.State == nil {
return ErrNilState
}
if s.State.Slot() == env.Slot() {
if err := s.State.SetLatestFullSlot(s.State.Slot()); err != nil {
return err
}
}
s.ExecutionPayloadEnvelope = env
return nil
}

View File

@@ -15,11 +15,13 @@ go_library(
"doc.go",
"error.go",
"interfaces.go",
"payload_attestation.go",
"payload_id.go",
"proposer_indices.go",
"proposer_indices_disabled.go", # keep
"proposer_indices_type.go",
"registration.go",
"signed_execution_header.go",
"skip_slot_cache.go",
"subnet_ids.go",
"sync_committee.go",
@@ -42,11 +44,13 @@ go_library(
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/slice:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//crypto/rand:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//monitoring/tracing/trace:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
@@ -70,10 +74,12 @@ go_test(
"checkpoint_state_test.go",
"committee_fuzz_test.go",
"committee_test.go",
"payload_attestation_test.go",
"payload_id_test.go",
"private_access_test.go",
"proposer_indices_test.go",
"registration_test.go",
"signed_execution_header_test.go",
"skip_slot_cache_test.go",
"subnet_ids_test.go",
"sync_committee_head_state_test.go",
@@ -88,7 +94,9 @@ go_test(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/primitives:go_default_library",
"//crypto/bls:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",

View File

@@ -0,0 +1,132 @@
package cache
import (
"errors"
"sync"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
var errNilPayloadAttestationMessage = errors.New("nil Payload Attestation Message")
// PayloadAttestationCache keeps a map of all the PTC votes that were seen,
// already aggregated. The key is the beacon block root.
type PayloadAttestationCache struct {
root [32]byte
attestations [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation
sync.Mutex
}
// Seen returns true if a vote for the given Beacon Block Root has already been processed
// for this Payload Timeliness Committee index. This will return true even if
// the Payload status differs.
func (p *PayloadAttestationCache) Seen(root [32]byte, idx uint64) bool {
p.Lock()
defer p.Unlock()
if p.root != root {
return false
}
for _, agg := range p.attestations {
if agg == nil {
continue
}
if agg.AggregationBits.BitAt(idx) {
return true
}
}
return false
}
// messageToPayloadAttestation creates a PayloadAttestation with a single
// aggregated bit from the passed PayloadAttestationMessage
func messageToPayloadAttestation(att *eth.PayloadAttestationMessage, idx uint64) *eth.PayloadAttestation {
bits := primitives.NewPayloadAttestationAggregationBits()
bits.SetBitAt(idx, true)
data := &eth.PayloadAttestationData{
BeaconBlockRoot: bytesutil.SafeCopyBytes(att.Data.BeaconBlockRoot),
Slot: att.Data.Slot,
PayloadStatus: att.Data.PayloadStatus,
}
return &eth.PayloadAttestation{
AggregationBits: bits,
Data: data,
Signature: bytesutil.SafeCopyBytes(att.Signature),
}
}
// aggregateSigFromMessage returns the aggregated signature from a Payload
// Attestation by adding the passed signature in the PayloadAttestationMessage,
// no signature validation is performed.
func aggregateSigFromMessage(aggregated *eth.PayloadAttestation, message *eth.PayloadAttestationMessage) ([]byte, error) {
aggSig, err := bls.SignatureFromBytesNoValidation(aggregated.Signature)
if err != nil {
return nil, err
}
sig, err := bls.SignatureFromBytesNoValidation(message.Signature)
if err != nil {
return nil, err
}
return bls.AggregateSignatures([]bls.Signature{aggSig, sig}).Marshal(), nil
}
// Add adds a PayloadAttestationMessage to the internal cache of aggregated
// PayloadAttestations.
// If the index has already been seen for this attestation status the function does nothing.
// If the root is not the cached root, the function will clear the previous cache
// This function assumes that the message has already been validated. In
// particular that the signature is valid and that the block root corresponds to
// the given slot in the attestation data.
func (p *PayloadAttestationCache) Add(att *eth.PayloadAttestationMessage, idx uint64) error {
if att == nil || att.Data == nil || att.Data.BeaconBlockRoot == nil {
return errNilPayloadAttestationMessage
}
p.Lock()
defer p.Unlock()
root := [32]byte(att.Data.BeaconBlockRoot)
if p.root != root {
p.root = root
p.attestations = [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{}
}
agg := p.attestations[att.Data.PayloadStatus]
if agg == nil {
p.attestations[att.Data.PayloadStatus] = messageToPayloadAttestation(att, idx)
return nil
}
if agg.AggregationBits.BitAt(idx) {
return nil
}
sig, err := aggregateSigFromMessage(agg, att)
if err != nil {
return err
}
agg.Signature = sig
agg.AggregationBits.SetBitAt(idx, true)
return nil
}
// Get returns the aggregated PayloadAttestation for the given root and status
// if the root doesn't exist or status is invalid, the function returns nil.
func (p *PayloadAttestationCache) Get(root [32]byte, status primitives.PTCStatus) *eth.PayloadAttestation {
p.Lock()
defer p.Unlock()
if p.root != root {
return nil
}
if status >= primitives.PAYLOAD_INVALID_STATUS {
return nil
}
return eth.CopyPayloadAttestation(p.attestations[status])
}
// Clear clears the internal map
func (p *PayloadAttestationCache) Clear() {
p.Lock()
defer p.Unlock()
p.root = [32]byte{}
p.attestations = [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{}
}

View File

@@ -0,0 +1,143 @@
package cache
import (
"testing"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestPayloadAttestationCache(t *testing.T) {
p := &PayloadAttestationCache{}
//Test Has seen
root := [32]byte{'r'}
idx := uint64(5)
require.Equal(t, false, p.Seen(root, idx))
// Test Add
msg := &eth.PayloadAttestationMessage{
Signature: bls.NewAggregateSignature().Marshal(),
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: root[:],
Slot: 1,
PayloadStatus: primitives.PAYLOAD_PRESENT,
},
}
// Add new root
require.NoError(t, p.Add(msg, idx))
require.Equal(t, true, p.Seen(root, idx))
require.Equal(t, root, p.root)
att := p.attestations[primitives.PAYLOAD_PRESENT]
indices := att.AggregationBits.BitIndices()
require.DeepEqual(t, []int{int(idx)}, indices)
singleSig := bytesutil.SafeCopyBytes(msg.Signature)
require.DeepEqual(t, singleSig, att.Signature)
// Test Seen
require.Equal(t, true, p.Seen(root, idx))
require.Equal(t, false, p.Seen(root, idx+1))
// Add another attestation on the same data
msg2 := &eth.PayloadAttestationMessage{
Signature: bls.NewAggregateSignature().Marshal(),
Data: att.Data,
}
idx2 := uint64(7)
require.NoError(t, p.Add(msg2, idx2))
att = p.attestations[primitives.PAYLOAD_PRESENT]
indices = att.AggregationBits.BitIndices()
require.DeepEqual(t, []int{int(idx), int(idx2)}, indices)
require.DeepNotEqual(t, att.Signature, msg.Signature)
// Try again the same index
require.NoError(t, p.Add(msg2, idx2))
att2 := p.attestations[primitives.PAYLOAD_PRESENT]
indices = att.AggregationBits.BitIndices()
require.DeepEqual(t, []int{int(idx), int(idx2)}, indices)
require.DeepEqual(t, att, att2)
// Test Seen
require.Equal(t, true, p.Seen(root, idx2))
require.Equal(t, false, p.Seen(root, idx2+1))
// Add another payload status for a different payload status
msg3 := &eth.PayloadAttestationMessage{
Signature: bls.NewAggregateSignature().Marshal(),
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: root[:],
Slot: 1,
PayloadStatus: primitives.PAYLOAD_WITHHELD,
},
}
idx3 := uint64(17)
require.NoError(t, p.Add(msg3, idx3))
att3 := p.attestations[primitives.PAYLOAD_WITHHELD]
indices3 := att3.AggregationBits.BitIndices()
require.DeepEqual(t, []int{int(idx3)}, indices3)
require.DeepEqual(t, singleSig, att3.Signature)
// Add a different root
root2 := [32]byte{'s'}
msg.Data.BeaconBlockRoot = root2[:]
require.NoError(t, p.Add(msg, idx))
require.Equal(t, root2, p.root)
require.Equal(t, true, p.Seen(root2, idx))
require.Equal(t, false, p.Seen(root, idx))
att = p.attestations[primitives.PAYLOAD_PRESENT]
indices = att.AggregationBits.BitIndices()
require.DeepEqual(t, []int{int(idx)}, indices)
}
func TestPayloadAttestationCache_Get(t *testing.T) {
root := [32]byte{1, 2, 3}
wrongRoot := [32]byte{4, 5, 6}
status := primitives.PAYLOAD_PRESENT
invalidStatus := primitives.PAYLOAD_INVALID_STATUS
cache := &PayloadAttestationCache{
root: root,
attestations: [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{
{
Signature: []byte{1},
},
{
Signature: []byte{2},
},
{
Signature: []byte{3},
},
},
}
t.Run("valid root and status", func(t *testing.T) {
result := cache.Get(root, status)
require.NotNil(t, result, "Expected a non-nil result")
require.DeepEqual(t, cache.attestations[status], result)
})
t.Run("invalid root", func(t *testing.T) {
result := cache.Get(wrongRoot, status)
require.IsNil(t, result)
})
t.Run("status out of bound", func(t *testing.T) {
result := cache.Get(root, invalidStatus)
require.IsNil(t, result)
})
t.Run("no attestation", func(t *testing.T) {
emptyCache := &PayloadAttestationCache{
root: root,
attestations: [primitives.PAYLOAD_INVALID_STATUS]*eth.PayloadAttestation{},
}
result := emptyCache.Get(root, status)
require.IsNil(t, result)
})
}

View File

@@ -0,0 +1,76 @@
package cache
import (
"bytes"
"sync"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
)
// ExecutionPayloadHeaders is used by the sync service to store signed execution payload headers after they pass validation,
// and filter out subsequent headers with lower value.
// The signed header from this cache could be used by the proposer when proposing the next slot.
type ExecutionPayloadHeaders struct {
headers map[primitives.Slot][]*enginev1.SignedExecutionPayloadHeader
sync.RWMutex
}
func NewExecutionPayloadHeaders() *ExecutionPayloadHeaders {
return &ExecutionPayloadHeaders{
headers: make(map[primitives.Slot][]*enginev1.SignedExecutionPayloadHeader),
}
}
// SaveSignedExecutionPayloadHeader saves the signed execution payload header to the cache.
// The cache stores headers for up to two slots. If the input slot is higher than the lowest slot
// currently in the cache, the lowest slot is removed to make space for the new header.
// Only the highest value header for a given parent block hash will be stored.
// This function assumes caller already checks header's slot is current or next slot, it doesn't account for slot validation.
func (c *ExecutionPayloadHeaders) SaveSignedExecutionPayloadHeader(header *enginev1.SignedExecutionPayloadHeader) {
c.Lock()
defer c.Unlock()
for s := range c.headers {
if s+1 < header.Message.Slot {
delete(c.headers, s)
}
}
// Add or update the header in the map
if _, ok := c.headers[header.Message.Slot]; !ok {
c.headers[header.Message.Slot] = []*enginev1.SignedExecutionPayloadHeader{header}
} else {
found := false
for i, h := range c.headers[header.Message.Slot] {
if bytes.Equal(h.Message.ParentBlockHash, header.Message.ParentBlockHash) && bytes.Equal(h.Message.ParentBlockRoot, header.Message.ParentBlockRoot) {
if header.Message.Value > h.Message.Value {
c.headers[header.Message.Slot][i] = header
}
found = true
break
}
}
if !found {
c.headers[header.Message.Slot] = append(c.headers[header.Message.Slot], header)
}
}
}
// SignedExecutionPayloadHeader returns the signed payload header for the given slot and parent block hash and block root.
// Returns nil if the header is not found.
// This should be used when the caller wants the header to match parent block hash and parent block root such as proposer choosing a header to propose.
func (c *ExecutionPayloadHeaders) SignedExecutionPayloadHeader(slot primitives.Slot, parentBlockHash []byte, parentBlockRoot []byte) *enginev1.SignedExecutionPayloadHeader {
c.RLock()
defer c.RUnlock()
if headers, ok := c.headers[slot]; ok {
for _, header := range headers {
if bytes.Equal(header.Message.ParentBlockHash, parentBlockHash) && bytes.Equal(header.Message.ParentBlockRoot, parentBlockRoot) {
return header
}
}
}
return nil
}

View File

@@ -0,0 +1,243 @@
package cache
import (
"testing"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func Test_SaveSignedExecutionPayloadHeader(t *testing.T) {
t.Run("First header should be added to cache", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header)
require.Equal(t, 1, len(c.headers))
require.Equal(t, header, c.headers[1][0])
})
t.Run("Second header with higher slot should be added, and both slots should be in cache", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
require.Equal(t, 2, len(c.headers))
require.Equal(t, header1, c.headers[1][0])
require.Equal(t, header2, c.headers[2][0])
})
t.Run("Third header with higher slot should replace the oldest slot", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 100,
},
}
header3 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 3,
ParentBlockHash: []byte("parent3"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
c.SaveSignedExecutionPayloadHeader(header3)
require.Equal(t, 2, len(c.headers))
require.Equal(t, header2, c.headers[2][0])
require.Equal(t, header3, c.headers[3][0])
})
t.Run("Header with same slot but higher value should replace the existing one", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 200,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
require.Equal(t, 1, len(c.headers[2]))
require.Equal(t, header2, c.headers[2][0])
})
t.Run("Header with different parent block hash should be appended to the same slot", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 200,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
require.Equal(t, 2, len(c.headers[2]))
require.Equal(t, header1, c.headers[2][0])
require.Equal(t, header2, c.headers[2][1])
})
}
func TestSignedExecutionPayloadHeader(t *testing.T) {
t.Run("Return header when slot and parentBlockHash match", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
ParentBlockRoot: []byte("root1"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header)
result := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte("root1"))
require.NotNil(t, result)
require.Equal(t, header, result)
})
t.Run("Return nil when no matching slot and parentBlockHash", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
ParentBlockRoot: []byte("root1"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header)
result := c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte("root1"))
require.IsNil(t, result)
})
t.Run("Return nil when no matching slot and parentBlockRoot", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
ParentBlockRoot: []byte("root1"),
Value: 100,
},
}
c.SaveSignedExecutionPayloadHeader(header)
result := c.SignedExecutionPayloadHeader(2, []byte("parent1"), []byte("root2"))
require.IsNil(t, result)
})
t.Run("Return header when there are two slots in the cache and a match is found", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 200,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
// Check for the first header
result1 := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte{})
require.NotNil(t, result1)
require.Equal(t, header1, result1)
// Check for the second header
result2 := c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte{})
require.NotNil(t, result2)
require.Equal(t, header2, result2)
})
t.Run("Return nil when slot is evicted from cache", func(t *testing.T) {
c := NewExecutionPayloadHeaders()
header1 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 1,
ParentBlockHash: []byte("parent1"),
Value: 100,
},
}
header2 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 2,
ParentBlockHash: []byte("parent2"),
Value: 200,
},
}
header3 := &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
Slot: 3,
ParentBlockHash: []byte("parent3"),
Value: 300,
},
}
c.SaveSignedExecutionPayloadHeader(header1)
c.SaveSignedExecutionPayloadHeader(header2)
c.SaveSignedExecutionPayloadHeader(header3)
// The first slot should be evicted, so result should be nil
result := c.SignedExecutionPayloadHeader(1, []byte("parent1"), []byte{})
require.IsNil(t, result)
// The second slot should still be present
result = c.SignedExecutionPayloadHeader(2, []byte("parent2"), []byte{})
require.NotNil(t, result)
require.Equal(t, header2, result)
// The third slot should be present
result = c.SignedExecutionPayloadHeader(3, []byte("parent3"), []byte{})
require.NotNil(t, result)
require.Equal(t, header3, result)
})
}

View File

@@ -107,6 +107,7 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
"//time/slots:go_default_library",
"@com_github_google_gofuzz//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",

View File

@@ -12,6 +12,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
)
@@ -213,6 +214,47 @@ func NewGenesisBlockForState(ctx context.Context, st state.BeaconState) (interfa
},
BlsToExecutionChanges: make([]*ethpb.SignedBLSToExecutionChange, 0),
BlobKzgCommitments: make([][]byte, 0),
ExecutionRequests: &enginev1.ExecutionRequests{
Withdrawals: make([]*enginev1.WithdrawalRequest, 0),
Deposits: make([]*enginev1.DepositRequest, 0),
Consolidations: make([]*enginev1.ConsolidationRequest, 0),
},
},
},
Signature: params.BeaconConfig().EmptySignature[:],
})
case *ethpb.BeaconStateEPBS:
kzgs := make([][]byte, 0)
kzgRoot, err := ssz.KzgCommitmentsRoot(kzgs)
if err != nil {
return nil, err
}
return blocks.NewSignedBeaconBlock(&ethpb.SignedBeaconBlockEpbs{
Block: &ethpb.BeaconBlockEpbs{
ParentRoot: params.BeaconConfig().ZeroHash[:],
StateRoot: root[:],
Body: &ethpb.BeaconBlockBodyEpbs{
RandaoReveal: make([]byte, 96),
Eth1Data: &ethpb.Eth1Data{
DepositRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
},
Graffiti: make([]byte, 32),
SyncAggregate: &ethpb.SyncAggregate{
SyncCommitteeBits: make([]byte, fieldparams.SyncCommitteeLength/8),
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
},
SignedExecutionPayloadHeader: &enginev1.SignedExecutionPayloadHeader{
Message: &enginev1.ExecutionPayloadHeaderEPBS{
ParentBlockHash: make([]byte, 32),
ParentBlockRoot: make([]byte, 32),
BlockHash: make([]byte, 32),
BlobKzgCommitmentsRoot: kzgRoot[:],
},
Signature: make([]byte, 96),
},
BlsToExecutionChanges: make([]*ethpb.SignedBLSToExecutionChange, 0),
PayloadAttestations: make([]*ethpb.PayloadAttestation, 0),
},
},
Signature: params.BeaconConfig().EmptySignature[:],

View File

@@ -239,12 +239,47 @@ func verifyBlobCommitmentCount(body interfaces.ReadOnlyBeaconBlockBody) error {
// GetBlockPayloadHash returns the hash of the execution payload of the block
func GetBlockPayloadHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
var payloadHash [32]byte
if IsPreBellatrixVersion(blk.Version()) {
return payloadHash, nil
if blk.Version() >= version.EPBS {
header, err := blk.Body().SignedExecutionPayloadHeader()
if err != nil {
return payloadHash, err
}
payload, err := header.Header()
if err != nil {
return payloadHash, err
}
return payload.BlockHash(), nil
}
payload, err := blk.Body().Execution()
if err != nil {
return payloadHash, err
if blk.Version() >= version.Bellatrix {
payload, err := blk.Body().Execution()
if err != nil {
return payloadHash, err
}
return bytesutil.ToBytes32(payload.BlockHash()), nil
}
return bytesutil.ToBytes32(payload.BlockHash()), nil
return payloadHash, nil
}
// GetBlockParentHash returns the hash of the parent execution payload
func GetBlockParentHash(blk interfaces.ReadOnlyBeaconBlock) ([32]byte, error) {
var parentHash [32]byte
if blk.Version() >= version.EPBS {
header, err := blk.Body().SignedExecutionPayloadHeader()
if err != nil {
return parentHash, err
}
payload, err := header.Header()
if err != nil {
return parentHash, err
}
return payload.ParentBlockHash(), nil
}
if blk.Version() >= version.Bellatrix {
payload, err := blk.Body().Execution()
if err != nil {
return parentHash, err
}
return bytesutil.ToBytes32(payload.ParentHash()), nil
}
return parentHash, nil
}

View File

@@ -14,8 +14,8 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
"github.com/prysmaticlabs/prysm/v5/encoding/ssz"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
)
@@ -115,15 +115,97 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
return val, nil
}
func checkWithdrawalsAgainstPayload(
executionData interfaces.ExecutionData,
numExpected int,
expectedRoot [32]byte,
) error {
var wdRoot [32]byte
if executionData.IsBlinded() {
r, err := executionData.WithdrawalsRoot()
if err != nil {
return errors.Wrap(err, "could not get withdrawals root")
}
copy(wdRoot[:], r)
} else {
wds, err := executionData.Withdrawals()
if err != nil {
return errors.Wrap(err, "could not get withdrawals")
}
if len(wds) != numExpected {
return fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), numExpected)
}
wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return errors.Wrap(err, "could not get withdrawals root")
}
}
if expectedRoot != wdRoot {
return fmt.Errorf("expected withdrawals root %#x, got %#x", expectedRoot, wdRoot)
}
return nil
}
func processWithdrawalStateTransition(
st state.BeaconState,
expectedWithdrawals []*enginev1.Withdrawal,
partialWithdrawalsCount uint64,
) (err error) {
for _, withdrawal := range expectedWithdrawals {
err := helpers.DecreaseBalance(st, withdrawal.ValidatorIndex, withdrawal.Amount)
if err != nil {
return errors.Wrap(err, "could not decrease balance")
}
}
if st.Version() >= version.Electra {
if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil {
return fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
}
}
if len(expectedWithdrawals) > 0 {
if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil {
return errors.Wrap(err, "could not set next withdrawal index")
}
}
var nextValidatorIndex primitives.ValidatorIndex
if uint64(len(expectedWithdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
if err != nil {
return errors.Wrap(err, "could not get next withdrawal validator index")
}
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
} else {
nextValidatorIndex = expectedWithdrawals[len(expectedWithdrawals)-1].ValidatorIndex + 1
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
nextValidatorIndex = 0
}
}
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
return errors.Wrap(err, "could not set next withdrawal validator index")
}
return nil
}
// ProcessWithdrawals processes the validator withdrawals from the provided execution payload
// into the beacon state.
//
// Spec pseudocode definition:
//
// def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
// if state.fork.current_version >= EIP7732_FORK_VERSION :
// if not is_parent_block_full(state): # [New in EIP-7732]
// return
//
// expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251]
//
// assert len(payload.withdrawals) == len(expected_withdrawals)
// if state.fork.current_version >= EIP7732_FORK_VERSION :
// state.latest_withdrawals_root = hash_tree_root(expected_withdrawals) # [New in EIP-7732]
// else :
// assert len(payload.withdrawals) == len(expected_withdrawals)
//
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
// assert withdrawal == expected_withdrawal
@@ -148,76 +230,39 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
// next_validator_index = ValidatorIndex(next_index % len(state.validators))
// state.next_withdrawal_validator_index = next_validator_index
func ProcessWithdrawals(st state.BeaconState, executionData interfaces.ExecutionData) (state.BeaconState, error) {
if st.Version() >= version.EPBS {
IsParentBlockFull, err := st.IsParentBlockFull()
if err != nil {
return nil, errors.Wrap(err, "could not check if parent block is full")
}
if !IsParentBlockFull {
return nil, nil
}
}
expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get expected withdrawals")
}
var wdRoot [32]byte
if executionData.IsBlinded() {
r, err := executionData.WithdrawalsRoot()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
wdRoot = bytesutil.ToBytes32(r)
} else {
wds, err := executionData.Withdrawals()
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals")
}
if len(wds) != len(expectedWithdrawals) {
return nil, fmt.Errorf("execution payload header has %d withdrawals when %d were expected", len(wds), len(expectedWithdrawals))
}
wdRoot, err = ssz.WithdrawalSliceRoot(wds, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get withdrawals root")
}
}
expectedRoot, err := ssz.WithdrawalSliceRoot(expectedWithdrawals, fieldparams.MaxWithdrawalsPerPayload)
if err != nil {
return nil, errors.Wrap(err, "could not get expected withdrawals root")
}
if expectedRoot != wdRoot {
return nil, fmt.Errorf("expected withdrawals root %#x, got %#x", expectedRoot, wdRoot)
}
for _, withdrawal := range expectedWithdrawals {
err := helpers.DecreaseBalance(st, withdrawal.ValidatorIndex, withdrawal.Amount)
if st.Version() >= version.EPBS {
err = st.SetLastWithdrawalsRoot(expectedRoot[:])
if err != nil {
return nil, errors.Wrap(err, "could not decrease balance")
return nil, errors.Wrap(err, "could not set withdrawals root")
}
}
if st.Version() >= version.Electra {
if err := st.DequeuePartialWithdrawals(partialWithdrawalsCount); err != nil {
return nil, fmt.Errorf("unable to dequeue partial withdrawals from state: %w", err)
}
}
if len(expectedWithdrawals) > 0 {
if err := st.SetNextWithdrawalIndex(expectedWithdrawals[len(expectedWithdrawals)-1].Index + 1); err != nil {
return nil, errors.Wrap(err, "could not set next withdrawal index")
}
}
var nextValidatorIndex primitives.ValidatorIndex
if uint64(len(expectedWithdrawals)) < params.BeaconConfig().MaxWithdrawalsPerPayload {
nextValidatorIndex, err = st.NextWithdrawalValidatorIndex()
if err != nil {
return nil, errors.Wrap(err, "could not get next withdrawal validator index")
}
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
} else {
nextValidatorIndex = expectedWithdrawals[len(expectedWithdrawals)-1].ValidatorIndex + 1
if nextValidatorIndex == primitives.ValidatorIndex(st.NumValidators()) {
nextValidatorIndex = 0
if err := checkWithdrawalsAgainstPayload(executionData, len(expectedWithdrawals), expectedRoot); err != nil {
return nil, err
}
}
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
return nil, errors.Wrap(err, "could not set next withdrawal validator index")
if err := processWithdrawalStateTransition(st, expectedWithdrawals, partialWithdrawalsCount); err != nil {
return nil, err
}
return st, nil
}

View File

@@ -22,6 +22,7 @@ import (
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
@@ -1132,13 +1133,407 @@ func TestProcessWithdrawals(t *testing.T) {
checkPostState(t, test.Control, post)
}
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
})
}
})
}
}
func TestProcessWithdrawalsEPBS(t *testing.T) {
const (
currentEpoch = primitives.Epoch(10)
epochInFuture = primitives.Epoch(12)
epochInPast = primitives.Epoch(8)
numValidators = 128
notWithdrawableIndex = 127
notPartiallyWithdrawable = 126
maxSweep = uint64(80)
)
maxEffectiveBalance := params.BeaconConfig().MaxEffectiveBalance
type args struct {
Name string
NextWithdrawalValidatorIndex primitives.ValidatorIndex
NextWithdrawalIndex uint64
FullWithdrawalIndices []primitives.ValidatorIndex
PendingPartialWithdrawalIndices []primitives.ValidatorIndex
Withdrawals []*enginev1.Withdrawal
PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // Electra
LatestBlockHash []byte // EIP-7732
}
type control struct {
NextWithdrawalValidatorIndex primitives.ValidatorIndex
NextWithdrawalIndex uint64
Balances map[uint64]uint64
}
type Test struct {
Args args
Control control
}
executionAddress := func(i primitives.ValidatorIndex) []byte {
wc := make([]byte, 20)
wc[19] = byte(i)
return wc
}
withdrawalAmount := func(i primitives.ValidatorIndex) uint64 {
return maxEffectiveBalance + uint64(i)*100000
}
fullWithdrawal := func(i primitives.ValidatorIndex, idx uint64) *enginev1.Withdrawal {
return &enginev1.Withdrawal{
Index: idx,
ValidatorIndex: i,
Address: executionAddress(i),
Amount: withdrawalAmount(i),
}
}
PendingPartialWithdrawal := func(i primitives.ValidatorIndex, idx uint64) *enginev1.Withdrawal {
return &enginev1.Withdrawal{
Index: idx,
ValidatorIndex: i,
Address: executionAddress(i),
Amount: withdrawalAmount(i) - maxEffectiveBalance,
}
}
tests := []Test{
{
Args: args{
Name: "success no withdrawals",
NextWithdrawalValidatorIndex: 10,
NextWithdrawalIndex: 3,
},
Control: control{
NextWithdrawalValidatorIndex: 90,
NextWithdrawalIndex: 3,
},
},
{
Args: args{
Name: "success one full withdrawal",
NextWithdrawalIndex: 3,
NextWithdrawalValidatorIndex: 5,
FullWithdrawalIndices: []primitives.ValidatorIndex{70},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(70, 3),
},
},
Control: control{
NextWithdrawalValidatorIndex: 85,
NextWithdrawalIndex: 4,
Balances: map[uint64]uint64{70: 0},
},
},
{
Args: args{
Name: "success one partial withdrawal",
NextWithdrawalIndex: 21,
NextWithdrawalValidatorIndex: 120,
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{7},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(7, 21),
},
},
Control: control{
NextWithdrawalValidatorIndex: 72,
NextWithdrawalIndex: 22,
Balances: map[uint64]uint64{7: maxEffectiveBalance},
},
},
{
Args: args{
Name: "success many full withdrawals",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28, 1},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(7, 22), fullWithdrawal(19, 23), fullWithdrawal(28, 24),
},
},
Control: control{
NextWithdrawalValidatorIndex: 84,
NextWithdrawalIndex: 25,
Balances: map[uint64]uint64{7: 0, 19: 0, 28: 0},
},
},
{
Args: args{
Name: "less than max sweep at end",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{80, 81, 82, 83},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(80, 22), fullWithdrawal(81, 23), fullWithdrawal(82, 24),
fullWithdrawal(83, 25),
},
},
Control: control{
NextWithdrawalValidatorIndex: 84,
NextWithdrawalIndex: 26,
Balances: map[uint64]uint64{80: 0, 81: 0, 82: 0, 83: 0},
},
},
{
Args: args{
Name: "less than max sweep and beginning",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{4, 5, 6},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(4, 22), fullWithdrawal(5, 23), fullWithdrawal(6, 24),
},
},
Control: control{
NextWithdrawalValidatorIndex: 84,
NextWithdrawalIndex: 25,
Balances: map[uint64]uint64{4: 0, 5: 0, 6: 0},
},
},
{
Args: args{
Name: "success many partial withdrawals",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(7, 22), PendingPartialWithdrawal(19, 23), PendingPartialWithdrawal(28, 24),
},
},
Control: control{
NextWithdrawalValidatorIndex: 84,
NextWithdrawalIndex: 25,
Balances: map[uint64]uint64{
7: maxEffectiveBalance,
19: maxEffectiveBalance,
28: maxEffectiveBalance,
},
},
},
{
Args: args{
Name: "success many withdrawals",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 88,
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24),
fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27),
fullWithdrawal(28, 28),
},
},
Control: control{
NextWithdrawalValidatorIndex: 40,
NextWithdrawalIndex: 29,
Balances: map[uint64]uint64{
7: 0, 19: 0, 28: 0,
2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance,
15: maxEffectiveBalance,
},
},
},
{
Args: args{
Name: "success many withdrawals with pending partial withdrawals in state",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 88,
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28},
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{2, 1, 89, 15},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(89, 22), PendingPartialWithdrawal(1, 23), PendingPartialWithdrawal(2, 24),
fullWithdrawal(7, 25), PendingPartialWithdrawal(15, 26), fullWithdrawal(19, 27),
fullWithdrawal(28, 28),
},
PendingPartialWithdrawals: []*ethpb.PendingPartialWithdrawal{
{
Index: 11,
Amount: withdrawalAmount(11) - maxEffectiveBalance,
},
},
},
Control: control{
NextWithdrawalValidatorIndex: 40,
NextWithdrawalIndex: 29,
Balances: map[uint64]uint64{
7: 0, 19: 0, 28: 0,
2: maxEffectiveBalance, 1: maxEffectiveBalance, 89: maxEffectiveBalance,
15: maxEffectiveBalance,
},
},
},
{
Args: args{
Name: "success more than max fully withdrawals",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 0,
FullWithdrawalIndices: []primitives.ValidatorIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 29, 35, 89},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(1, 22), fullWithdrawal(2, 23), fullWithdrawal(3, 24),
fullWithdrawal(4, 25), fullWithdrawal(5, 26), fullWithdrawal(6, 27),
fullWithdrawal(7, 28), fullWithdrawal(8, 29), fullWithdrawal(9, 30),
fullWithdrawal(21, 31), fullWithdrawal(22, 32), fullWithdrawal(23, 33),
fullWithdrawal(24, 34), fullWithdrawal(25, 35), fullWithdrawal(26, 36),
fullWithdrawal(27, 37),
},
},
Control: control{
NextWithdrawalValidatorIndex: 28,
NextWithdrawalIndex: 38,
Balances: map[uint64]uint64{
1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0,
21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 26: 0, 27: 0,
},
},
},
{
Args: args{
Name: "success more than max partially withdrawals",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 0,
PendingPartialWithdrawalIndices: []primitives.ValidatorIndex{1, 2, 3, 4, 5, 6, 7, 8, 9, 21, 22, 23, 24, 25, 26, 27, 29, 35, 89},
Withdrawals: []*enginev1.Withdrawal{
PendingPartialWithdrawal(1, 22), PendingPartialWithdrawal(2, 23), PendingPartialWithdrawal(3, 24),
PendingPartialWithdrawal(4, 25), PendingPartialWithdrawal(5, 26), PendingPartialWithdrawal(6, 27),
PendingPartialWithdrawal(7, 28), PendingPartialWithdrawal(8, 29), PendingPartialWithdrawal(9, 30),
PendingPartialWithdrawal(21, 31), PendingPartialWithdrawal(22, 32), PendingPartialWithdrawal(23, 33),
PendingPartialWithdrawal(24, 34), PendingPartialWithdrawal(25, 35), PendingPartialWithdrawal(26, 36),
PendingPartialWithdrawal(27, 37),
},
},
Control: control{
NextWithdrawalValidatorIndex: 28,
NextWithdrawalIndex: 38,
Balances: map[uint64]uint64{
1: maxEffectiveBalance,
2: maxEffectiveBalance,
3: maxEffectiveBalance,
4: maxEffectiveBalance,
5: maxEffectiveBalance,
6: maxEffectiveBalance,
7: maxEffectiveBalance,
8: maxEffectiveBalance,
9: maxEffectiveBalance,
21: maxEffectiveBalance,
22: maxEffectiveBalance,
23: maxEffectiveBalance,
24: maxEffectiveBalance,
25: maxEffectiveBalance,
26: maxEffectiveBalance,
27: maxEffectiveBalance,
},
},
},
{
Args: args{
Name: "Parent Node is not full",
NextWithdrawalIndex: 22,
NextWithdrawalValidatorIndex: 4,
FullWithdrawalIndices: []primitives.ValidatorIndex{7, 19, 28, 1},
Withdrawals: []*enginev1.Withdrawal{
fullWithdrawal(7, 22), fullWithdrawal(19, 23), fullWithdrawal(28, 24),
},
LatestBlockHash: []byte{1, 2, 3},
},
},
}
checkPostState := func(t *testing.T, expected control, st state.BeaconState) {
l, err := st.NextWithdrawalValidatorIndex()
require.NoError(t, err)
require.Equal(t, expected.NextWithdrawalValidatorIndex, l)
n, err := st.NextWithdrawalIndex()
require.NoError(t, err)
require.Equal(t, expected.NextWithdrawalIndex, n)
balances := st.Balances()
for idx, bal := range expected.Balances {
require.Equal(t, bal, balances[idx])
}
}
prepareValidators := func(st state.BeaconState, arguments args) error {
validators := make([]*ethpb.Validator, numValidators)
if err := st.SetBalances(make([]uint64, numValidators)); err != nil {
return err
}
for i := range validators {
v := &ethpb.Validator{}
v.EffectiveBalance = maxEffectiveBalance
v.WithdrawableEpoch = epochInFuture
v.WithdrawalCredentials = make([]byte, 32)
v.WithdrawalCredentials[31] = byte(i)
if err := st.UpdateBalancesAtIndex(primitives.ValidatorIndex(i), v.EffectiveBalance-uint64(rand.Intn(1000))); err != nil {
return err
}
validators[i] = v
}
for _, idx := range arguments.FullWithdrawalIndices {
if idx != notWithdrawableIndex {
validators[idx].WithdrawableEpoch = epochInPast
}
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
return err
}
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
}
for _, idx := range arguments.PendingPartialWithdrawalIndices {
validators[idx].WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte
if err := st.UpdateBalancesAtIndex(idx, withdrawalAmount(idx)); err != nil {
return err
}
}
return st.SetValidators(validators)
}
for _, test := range tests {
t.Run(test.Args.Name, func(t *testing.T) {
saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = maxSweep
if test.Args.Withdrawals == nil {
test.Args.Withdrawals = make([]*enginev1.Withdrawal, 0)
}
if test.Args.FullWithdrawalIndices == nil {
test.Args.FullWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
if test.Args.PendingPartialWithdrawalIndices == nil {
test.Args.PendingPartialWithdrawalIndices = make([]primitives.ValidatorIndex, 0)
}
slot, err := slots.EpochStart(currentEpoch)
require.NoError(t, err)
var st state.BeaconState
var p interfaces.ExecutionData
spb := &ethpb.BeaconStateEPBS{
Slot: slot,
NextWithdrawalValidatorIndex: test.Args.NextWithdrawalValidatorIndex,
NextWithdrawalIndex: test.Args.NextWithdrawalIndex,
PendingPartialWithdrawals: test.Args.PendingPartialWithdrawals,
LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderEPBS{
BlockHash: []byte{},
},
LatestBlockHash: test.Args.LatestBlockHash,
}
st, err = state_native.InitializeFromProtoUnsafeEpbs(spb)
require.NoError(t, err)
env := random.ExecutionPayloadEnvelope(t)
env.Payload.Withdrawals = test.Args.Withdrawals
wp, err := consensusblocks.WrappedROExecutionPayloadEnvelope(env)
require.NoError(t, err)
p, err = wp.Execution()
require.NoError(t, err)
err = prepareValidators(st, test.Args)
require.NoError(t, err)
post, err := blocks.ProcessWithdrawals(st, p)
if test.Args.Name == "Parent Node is not full" {
require.IsNil(t, post)
require.IsNil(t, err)
} else {
require.NoError(t, err)
checkPostState(t, test.Control, post)
}
params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved
})
}
}
func TestProcessBLSToExecutionChanges(t *testing.T) {
spb := &ethpb.BeaconStateCapella{
Fork: &ethpb.Fork{

View File

@@ -78,23 +78,19 @@ func ProcessOperations(
return nil, errors.Wrap(err, "could not process bls-to-execution changes")
}
// new in electra
e, err := bb.Execution()
requests, err := bb.ExecutionRequests()
if err != nil {
return nil, errors.Wrap(err, "could not get execution data from block")
return nil, errors.Wrap(err, "could not get execution requests")
}
exe, ok := e.(interfaces.ExecutionDataElectra)
if !ok {
return nil, errors.New("could not cast execution data to electra execution data")
}
st, err = ProcessDepositRequests(ctx, st, exe.DepositRequests())
st, err = ProcessDepositRequests(ctx, st, requests.Deposits)
if err != nil {
return nil, errors.Wrap(err, "could not process deposit receipts")
}
st, err = ProcessWithdrawalRequests(ctx, st, exe.WithdrawalRequests())
st, err = ProcessWithdrawalRequests(ctx, st, requests.Withdrawals)
if err != nil {
return nil, errors.Wrap(err, "could not process execution layer withdrawal requests")
}
if err := ProcessConsolidationRequests(ctx, st, exe.ConsolidationRequests()); err != nil {
if err := ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil {
return nil, fmt.Errorf("could not process consolidation requests: %w", err)
}
return st, nil

View File

@@ -10,7 +10,6 @@ import (
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/time/slots"
@@ -245,26 +244,23 @@ func UpgradeToElectra(beaconState state.BeaconState) (state.BeaconState, error)
CurrentSyncCommittee: currentSyncCommittee,
NextSyncCommittee: nextSyncCommittee,
LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{
ParentHash: payloadHeader.ParentHash(),
FeeRecipient: payloadHeader.FeeRecipient(),
StateRoot: payloadHeader.StateRoot(),
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
LogsBloom: payloadHeader.LogsBloom(),
PrevRandao: payloadHeader.PrevRandao(),
BlockNumber: payloadHeader.BlockNumber(),
GasLimit: payloadHeader.GasLimit(),
GasUsed: payloadHeader.GasUsed(),
Timestamp: payloadHeader.Timestamp(),
ExtraData: payloadHeader.ExtraData(),
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
BlockHash: payloadHeader.BlockHash(),
TransactionsRoot: txRoot,
WithdrawalsRoot: wdRoot,
ExcessBlobGas: excessBlobGas,
BlobGasUsed: blobGasUsed,
DepositRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP6110]
WithdrawalRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7002]
ConsolidationRequestsRoot: bytesutil.Bytes32(0), // [New in Electra:EIP7251]
ParentHash: payloadHeader.ParentHash(),
FeeRecipient: payloadHeader.FeeRecipient(),
StateRoot: payloadHeader.StateRoot(),
ReceiptsRoot: payloadHeader.ReceiptsRoot(),
LogsBloom: payloadHeader.LogsBloom(),
PrevRandao: payloadHeader.PrevRandao(),
BlockNumber: payloadHeader.BlockNumber(),
GasLimit: payloadHeader.GasLimit(),
GasUsed: payloadHeader.GasUsed(),
Timestamp: payloadHeader.Timestamp(),
ExtraData: payloadHeader.ExtraData(),
BaseFeePerGas: payloadHeader.BaseFeePerGas(),
BlockHash: payloadHeader.BlockHash(),
TransactionsRoot: txRoot,
WithdrawalsRoot: wdRoot,
ExcessBlobGas: excessBlobGas,
BlobGasUsed: blobGasUsed,
},
NextWithdrawalIndex: wi,
NextWithdrawalValidatorIndex: vi,

View File

@@ -8,7 +8,6 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
@@ -113,24 +112,21 @@ func TestUpgradeToElectra(t *testing.T) {
wdRoot, err := prevHeader.WithdrawalsRoot()
require.NoError(t, err)
wanted := &enginev1.ExecutionPayloadHeaderElectra{
ParentHash: prevHeader.ParentHash(),
FeeRecipient: prevHeader.FeeRecipient(),
StateRoot: prevHeader.StateRoot(),
ReceiptsRoot: prevHeader.ReceiptsRoot(),
LogsBloom: prevHeader.LogsBloom(),
PrevRandao: prevHeader.PrevRandao(),
BlockNumber: prevHeader.BlockNumber(),
GasLimit: prevHeader.GasLimit(),
GasUsed: prevHeader.GasUsed(),
Timestamp: prevHeader.Timestamp(),
ExtraData: prevHeader.ExtraData(),
BaseFeePerGas: prevHeader.BaseFeePerGas(),
BlockHash: prevHeader.BlockHash(),
TransactionsRoot: txRoot,
WithdrawalsRoot: wdRoot,
DepositRequestsRoot: bytesutil.Bytes32(0),
WithdrawalRequestsRoot: bytesutil.Bytes32(0),
ConsolidationRequestsRoot: bytesutil.Bytes32(0),
ParentHash: prevHeader.ParentHash(),
FeeRecipient: prevHeader.FeeRecipient(),
StateRoot: prevHeader.StateRoot(),
ReceiptsRoot: prevHeader.ReceiptsRoot(),
LogsBloom: prevHeader.LogsBloom(),
PrevRandao: prevHeader.PrevRandao(),
BlockNumber: prevHeader.BlockNumber(),
GasLimit: prevHeader.GasLimit(),
GasUsed: prevHeader.GasUsed(),
Timestamp: prevHeader.Timestamp(),
ExtraData: prevHeader.ExtraData(),
BaseFeePerGas: prevHeader.BaseFeePerGas(),
BlockHash: prevHeader.BlockHash(),
TransactionsRoot: txRoot,
WithdrawalsRoot: wdRoot,
}
require.DeepEqual(t, wanted, protoHeader)

View File

@@ -0,0 +1,38 @@
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"attestation.go",
"execution_payload_envelope.go",
],
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs",
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/core/electra:go_default_library",
"//beacon-chain/state:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//proto/engine/v1:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"attestation_test.go",
"execution_payload_envelope_test.go",
],
embed = [":go_default_library"],
deps = [
"//beacon-chain/core/altair:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
],
)

View File

@@ -0,0 +1,13 @@
package epbs
import (
"fmt"
)
// RemoveValidatorFlag removes validator flag from existing one.
func RemoveValidatorFlag(flag, flagPosition uint8) (uint8, error) {
if flagPosition > 7 {
return flag, fmt.Errorf("flag position %d exceeds length", flagPosition)
}
return flag & ^(1 << flagPosition), nil
}

View File

@@ -0,0 +1,93 @@
package epbs_test
import (
"testing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestValidatorFlag_Remove(t *testing.T) {
tests := []struct {
name string
add []uint8
remove []uint8
expectedTrue []uint8
expectedFalse []uint8
}{
{
name: "none",
add: []uint8{},
remove: []uint8{},
expectedTrue: []uint8{},
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
},
{
name: "source",
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
remove: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
expectedTrue: []uint8{},
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
},
{
name: "source, target",
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex},
remove: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
expectedTrue: []uint8{params.BeaconConfig().TimelyTargetFlagIndex},
expectedFalse: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
},
{
name: "source, target, head",
add: []uint8{params.BeaconConfig().TimelySourceFlagIndex, params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
remove: []uint8{params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
expectedTrue: []uint8{params.BeaconConfig().TimelySourceFlagIndex},
expectedFalse: []uint8{params.BeaconConfig().TimelyTargetFlagIndex, params.BeaconConfig().TimelyHeadFlagIndex},
},
}
var err error
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
flag := uint8(0)
// Add flags.
for _, flagPosition := range test.add {
flag, err = altair.AddValidatorFlag(flag, flagPosition)
require.NoError(t, err)
has, err := altair.HasValidatorFlag(flag, flagPosition)
require.NoError(t, err)
require.Equal(t, true, has)
}
// Remove flags.
for _, flagPosition := range test.remove {
flag, err = epbs.RemoveValidatorFlag(flag, flagPosition)
require.NoError(t, err)
}
// Check if flags are set correctly.
for _, flagPosition := range test.expectedTrue {
has, err := altair.HasValidatorFlag(flag, flagPosition)
require.NoError(t, err)
require.Equal(t, true, has)
}
for _, flagPosition := range test.expectedFalse {
has, err := altair.HasValidatorFlag(flag, flagPosition)
require.NoError(t, err)
require.Equal(t, false, has)
}
})
}
}
func TestValidatorFlag_Remove_ExceedsLength(t *testing.T) {
_, err := epbs.RemoveValidatorFlag(0, 8)
require.ErrorContains(t, "flag position 8 exceeds length", err)
}
func TestValidatorFlag_Remove_NotSet(t *testing.T) {
_, err := epbs.RemoveValidatorFlag(0, 1)
require.NoError(t, err)
}

View File

@@ -0,0 +1,126 @@
package epbs
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
)
// ValidatePayloadStateTransition performs the process_execution_payload
// function.
func ValidatePayloadStateTransition(
ctx context.Context,
preState state.BeaconState,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
if err := validateAgainstHeader(ctx, preState, envelope); err != nil {
return err
}
committedHeader, err := preState.LatestExecutionPayloadHeaderEPBS()
if err != nil {
return err
}
if err := validateAgainstCommittedBid(committedHeader, envelope); err != nil {
return err
}
if err := ProcessPayloadStateTransition(ctx, preState, envelope); err != nil {
return err
}
return checkPostStateRoot(ctx, preState, envelope)
}
func ProcessPayloadStateTransition(
ctx context.Context,
preState state.BeaconState,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
er := envelope.ExecutionRequests()
preState, err := electra.ProcessDepositRequests(ctx, preState, er.Deposits)
if err != nil {
return errors.Wrap(err, "could not process deposit receipts")
}
preState, err = electra.ProcessWithdrawalRequests(ctx, preState, er.Withdrawals)
if err != nil {
return errors.Wrap(err, "could not process ercution layer withdrawal requests")
}
if err := electra.ProcessConsolidationRequests(ctx, preState, er.Consolidations); err != nil {
return errors.Wrap(err, "could not process consolidation requests")
}
payload, err := envelope.Execution()
if err != nil {
return errors.Wrap(err, "could not get execution payload")
}
if err := preState.SetLatestBlockHash(payload.BlockHash()); err != nil {
return err
}
return preState.SetLatestFullSlot(preState.Slot())
}
func validateAgainstHeader(
ctx context.Context,
preState state.BeaconState,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
blockHeader := preState.LatestBlockHeader()
if blockHeader == nil {
return errors.New("invalid nil latest block header")
}
if len(blockHeader.StateRoot) == 0 || [32]byte(blockHeader.StateRoot) == [32]byte{} {
prevStateRoot, err := preState.HashTreeRoot(ctx)
if err != nil {
return errors.Wrap(err, "could not compute previous state root")
}
blockHeader.StateRoot = prevStateRoot[:]
if err := preState.SetLatestBlockHeader(blockHeader); err != nil {
return errors.Wrap(err, "could not set latest block header")
}
}
blockHeaderRoot, err := blockHeader.HashTreeRoot()
if err != nil {
return err
}
beaconBlockRoot := envelope.BeaconBlockRoot()
if blockHeaderRoot != beaconBlockRoot {
return fmt.Errorf("beacon block root does not match previous header, got: %#x wanted: %#x", beaconBlockRoot, blockHeaderRoot)
}
return nil
}
func validateAgainstCommittedBid(
committedHeader *enginev1.ExecutionPayloadHeaderEPBS,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
builderIndex := envelope.BuilderIndex()
if committedHeader.BuilderIndex != builderIndex {
return errors.New("builder index does not match committed header")
}
kzgRoot, err := envelope.BlobKzgCommitmentsRoot()
if err != nil {
return err
}
if [32]byte(committedHeader.BlobKzgCommitmentsRoot) != kzgRoot {
return errors.New("blob KZG commitments root does not match committed header")
}
return nil
}
func checkPostStateRoot(
ctx context.Context,
preState state.BeaconState,
envelope interfaces.ROExecutionPayloadEnvelope,
) error {
stateRoot, err := preState.HashTreeRoot(ctx)
if err != nil {
return err
}
envelopeStateRoot := envelope.StateRoot()
if stateRoot != envelopeStateRoot {
return errors.New("state root mismatch")
}
return nil
}

View File

@@ -0,0 +1,108 @@
package epbs
import (
"context"
"testing"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
)
func TestProcessPayloadStateTransition(t *testing.T) {
bh := [32]byte{'h'}
p := random.ExecutionPayloadEnvelope(t)
p.Payload.BlockHash = bh[:]
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
validators := make([]*ethpb.Validator, 0)
stpb := &ethpb.BeaconStateEPBS{Slot: 3, Validators: validators}
st, err := state_native.InitializeFromProtoUnsafeEpbs(stpb)
require.NoError(t, err)
ctx := context.Background()
lbh, err := st.LatestBlockHash()
require.NoError(t, err)
require.Equal(t, [32]byte{}, [32]byte(lbh))
require.NoError(t, ProcessPayloadStateTransition(ctx, st, e))
lbh, err = st.LatestBlockHash()
require.NoError(t, err)
require.Equal(t, bh, [32]byte(lbh))
lfs, err := st.LatestFullSlot()
require.NoError(t, err)
require.Equal(t, lfs, st.Slot())
}
func Test_validateAgainstHeader(t *testing.T) {
bh := [32]byte{'h'}
payload := &enginev1.ExecutionPayloadElectra{BlockHash: bh[:]}
p := random.ExecutionPayloadEnvelope(t)
p.Payload = payload
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
stpb := &ethpb.BeaconStateEPBS{Slot: 3}
st, err := state_native.InitializeFromProtoUnsafeEpbs(stpb)
require.NoError(t, err)
ctx := context.Background()
require.ErrorContains(t, "invalid nil latest block header", validateAgainstHeader(ctx, st, e))
prest, _ := util.DeterministicGenesisStateEpbs(t, 64)
br := [32]byte{'r'}
p.BeaconBlockRoot = br[:]
require.ErrorContains(t, "beacon block root does not match previous header", validateAgainstHeader(ctx, prest, e))
header := prest.LatestBlockHeader()
require.NoError(t, err)
headerRoot, err := header.HashTreeRoot()
require.NoError(t, err)
p.BeaconBlockRoot = headerRoot[:]
require.NoError(t, validateAgainstHeader(ctx, prest, e))
}
func Test_validateAgainstCommittedBid(t *testing.T) {
payload := &enginev1.ExecutionPayloadElectra{}
p := random.ExecutionPayloadEnvelope(t)
p.Payload = payload
p.BuilderIndex = 1
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
h := &enginev1.ExecutionPayloadHeaderEPBS{}
require.ErrorContains(t, "builder index does not match committed header", validateAgainstCommittedBid(h, e))
h.BuilderIndex = 1
p.BlobKzgCommitments = make([][]byte, 6)
for i := range p.BlobKzgCommitments {
p.BlobKzgCommitments[i] = make([]byte, 48)
}
h.BlobKzgCommitmentsRoot = make([]byte, 32)
require.ErrorContains(t, "blob KZG commitments root does not match committed header", validateAgainstCommittedBid(h, e))
root, err := e.BlobKzgCommitmentsRoot()
require.NoError(t, err)
h.BlobKzgCommitmentsRoot = root[:]
require.NoError(t, validateAgainstCommittedBid(h, e))
}
func TestCheckPostStateRoot(t *testing.T) {
payload := &enginev1.ExecutionPayloadElectra{}
p := random.ExecutionPayloadEnvelope(t)
p.Payload = payload
p.BuilderIndex = 1
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
ctx := context.Background()
st, _ := util.DeterministicGenesisStateEpbs(t, 64)
p.StateRoot = make([]byte, 32)
require.ErrorContains(t, "state root mismatch", checkPostStateRoot(ctx, st, e))
root, err := st.HashTreeRoot(ctx)
require.NoError(t, err)
p.StateRoot = root[:]
require.NoError(t, checkPostStateRoot(ctx, st, e))
}

View File

@@ -8,6 +8,7 @@ go_library(
"block.go",
"genesis.go",
"metrics.go",
"payload_attestation.go",
"randao.go",
"rewards_penalties.go",
"shuffle.go",
@@ -20,11 +21,13 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/forkchoice/types:go_default_library",
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/epbs:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/slice:go_default_library",
@@ -53,6 +56,8 @@ go_test(
"attestation_test.go",
"beacon_committee_test.go",
"block_test.go",
"exports_test.go",
"payload_attestation_test.go",
"private_access_fuzz_noop_test.go", # keep
"private_access_test.go",
"randao_test.go",
@@ -63,26 +68,33 @@ go_test(
"validators_test.go",
"weak_subjectivity_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
shard_count = 2,
tags = ["CI_race_detection"],
deps = [
"//beacon-chain/cache:go_default_library",
"//beacon-chain/core/signing:go_default_library",
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/forkchoice/types:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/epbs:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/slice:go_default_library",
"//crypto/bls:go_default_library",
"//crypto/hash:go_default_library",
"//crypto/rand:go_default_library",
"//encoding/bytesutil:go_default_library",
"//math:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//runtime/version:go_default_library",
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",

View File

@@ -213,6 +213,7 @@ type CommitteeAssignment struct {
Committee []primitives.ValidatorIndex
AttesterSlot primitives.Slot
CommitteeIndex primitives.CommitteeIndex
PtcSlot primitives.Slot
}
// verifyAssignmentEpoch verifies if the given epoch is valid for assignment based on the provided state.
@@ -294,7 +295,7 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
if err := verifyAssignmentEpoch(epoch, state); err != nil {
return nil, err
}
startSlot, err := slots.EpochStart(epoch)
slot, err := slots.EpochStart(epoch)
if err != nil {
return nil, err
}
@@ -303,14 +304,17 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
vals[v] = struct{}{}
}
assignments := make(map[primitives.ValidatorIndex]*CommitteeAssignment)
committees, err := BeaconCommittees(ctx, state, slot)
if err != nil {
return nil, errors.Wrap(err, "could not compute beacon committees")
}
ptcPerSlot, ptcMembersPerCommittee := PtcAllocation(len(committees))
// Compute committee assignments for each slot in the epoch.
for slot := startSlot; slot < startSlot+params.BeaconConfig().SlotsPerEpoch; slot++ {
committees, err := BeaconCommittees(ctx, state, slot)
if err != nil {
return nil, errors.Wrap(err, "could not compute beacon committees")
}
endSlot := slot + params.BeaconConfig().SlotsPerEpoch
for {
for j, committee := range committees {
for _, vIndex := range committee {
for i, vIndex := range committee {
if _, ok := vals[vIndex]; !ok { // Skip if the validator is not in the provided validators slice.
continue
}
@@ -320,8 +324,19 @@ func CommitteeAssignments(ctx context.Context, state state.BeaconState, epoch pr
assignments[vIndex].Committee = committee
assignments[vIndex].AttesterSlot = slot
assignments[vIndex].CommitteeIndex = primitives.CommitteeIndex(j)
if uint64(j) < ptcPerSlot && uint64(i) < ptcMembersPerCommittee {
assignments[vIndex].PtcSlot = slot
}
}
}
slot++
if slot == endSlot {
break
}
committees, err = BeaconCommittees(ctx, state, slot)
if err != nil {
return nil, errors.Wrap(err, "could not compute beacon committees")
}
}
return assignments, nil
}

View File

@@ -3,6 +3,7 @@ package helpers_test
import (
"context"
"fmt"
"slices"
"strconv"
"testing"
@@ -10,6 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/container/slice"
@@ -722,15 +724,26 @@ func TestCommitteeIndices(t *testing.T) {
assert.DeepEqual(t, []primitives.CommitteeIndex{0, 1, 3}, indices)
}
func TestAttestationCommittees(t *testing.T) {
validators := make([]*ethpb.Validator, params.BeaconConfig().SlotsPerEpoch.Mul(params.BeaconConfig().TargetCommitteeSize))
func TestCommitteeAssignments_PTC(t *testing.T) {
helpers.ClearCache()
// Create 10 committees. Total 40960 validators.
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)
validatorIndices := make([]primitives.ValidatorIndex, validatorCount)
for i := 0; i < len(validators); i++ {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
PublicKey: k,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
validatorIndices[i] = primitives.ValidatorIndex(i)
}
state, err := state_native.InitializeFromProtoPhase0(&ethpb.BeaconState{
state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
@@ -754,6 +767,31 @@ func TestAttestationCommittees(t *testing.T) {
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[0])))
assert.Equal(t, params.BeaconConfig().TargetCommitteeSize, uint64(len(committees[1])))
})
as, err := helpers.CommitteeAssignments(context.Background(), state, 1, validatorIndices)
require.NoError(t, err)
// Capture all the slots and all the validator index that belonged in a PTC using a map for verification later.
slotValidatorMap := make(map[primitives.Slot][]primitives.ValidatorIndex)
for i, a := range as {
slotValidatorMap[a.PtcSlot] = append(slotValidatorMap[a.PtcSlot], i)
}
// Verify that all the slots have the correct number of PTC.
for s, v := range slotValidatorMap {
if s == 0 {
continue
}
// Make sure all the PTC are the correct size from the map.
require.Equal(t, len(v), field_params.PTCSize)
// Get the actual PTC from the beacon state using the helper function
ptc, err := helpers.GetPayloadTimelinessCommittee(context.Background(), state, s)
require.NoError(t, err)
for _, index := range ptc {
i := slices.Index(v, index)
require.NotEqual(t, -1, i) // PTC not found from the assignment map
}
}
}
func TestBeaconCommittees(t *testing.T) {

View File

@@ -0,0 +1,12 @@
package helpers
var (
ErrNilMessage = errNilMessage
ErrNilData = errNilData
ErrNilBeaconBlockRoot = errNilBeaconBlockRoot
ErrNilPayloadAttestation = errNilPayloadAttestation
ErrNilSignature = errNilSignature
ErrNilAggregationBits = errNilAggregationBits
ErrPreEPBSState = errPreEPBSState
ErrCommitteeOverflow = errCommitteeOverflow
)

View File

@@ -0,0 +1,296 @@
package helpers
import (
"context"
"slices"
"github.com/pkg/errors"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/epbs"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/math"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/runtime/version"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
var (
errNilMessage = errors.New("nil PayloadAttestationMessage")
errNilData = errors.New("nil PayloadAttestationData")
errNilBeaconBlockRoot = errors.New("nil BeaconBlockRoot")
errNilPayloadAttestation = errors.New("nil PayloadAttestation")
errNilSignature = errors.New("nil Signature")
errNilAggregationBits = errors.New("nil AggregationBits")
errPreEPBSState = errors.New("beacon state pre ePBS fork")
errCommitteeOverflow = errors.New("beacon committee of insufficient size")
)
// ValidateNilPayloadAttestationData checks if any composite field of the
// payload attestation data is nil
func ValidateNilPayloadAttestationData(data *eth.PayloadAttestationData) error {
if data == nil {
return errNilData
}
if data.BeaconBlockRoot == nil {
return errNilBeaconBlockRoot
}
return nil
}
// ValidateNilPayloadAttestationMessage checks if any composite field of the
// payload attestation message is nil
func ValidateNilPayloadAttestationMessage(att *eth.PayloadAttestationMessage) error {
if att == nil {
return errNilMessage
}
if att.Signature == nil {
return errNilSignature
}
return ValidateNilPayloadAttestationData(att.Data)
}
// ValidateNilPayloadAttestation checks if any composite field of the
// payload attestation is nil
func ValidateNilPayloadAttestation(att *eth.PayloadAttestation) error {
if att == nil {
return errNilPayloadAttestation
}
if att.AggregationBits == nil {
return errNilAggregationBits
}
if att.Signature == nil {
return errNilSignature
}
return ValidateNilPayloadAttestationData(att.Data)
}
// InPayloadTimelinessCommittee returns whether the given index belongs to the
// PTC computed from the passed state.
func InPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, idx primitives.ValidatorIndex) (bool, error) {
ptc, err := GetPayloadTimelinessCommittee(ctx, state, slot)
if err != nil {
return false, err
}
for _, i := range ptc {
if i == idx {
return true, nil
}
}
return false, nil
}
// GetPayloadTimelinessCommittee returns the PTC for the given slot, computed from the passed state as in the
// spec function `get_ptc`.
func GetPayloadTimelinessCommittee(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot) (indices []primitives.ValidatorIndex, err error) {
if state.Version() < version.EPBS {
return nil, errPreEPBSState
}
committees, err := BeaconCommittees(ctx, state, slot)
if err != nil {
return nil, errors.Wrap(err, "could not get beacon committees")
}
committeesPerSlot, membersPerCommittee := PtcAllocation(len(committees))
for i, committee := range committees {
if uint64(i) >= committeesPerSlot {
return
}
if uint64(len(committee)) < membersPerCommittee {
return nil, errCommitteeOverflow
}
indices = append(indices, committee[:membersPerCommittee]...)
}
return
}
// PtcAllocation returns:
// 1. The number of beacon committees that PTC will borrow from in a slot.
// 2. The number of validators that PTC will borrow from in a beacon committee.
func PtcAllocation(slotCommittees int) (committeesPerSlot, membersPerCommittee uint64) {
committeesPerSlot = math.LargestPowerOfTwo(math.Min(uint64(slotCommittees), fieldparams.PTCSize))
membersPerCommittee = fieldparams.PTCSize / committeesPerSlot
return
}
// GetPayloadAttestingIndices returns the set of attester indices corresponding to the given PayloadAttestation.
//
// Spec pseudocode definition:
//
// def get_payload_attesting_indices(state: BeaconState, slot: Slot,
// payload_attestation: PayloadAttestation) -> Set[ValidatorIndex]:
// """
// Return the set of attesting indices corresponding to ``payload_attestation``.
// """
// ptc = get_ptc(state, slot)
// return set(index for i, index in enumerate(ptc) if payload_attestation.aggregation_bits[i])
func GetPayloadAttestingIndices(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, att *eth.PayloadAttestation) (indices []primitives.ValidatorIndex, err error) {
if state.Version() < version.EPBS {
return nil, errPreEPBSState
}
ptc, err := GetPayloadTimelinessCommittee(ctx, state, slot)
if err != nil {
return nil, err
}
for i, validatorIndex := range ptc {
if att.AggregationBits.BitAt(uint64(i)) {
indices = append(indices, validatorIndex)
}
}
return
}
// GetIndexedPayloadAttestation replaces a PayloadAttestation's AggregationBits with sorted AttestingIndices and returns an IndexedPayloadAttestation.
//
// Spec pseudocode definition:
//
// def get_indexed_payload_attestation(state: BeaconState, slot: Slot,
// payload_attestation: PayloadAttestation) -> IndexedPayloadAttestation:
// """
// Return the indexed payload attestation corresponding to ``payload_attestation``.
// """
// attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation)
//
// return IndexedPayloadAttestation(
// attesting_indices=sorted(attesting_indices),
// data=payload_attestation.data,
// signature=payload_attestation.signature,
// )
func GetIndexedPayloadAttestation(ctx context.Context, state state.ReadOnlyBeaconState, slot primitives.Slot, att *eth.PayloadAttestation) (*epbs.IndexedPayloadAttestation, error) {
if state.Version() < version.EPBS {
return nil, errPreEPBSState
}
attestingIndices, err := GetPayloadAttestingIndices(ctx, state, slot, att)
if err != nil {
return nil, err
}
slices.Sort(attestingIndices)
return &epbs.IndexedPayloadAttestation{
AttestingIndices: attestingIndices,
Data: att.Data,
Signature: att.Signature,
}, nil
}
// IsValidIndexedPayloadAttestation validates the given IndexedPayloadAttestation.
//
// Spec pseudocode definition:
//
// def is_valid_indexed_payload_attestation(
// state: BeaconState,
// indexed_payload_attestation: IndexedPayloadAttestation) -> bool:
// """
// Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has
// a valid aggregate signature.
// """
// # Verify the data is valid
// if indexed_payload_attestation.data.payload_status >= PAYLOAD_INVALID_STATUS:
// return False
//
// # Verify indices are sorted and unique
// indices = indexed_payload_attestation.attesting_indices
// if len(indices) == 0 or not indices == sorted(set(indices)):
// return False
//
// # Verify aggregate signature
// pubkeys = [state.validators[i].pubkey for i in indices]
// domain = get_domain(state, DOMAIN_PTC_ATTESTER, None)
// signing_root = compute_signing_root(indexed_payload_attestation.data, domain)
// return bls.FastAggregateVerify(pubkeys, signing_root, indexed_payload_attestation.signature)
func IsValidIndexedPayloadAttestation(state state.ReadOnlyBeaconState, att *epbs.IndexedPayloadAttestation) (bool, error) {
if state.Version() < version.EPBS {
return false, errPreEPBSState
}
// Verify the data is valid.
if att.Data.PayloadStatus >= primitives.PAYLOAD_INVALID_STATUS {
return false, nil
}
// Verify indices are sorted and unique.
indices := att.AttestingIndices
slices.Sort(indices)
if len(indices) == 0 || !slices.Equal(att.AttestingIndices, indices) {
return false, nil
}
// Verify aggregate signature.
publicKeys := make([]bls.PublicKey, len(indices))
for i, index := range indices {
validator, err := state.ValidatorAtIndexReadOnly(index)
if err != nil {
return false, err
}
publicKeyBytes := validator.PublicKey()
publicKey, err := bls.PublicKeyFromBytes(publicKeyBytes[:])
if err != nil {
return false, err
}
publicKeys[i] = publicKey
}
domain, err := signing.Domain(
state.Fork(),
slots.ToEpoch(state.Slot()),
params.BeaconConfig().DomainPTCAttester,
state.GenesisValidatorsRoot(),
)
if err != nil {
return false, err
}
signingRoot, err := signing.ComputeSigningRoot(att.Data, domain)
if err != nil {
return false, err
}
signature, err := bls.SignatureFromBytes(att.Signature)
if err != nil {
return false, err
}
return signature.FastAggregateVerify(publicKeys, signingRoot), nil
}
// ValidatePayloadAttestationMessageSignature verifies the signature of a
// payload attestation message.
func ValidatePayloadAttestationMessageSignature(ctx context.Context, st state.ReadOnlyBeaconState, msg *eth.PayloadAttestationMessage) error {
if err := ValidateNilPayloadAttestationMessage(msg); err != nil {
return err
}
val, err := st.ValidatorAtIndex(msg.ValidatorIndex)
if err != nil {
return err
}
pub, err := bls.PublicKeyFromBytes(val.PublicKey)
if err != nil {
return err
}
sig, err := bls.SignatureFromBytes(msg.Signature)
if err != nil {
return err
}
currentEpoch := slots.ToEpoch(st.Slot())
domain, err := signing.Domain(st.Fork(), currentEpoch, params.BeaconConfig().DomainPTCAttester, st.GenesisValidatorsRoot())
if err != nil {
return err
}
root, err := signing.ComputeSigningRoot(msg.Data, domain)
if err != nil {
return err
}
if !sig.Verify(pub, root[:]) {
return signing.ErrSigFailedToVerify
}
return nil
}

View File

@@ -0,0 +1,363 @@
package helpers_test
import (
"context"
"slices"
"strconv"
"testing"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/epbs"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
"github.com/prysmaticlabs/prysm/v5/crypto/rand"
"github.com/prysmaticlabs/prysm/v5/math"
eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func TestValidateNilPayloadAttestation(t *testing.T) {
require.ErrorIs(t, helpers.ErrNilData, helpers.ValidateNilPayloadAttestationData(nil))
data := &eth.PayloadAttestationData{}
require.ErrorIs(t, helpers.ErrNilBeaconBlockRoot, helpers.ValidateNilPayloadAttestationData(data))
data.BeaconBlockRoot = make([]byte, 32)
require.NoError(t, helpers.ValidateNilPayloadAttestationData(data))
require.ErrorIs(t, helpers.ErrNilMessage, helpers.ValidateNilPayloadAttestationMessage(nil))
message := &eth.PayloadAttestationMessage{}
require.ErrorIs(t, helpers.ErrNilSignature, helpers.ValidateNilPayloadAttestationMessage(message))
message.Signature = make([]byte, 96)
require.ErrorIs(t, helpers.ErrNilData, helpers.ValidateNilPayloadAttestationMessage(message))
message.Data = data
require.NoError(t, helpers.ValidateNilPayloadAttestationMessage(message))
require.ErrorIs(t, helpers.ErrNilPayloadAttestation, helpers.ValidateNilPayloadAttestation(nil))
att := &eth.PayloadAttestation{}
require.ErrorIs(t, helpers.ErrNilAggregationBits, helpers.ValidateNilPayloadAttestation(att))
att.AggregationBits = bitfield.NewBitvector512()
require.ErrorIs(t, helpers.ErrNilSignature, helpers.ValidateNilPayloadAttestation(att))
att.Signature = message.Signature
require.ErrorIs(t, helpers.ErrNilData, helpers.ValidateNilPayloadAttestation(att))
att.Data = data
require.NoError(t, helpers.ValidateNilPayloadAttestation(att))
}
func TestGetPayloadTimelinessCommittee(t *testing.T) {
helpers.ClearCache()
// Create 10 committees
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)
for i := 0; i < len(validators); i++ {
k := make([]byte, 48)
copy(k, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
PublicKey: k,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
state, err := state_native.InitializeFromProtoEpbs(random.BeaconState(t))
require.NoError(t, err)
require.NoError(t, state.SetValidators(validators))
require.NoError(t, state.SetSlot(200))
ctx := context.Background()
indices, err := helpers.BeaconCommitteeFromState(ctx, state, state.Slot(), 1)
require.NoError(t, err)
require.Equal(t, 128, len(indices))
epoch := slots.ToEpoch(state.Slot())
activeCount, err := helpers.ActiveValidatorCount(ctx, state, epoch)
require.NoError(t, err)
require.Equal(t, uint64(40960), activeCount)
computedCommitteeCount := helpers.SlotCommitteeCount(activeCount)
require.Equal(t, committeeCount, computedCommitteeCount)
committeesPerSlot := math.LargestPowerOfTwo(math.Min(committeeCount, fieldparams.PTCSize))
require.Equal(t, uint64(8), committeesPerSlot)
ptc, err := helpers.GetPayloadTimelinessCommittee(ctx, state, state.Slot())
require.NoError(t, err)
require.Equal(t, fieldparams.PTCSize, len(ptc))
committee1, err := helpers.BeaconCommitteeFromState(ctx, state, state.Slot(), 0)
require.NoError(t, err)
require.DeepEqual(t, committee1[:64], ptc[:64])
}
func Test_PtcAllocation(t *testing.T) {
tests := []struct {
committeeCount int
memberPerCommittee uint64
committeesPerSlot uint64
}{
{1, 512, 1},
{4, 128, 4},
{128, 4, 128},
{512, 1, 512},
{1024, 1, 512},
}
for _, test := range tests {
committeesPerSlot, memberPerCommittee := helpers.PtcAllocation(test.committeeCount)
if memberPerCommittee != test.memberPerCommittee {
t.Errorf("memberPerCommittee(%d) = %d; expected %d", test.committeeCount, memberPerCommittee, test.memberPerCommittee)
}
if committeesPerSlot != test.committeesPerSlot {
t.Errorf("committeesPerSlot(%d) = %d; expected %d", test.committeeCount, committeesPerSlot, test.committeesPerSlot)
}
}
}
func TestGetPayloadAttestingIndices(t *testing.T) {
helpers.ClearCache()
// Create 10 committees. Total 40960 validators.
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)
for i := 0; i < len(validators); i++ {
pubkey := make([]byte, 48)
copy(pubkey, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
PublicKey: pubkey,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
// Create a beacon state.
state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
require.NoError(t, err)
// Get PTC.
ptc, err := helpers.GetPayloadTimelinessCommittee(context.Background(), state, state.Slot())
require.NoError(t, err)
require.Equal(t, fieldparams.PTCSize, len(ptc))
// Generate random indices. PTC members at the corresponding indices are considered attested.
randGen := rand.NewDeterministicGenerator()
attesterCount := randGen.Intn(fieldparams.PTCSize) + 1
indices := randGen.Perm(fieldparams.PTCSize)[:attesterCount]
slices.Sort(indices)
require.Equal(t, attesterCount, len(indices))
// Create a PayloadAttestation with AggregationBits set true at the indices.
aggregationBits := bitfield.NewBitvector512()
for _, index := range indices {
aggregationBits.SetBitAt(uint64(index), true)
}
payloadAttestation := &eth.PayloadAttestation{
AggregationBits: aggregationBits,
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}
// Get attesting indices.
attesters, err := helpers.GetPayloadAttestingIndices(context.Background(), state, state.Slot(), payloadAttestation)
require.NoError(t, err)
require.Equal(t, len(indices), len(attesters))
// Check if each attester equals to the PTC member at the corresponding index.
for i, index := range indices {
require.Equal(t, attesters[i], ptc[index])
}
}
func TestGetIndexedPayloadAttestation(t *testing.T) {
helpers.ClearCache()
// Create 10 committees. Total 40960 validators.
committeeCount := uint64(10)
validatorCount := committeeCount * params.BeaconConfig().TargetCommitteeSize * uint64(params.BeaconConfig().SlotsPerEpoch)
validators := make([]*ethpb.Validator, validatorCount)
for i := 0; i < len(validators); i++ {
publicKey := make([]byte, 48)
copy(publicKey, strconv.Itoa(i))
validators[i] = &ethpb.Validator{
PublicKey: publicKey,
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
// Create a beacon state.
state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
require.NoError(t, err)
// Get PTC.
ptc, err := helpers.GetPayloadTimelinessCommittee(context.Background(), state, state.Slot())
require.NoError(t, err)
require.Equal(t, fieldparams.PTCSize, len(ptc))
// Generate random indices. PTC members at the corresponding indices are considered attested.
randGen := rand.NewDeterministicGenerator()
attesterCount := randGen.Intn(fieldparams.PTCSize) + 1
indices := randGen.Perm(fieldparams.PTCSize)[:attesterCount]
slices.Sort(indices)
require.Equal(t, attesterCount, len(indices))
// Create a PayloadAttestation with AggregationBits set true at the indices.
aggregationBits := bitfield.NewBitvector512()
for _, index := range indices {
aggregationBits.SetBitAt(uint64(index), true)
}
payloadAttestation := &eth.PayloadAttestation{
AggregationBits: aggregationBits,
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, 32),
},
Signature: make([]byte, 96),
}
// Get attesting indices.
ctx := context.Background()
attesters, err := helpers.GetPayloadAttestingIndices(ctx, state, state.Slot(), payloadAttestation)
require.NoError(t, err)
require.Equal(t, len(indices), len(attesters))
// Get an IndexedPayloadAttestation.
indexedPayloadAttestation, err := helpers.GetIndexedPayloadAttestation(ctx, state, state.Slot(), payloadAttestation)
require.NoError(t, err)
require.Equal(t, len(indices), len(indexedPayloadAttestation.AttestingIndices))
require.DeepEqual(t, payloadAttestation.Data, indexedPayloadAttestation.Data)
require.DeepEqual(t, payloadAttestation.Signature, indexedPayloadAttestation.Signature)
// Check if the attesting indices are the same.
slices.Sort(attesters) // GetIndexedPayloadAttestation sorts attesting indices.
require.DeepEqual(t, attesters, indexedPayloadAttestation.AttestingIndices)
}
func TestIsValidIndexedPayloadAttestation(t *testing.T) {
helpers.ClearCache()
// Create validators.
validatorCount := uint64(350)
validators := make([]*ethpb.Validator, validatorCount)
_, secretKeys, err := util.DeterministicDepositsAndKeys(validatorCount)
require.NoError(t, err)
for i := 0; i < len(validators); i++ {
validators[i] = &ethpb.Validator{
PublicKey: secretKeys[i].PublicKey().Marshal(),
WithdrawalCredentials: make([]byte, 32),
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
}
}
// Create a beacon state.
state, err := state_native.InitializeFromProtoEpbs(&ethpb.BeaconStateEPBS{
Validators: validators,
Fork: &ethpb.Fork{
Epoch: 0,
CurrentVersion: params.BeaconConfig().GenesisForkVersion,
PreviousVersion: params.BeaconConfig().GenesisForkVersion,
},
RandaoMixes: make([][]byte, params.BeaconConfig().EpochsPerHistoricalVector),
})
require.NoError(t, err)
// Define test cases.
tests := []struct {
attestation *epbs.IndexedPayloadAttestation
}{
{
attestation: &epbs.IndexedPayloadAttestation{
AttestingIndices: []primitives.ValidatorIndex{1},
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
{
attestation: &epbs.IndexedPayloadAttestation{
AttestingIndices: []primitives.ValidatorIndex{13, 19},
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
{
attestation: &epbs.IndexedPayloadAttestation{
AttestingIndices: []primitives.ValidatorIndex{123, 234, 345},
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
{
attestation: &epbs.IndexedPayloadAttestation{
AttestingIndices: []primitives.ValidatorIndex{38, 46, 54, 62, 70, 78, 86, 194},
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
{
attestation: &epbs.IndexedPayloadAttestation{
AttestingIndices: []primitives.ValidatorIndex{5},
Data: &eth.PayloadAttestationData{
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
},
Signature: make([]byte, fieldparams.BLSSignatureLength),
},
},
}
// Run test cases.
for _, test := range tests {
signatures := make([]bls.Signature, len(test.attestation.AttestingIndices))
for i, index := range test.attestation.AttestingIndices {
signedBytes, err := signing.ComputeDomainAndSign(
state,
slots.ToEpoch(test.attestation.Data.Slot),
test.attestation.Data,
params.BeaconConfig().DomainPTCAttester,
secretKeys[index],
)
require.NoError(t, err)
signature, err := bls.SignatureFromBytes(signedBytes)
require.NoError(t, err)
signatures[i] = signature
}
aggregatedSignature := bls.AggregateSignatures(signatures)
test.attestation.Signature = aggregatedSignature.Marshal()
isValid, err := helpers.IsValidIndexedPayloadAttestation(state, test.attestation)
require.NoError(t, err)
require.Equal(t, true, isValid)
}
}

View File

@@ -22,7 +22,7 @@ func SlashingParamsPerVersion(v int) (slashingQuotient, proposerRewardQuotient,
slashingQuotient = cfg.MinSlashingPenaltyQuotientBellatrix
proposerRewardQuotient = cfg.ProposerRewardQuotient
whistleblowerRewardQuotient = cfg.WhistleBlowerRewardQuotient
case version.Electra:
case version.Electra, version.EPBS:
slashingQuotient = cfg.MinSlashingPenaltyQuotientElectra
proposerRewardQuotient = cfg.ProposerRewardQuotient
whistleblowerRewardQuotient = cfg.WhistleBlowerRewardQuotientElectra

View File

@@ -25,6 +25,7 @@ import (
type ReadOnlyDatabase interface {
// Block related methods.
Block(ctx context.Context, blockRoot [32]byte) (interfaces.ReadOnlySignedBeaconBlock, error)
SignedExecutionPayloadHeader(ctx context.Context, blockRoot [32]byte) (interfaces.ROSignedExecutionPayloadHeader, error)
Blocks(ctx context.Context, f *filters.QueryFilter) ([]interfaces.ReadOnlySignedBeaconBlock, [][32]byte, error)
BlockRoots(ctx context.Context, f *filters.QueryFilter) ([][32]byte, error)
BlocksBySlot(ctx context.Context, slot primitives.Slot) ([]interfaces.ReadOnlySignedBeaconBlock, error)
@@ -55,6 +56,7 @@ type ReadOnlyDatabase interface {
DepositContractAddress(ctx context.Context) ([]byte, error)
// ExecutionChainData operations.
ExecutionChainData(ctx context.Context) (*ethpb.ETH1ChainData, error)
SignedBlindPayloadEnvelope(ctx context.Context, blockRoot []byte) (*ethpb.SignedBlindPayloadEnvelope, error)
// Fee recipients operations.
FeeRecipientByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (common.Address, error)
RegistrationByValidatorID(ctx context.Context, id primitives.ValidatorIndex) (*ethpb.ValidatorRegistrationV1, error)
@@ -92,6 +94,7 @@ type NoHeadAccessDatabase interface {
SaveDepositContractAddress(ctx context.Context, addr common.Address) error
// SaveExecutionChainData operations.
SaveExecutionChainData(ctx context.Context, data *ethpb.ETH1ChainData) error
SaveBlindPayloadEnvelope(ctx context.Context, envelope *ethpb.SignedBlindPayloadEnvelope) error
// Run any required database migrations.
RunMigrations(ctx context.Context) error
// Fee recipients operations.

View File

@@ -6,10 +6,12 @@ go_library(
"archived_point.go",
"backfill.go",
"backup.go",
"blind_payload_envelope.go",
"blocks.go",
"checkpoint.go",
"deposit_contract.go",
"encoding.go",
"epbs.go",
"error.go",
"execution_chain.go",
"finalized_block_roots.go",
@@ -70,6 +72,7 @@ go_library(
"@com_github_schollz_progressbar_v3//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_etcd_go_bbolt//:go_default_library",
"@io_opencensus_go//trace:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
)
@@ -80,10 +83,12 @@ go_test(
"archived_point_test.go",
"backfill_test.go",
"backup_test.go",
"blind_payload_envelope_test.go",
"blocks_test.go",
"checkpoint_test.go",
"deposit_contract_test.go",
"encoding_test.go",
"epbs_test.go",
"execution_chain_test.go",
"finalized_block_roots_test.go",
"genesis_test.go",
@@ -124,6 +129,7 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
"@com_github_ethereum_go_ethereum//common:go_default_library",
"@com_github_golang_snappy//:go_default_library",
"@com_github_pkg_errors//:go_default_library",

View File

@@ -0,0 +1,45 @@
package kv
import (
"context"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
bolt "go.etcd.io/bbolt"
"go.opencensus.io/trace"
)
// SaveBlindPayloadEnvelope saves a signed execution payload envelope blind in the database.
func (s *Store) SaveBlindPayloadEnvelope(ctx context.Context, env *ethpb.SignedBlindPayloadEnvelope) error {
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlindPayloadEnvelope")
defer span.End()
enc, err := encode(ctx, env)
if err != nil {
return err
}
r := env.Message.BeaconBlockRoot
err = s.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket(executionPayloadEnvelopeBucket)
return bucket.Put(r, enc)
})
return err
}
// SignedBlindPayloadEnvelope retrieves a signed execution payload envelope blind from the database.
func (s *Store) SignedBlindPayloadEnvelope(ctx context.Context, blockRoot []byte) (*ethpb.SignedBlindPayloadEnvelope, error) {
ctx, span := trace.StartSpan(ctx, "BeaconDB.SignedBlindPayloadEnvelope")
defer span.End()
env := &ethpb.SignedBlindPayloadEnvelope{}
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(executionPayloadEnvelopeBucket)
enc := bkt.Get(blockRoot)
if enc == nil {
return ErrNotFound
}
return decode(ctx, enc, env)
})
return env, err
}

View File

@@ -0,0 +1,23 @@
package kv
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
)
func TestStore_SignedBlindPayloadEnvelope(t *testing.T) {
db := setupDB(t)
ctx := context.Background()
_, err := db.SignedBlindPayloadEnvelope(ctx, []byte("test"))
require.ErrorIs(t, err, ErrNotFound)
env := random.SignedBlindPayloadEnvelope(t)
err = db.SaveBlindPayloadEnvelope(ctx, env)
require.NoError(t, err)
got, err := db.SignedBlindPayloadEnvelope(ctx, env.Message.BeaconBlockRoot)
require.NoError(t, err)
require.DeepEqual(t, got, env)
}

View File

@@ -823,6 +823,11 @@ func unmarshalBlock(_ context.Context, enc []byte) (interfaces.ReadOnlySignedBea
if err := rawBlock.UnmarshalSSZ(enc[len(electraBlindKey):]); err != nil {
return nil, errors.Wrap(err, "could not unmarshal blinded Electra block")
}
case hasEpbsKey(enc):
rawBlock = &ethpb.SignedBeaconBlockEpbs{}
if err := rawBlock.UnmarshalSSZ(enc[len(epbsKey):]); err != nil {
return nil, errors.Wrap(err, "could not unmarshal EPBS block")
}
default:
// Marshal block bytes to phase 0 beacon block.
rawBlock = &ethpb.SignedBeaconBlock{}
@@ -852,6 +857,8 @@ func encodeBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) {
func keyForBlock(blk interfaces.ReadOnlySignedBeaconBlock) ([]byte, error) {
switch blk.Version() {
case version.EPBS:
return epbsKey, nil
case version.Electra:
if blk.IsBlinded() {
return electraBlindKey, nil

View File

@@ -18,6 +18,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
"google.golang.org/protobuf/proto"
)
@@ -146,6 +147,17 @@ var blockTests = []struct {
}
return blocks.NewSignedBeaconBlock(b)
}},
{
name: "epbs",
newBlock: func(slot primitives.Slot, root []byte) (interfaces.ReadOnlySignedBeaconBlock, error) {
b := random.SignedBeaconBlock(&testing.T{})
b.Block.Slot = slot
if root != nil {
b.Block.ParentRoot = root
}
return blocks.NewSignedBeaconBlock(b)
},
},
}
func TestStore_SaveBlock_NoDuplicates(t *testing.T) {
@@ -202,7 +214,7 @@ func TestStore_BlocksCRUD(t *testing.T) {
retrievedBlock, err = db.Block(ctx, blockRoot)
require.NoError(t, err)
wanted := retrievedBlock
if retrievedBlock.Version() >= version.Bellatrix {
if retrievedBlock.Version() >= version.Bellatrix && retrievedBlock.Version() < version.EPBS {
wanted, err = retrievedBlock.ToBlinded()
require.NoError(t, err)
}
@@ -390,7 +402,7 @@ func TestStore_BlocksCRUD_NoCache(t *testing.T) {
require.NoError(t, err)
wanted := blk
if blk.Version() >= version.Bellatrix {
if blk.Version() >= version.Bellatrix && blk.Version() < version.EPBS {
wanted, err = blk.ToBlinded()
require.NoError(t, err)
}
@@ -609,7 +621,7 @@ func TestStore_SaveBlock_CanGetHighestAt(t *testing.T) {
b, err := db.Block(ctx, root)
require.NoError(t, err)
wanted := block1
if block1.Version() >= version.Bellatrix {
if block1.Version() >= version.Bellatrix && block1.Version() < version.EPBS {
wanted, err = wanted.ToBlinded()
require.NoError(t, err)
}
@@ -627,7 +639,7 @@ func TestStore_SaveBlock_CanGetHighestAt(t *testing.T) {
b, err = db.Block(ctx, root)
require.NoError(t, err)
wanted2 := block2
if block2.Version() >= version.Bellatrix {
if block2.Version() >= version.Bellatrix && block2.Version() < version.EPBS {
wanted2, err = block2.ToBlinded()
require.NoError(t, err)
}
@@ -645,7 +657,7 @@ func TestStore_SaveBlock_CanGetHighestAt(t *testing.T) {
b, err = db.Block(ctx, root)
require.NoError(t, err)
wanted = block3
if block3.Version() >= version.Bellatrix {
if block3.Version() >= version.Bellatrix && block3.Version() < version.EPBS {
wanted, err = wanted.ToBlinded()
require.NoError(t, err)
}
@@ -681,7 +693,7 @@ func TestStore_GenesisBlock_CanGetHighestAt(t *testing.T) {
b, err := db.Block(ctx, root)
require.NoError(t, err)
wanted := block1
if block1.Version() >= version.Bellatrix {
if block1.Version() >= version.Bellatrix && block1.Version() < version.EPBS {
wanted, err = block1.ToBlinded()
require.NoError(t, err)
}
@@ -698,7 +710,7 @@ func TestStore_GenesisBlock_CanGetHighestAt(t *testing.T) {
b, err = db.Block(ctx, root)
require.NoError(t, err)
wanted = genesisBlock
if genesisBlock.Version() >= version.Bellatrix {
if genesisBlock.Version() >= version.Bellatrix && genesisBlock.Version() < version.EPBS {
wanted, err = genesisBlock.ToBlinded()
require.NoError(t, err)
}
@@ -715,7 +727,7 @@ func TestStore_GenesisBlock_CanGetHighestAt(t *testing.T) {
b, err = db.Block(ctx, root)
require.NoError(t, err)
wanted = genesisBlock
if genesisBlock.Version() >= version.Bellatrix {
if genesisBlock.Version() >= version.Bellatrix && genesisBlock.Version() < version.EPBS {
wanted, err = genesisBlock.ToBlinded()
require.NoError(t, err)
}
@@ -811,7 +823,7 @@ func TestStore_BlocksBySlot_BlockRootsBySlot(t *testing.T) {
require.NoError(t, err)
wanted := b1
if b1.Version() >= version.Bellatrix {
if b1.Version() >= version.Bellatrix && b1.Version() < version.EPBS {
wanted, err = b1.ToBlinded()
require.NoError(t, err)
}
@@ -827,7 +839,7 @@ func TestStore_BlocksBySlot_BlockRootsBySlot(t *testing.T) {
t.Fatalf("Expected 2 blocks, received %d blocks", len(retrievedBlocks))
}
wanted = b2
if b2.Version() >= version.Bellatrix {
if b2.Version() >= version.Bellatrix && b2.Version() < version.EPBS {
wanted, err = b2.ToBlinded()
require.NoError(t, err)
}
@@ -837,7 +849,7 @@ func TestStore_BlocksBySlot_BlockRootsBySlot(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, true, proto.Equal(wantedPb, retrieved0Pb), "Wanted: %v, received: %v", retrievedBlocks[0], wanted)
wanted = b3
if b3.Version() >= version.Bellatrix {
if b3.Version() >= version.Bellatrix && b3.Version() < version.EPBS {
wanted, err = b3.ToBlinded()
require.NoError(t, err)
}

View File

@@ -78,6 +78,8 @@ func isSSZStorageFormat(obj interface{}) bool {
return true
case *ethpb.VoluntaryExit:
return true
case *ethpb.SignedBlindPayloadEnvelope:
return true
case *ethpb.ValidatorRegistrationV1:
return true
default:

View File

@@ -0,0 +1,18 @@
package kv
import (
"context"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
)
func (s *Store) SignedExecutionPayloadHeader(ctx context.Context, blockRoot [32]byte) (interfaces.ROSignedExecutionPayloadHeader, error) {
b, err := s.Block(ctx, blockRoot)
if err != nil {
return nil, err
}
if b.IsNil() {
return nil, ErrNotFound
}
return b.Block().Body().SignedExecutionPayloadHeader()
}

View File

@@ -0,0 +1,28 @@
package kv
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
)
func Test_SignedExecutionPayloadHeader(t *testing.T) {
db := setupDB(t)
ctx := context.Background()
b := random.SignedBeaconBlock(t)
blk, err := blocks.NewSignedBeaconBlock(b)
require.NoError(t, err)
blockRoot, err := blk.Block().HashTreeRoot()
require.NoError(t, err)
require.NoError(t, db.SaveBlock(ctx, blk))
retrievedHeader, err := db.SignedExecutionPayloadHeader(ctx, blockRoot)
require.NoError(t, err)
wantedHeader, err := blk.Block().Body().SignedExecutionPayloadHeader()
require.NoError(t, err)
require.DeepEqual(t, wantedHeader, retrievedHeader)
}

View File

@@ -65,3 +65,10 @@ func hasElectraBlindKey(enc []byte) bool {
}
return bytes.Equal(enc[:len(electraBlindKey)], electraBlindKey)
}
func hasEpbsKey(enc []byte) bool {
if len(epbsKey) >= len(enc) {
return false
}
return bytes.Equal(enc[:len(epbsKey)], epbsKey)
}

View File

@@ -119,6 +119,9 @@ var Buckets = [][]byte{
feeRecipientBucket,
registrationBucket,
// ePBS
executionPayloadEnvelopeBucket,
}
// KVStoreOption is a functional option that modifies a kv.Store.

View File

@@ -7,15 +7,16 @@ package kv
// it easy to scan for keys that have a certain shard number as a prefix and return those
// corresponding attestations.
var (
blocksBucket = []byte("blocks")
stateBucket = []byte("state")
stateSummaryBucket = []byte("state-summary")
chainMetadataBucket = []byte("chain-metadata")
checkpointBucket = []byte("check-point")
powchainBucket = []byte("powchain")
stateValidatorsBucket = []byte("state-validators")
feeRecipientBucket = []byte("fee-recipient")
registrationBucket = []byte("registration")
blocksBucket = []byte("blocks")
stateBucket = []byte("state")
stateSummaryBucket = []byte("state-summary")
chainMetadataBucket = []byte("chain-metadata")
checkpointBucket = []byte("check-point")
powchainBucket = []byte("powchain")
stateValidatorsBucket = []byte("state-validators")
feeRecipientBucket = []byte("fee-recipient")
registrationBucket = []byte("registration")
executionPayloadEnvelopeBucket = []byte("execution-payload-envelope")
// Light Client Updates Bucket
lightClientUpdatesBucket = []byte("light-client-updates")
@@ -53,6 +54,7 @@ var (
denebBlindKey = []byte("blind-deneb")
electraKey = []byte("electra")
electraBlindKey = []byte("blind-electra")
epbsKey = []byte("epbs")
// block root included in the beacon state used by weak subjectivity initial sync
originCheckpointBlockRootKey = []byte("origin-checkpoint-block-root")

View File

@@ -252,6 +252,10 @@ func (s *Store) saveStatesEfficientInternal(ctx context.Context, tx *bolt.Tx, bl
if err := s.processElectra(ctx, rawType, rt[:], bucket, valIdxBkt, validatorKeys[i]); err != nil {
return err
}
case *ethpb.BeaconStateEPBS:
if err := s.processEPBS(ctx, rawType, rt[:], bucket, valIdxBkt, validatorKeys[i]); err != nil {
return err
}
default:
return errors.New("invalid state type")
}
@@ -367,6 +371,24 @@ func (s *Store) processElectra(ctx context.Context, pbState *ethpb.BeaconStateEl
return nil
}
func (s *Store) processEPBS(ctx context.Context, pbState *ethpb.BeaconStateEPBS, rootHash []byte, bucket, valIdxBkt *bolt.Bucket, validatorKey []byte) error {
valEntries := pbState.Validators
pbState.Validators = make([]*ethpb.Validator, 0)
rawObj, err := pbState.MarshalSSZ()
if err != nil {
return err
}
encodedState := snappy.Encode(nil, append(epbsKey, rawObj...))
if err := bucket.Put(rootHash, encodedState); err != nil {
return err
}
pbState.Validators = valEntries
if err := valIdxBkt.Put(rootHash, validatorKey); err != nil {
return err
}
return nil
}
func (s *Store) storeValidatorEntriesSeparately(ctx context.Context, tx *bolt.Tx, validatorsEntries map[string]*ethpb.Validator) error {
valBkt := tx.Bucket(stateValidatorsBucket)
for hashStr, validatorEntry := range validatorsEntries {
@@ -516,6 +538,19 @@ func (s *Store) unmarshalState(_ context.Context, enc []byte, validatorEntries [
}
switch {
case hasEpbsKey(enc):
protoState := &ethpb.BeaconStateEPBS{}
if err := protoState.UnmarshalSSZ(enc[len(epbsKey):]); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal encoding for EPBS")
}
ok, err := s.isStateValidatorMigrationOver()
if err != nil {
return nil, err
}
if ok {
protoState.Validators = validatorEntries
}
return statenative.InitializeFromProtoEpbs(protoState)
case hasElectraKey(enc):
protoState := &ethpb.BeaconStateElectra{}
if err := protoState.UnmarshalSSZ(enc[len(electraKey):]); err != nil {
@@ -675,6 +710,19 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
return nil, err
}
return snappy.Encode(nil, append(electraKey, rawObj...)), nil
case *ethpb.BeaconStateEPBS:
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateEPBS)
if !ok {
return nil, errors.New("non valid inner state")
}
if rState == nil {
return nil, errors.New("nil state")
}
rawObj, err := rState.MarshalSSZ()
if err != nil {
return nil, err
}
return snappy.Encode(nil, append(epbsKey, rawObj...)), nil
default:
return nil, errors.New("invalid inner state")
}

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
statenative "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
"github.com/prysmaticlabs/prysm/v5/config/features"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
@@ -20,6 +21,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
bolt "go.etcd.io/bbolt"
)
@@ -138,20 +140,17 @@ func TestState_CanSaveRetrieve(t *testing.T) {
require.NoError(t, err)
require.NoError(t, st.SetSlot(100))
p, err := blocks.WrappedExecutionPayloadHeaderElectra(&enginev1.ExecutionPayloadHeaderElectra{
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
ExtraData: []byte("foo"),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
TransactionsRoot: make([]byte, 32),
WithdrawalsRoot: make([]byte, 32),
DepositRequestsRoot: make([]byte, 32),
WithdrawalRequestsRoot: make([]byte, 32),
ConsolidationRequestsRoot: make([]byte, 32),
ParentHash: make([]byte, 32),
FeeRecipient: make([]byte, 20),
StateRoot: make([]byte, 32),
ReceiptsRoot: make([]byte, 32),
LogsBloom: make([]byte, 256),
PrevRandao: make([]byte, 32),
ExtraData: []byte("foo"),
BaseFeePerGas: make([]byte, 32),
BlockHash: make([]byte, 32),
TransactionsRoot: make([]byte, 32),
WithdrawalsRoot: make([]byte, 32),
})
require.NoError(t, err)
require.NoError(t, st.SetLatestExecutionPayloadHeader(p))
@@ -159,6 +158,16 @@ func TestState_CanSaveRetrieve(t *testing.T) {
},
rootSeed: 'E',
},
{
name: "epbs",
s: func() state.BeaconState {
stPb := random.BeaconState(t)
st, err := statenative.InitializeFromProtoUnsafeEpbs(stPb)
require.NoError(t, err)
return st
},
rootSeed: 'F',
},
}
db := setupDB(t)
@@ -1113,6 +1122,26 @@ func TestDenebState_CanDelete(t *testing.T) {
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
}
func TestEpbsState_CanDelete(t *testing.T) {
db := setupDB(t)
r := [32]byte{'A'}
require.Equal(t, false, db.HasState(context.Background(), r))
s := random.BeaconState(t)
st, err := statenative.InitializeFromProtoUnsafeEpbs(s)
require.NoError(t, err)
require.NoError(t, db.SaveState(context.Background(), st, r))
require.Equal(t, true, db.HasState(context.Background(), r))
require.NoError(t, db.DeleteState(context.Background(), r))
savedS, err := db.State(context.Background(), r)
require.NoError(t, err)
require.Equal(t, state.ReadOnlyBeaconState(nil), savedS, "Unsaved state should've been nil")
}
func TestStateDeneb_CanSaveRetrieveValidatorEntries(t *testing.T) {
db := setupDB(t)

View File

@@ -167,15 +167,6 @@ func (s *Service) NewPayload(ctx context.Context, payload interfaces.ExecutionDa
if err != nil {
return nil, handleRPCError(err)
}
case *pb.ExecutionPayloadElectra:
payloadPb, ok := payload.Proto().(*pb.ExecutionPayloadElectra)
if !ok {
return nil, errors.New("execution data must be a Electra execution payload")
}
err := s.rpcClient.CallContext(ctx, result, NewPayloadMethodV4, payloadPb, versionedHashes, parentBlockRoot)
if err != nil {
return nil, handleRPCError(err)
}
default:
return nil, errors.New("unknown execution data type")
}
@@ -268,9 +259,6 @@ func (s *Service) ForkchoiceUpdated(
func getPayloadMethodAndMessage(slot primitives.Slot) (string, proto.Message) {
pe := slots.ToEpoch(slot)
if pe >= params.BeaconConfig().ElectraForkEpoch {
return GetPayloadMethodV4, &pb.ExecutionPayloadElectraWithValueAndBlobsBundle{}
}
if pe >= params.BeaconConfig().DenebForkEpoch {
return GetPayloadMethodV3, &pb.ExecutionPayloadDenebWithValueAndBlobsBundle{}
}
@@ -566,7 +554,7 @@ func fullPayloadFromPayloadBody(
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
Withdrawals: body.Withdrawals,
}) // We can't get the block value and don't care about the block value for this instance
case version.Deneb:
case version.Deneb, version.Electra:
ebg, err := header.ExcessBlobGas()
if err != nil {
return nil, errors.Wrap(err, "unable to extract ExcessBlobGas attribute from execution payload header")
@@ -595,50 +583,6 @@ func fullPayloadFromPayloadBody(
ExcessBlobGas: ebg,
BlobGasUsed: bgu,
}) // We can't get the block value and don't care about the block value for this instance
case version.Electra:
ebg, err := header.ExcessBlobGas()
if err != nil {
return nil, errors.Wrap(err, "unable to extract ExcessBlobGas attribute from execution payload header")
}
bgu, err := header.BlobGasUsed()
if err != nil {
return nil, errors.Wrap(err, "unable to extract BlobGasUsed attribute from execution payload header")
}
wr, err := pb.JsonWithdrawalRequestsToProto(body.WithdrawalRequests)
if err != nil {
return nil, err
}
dr, err := pb.JsonDepositRequestsToProto(body.DepositRequests)
if err != nil {
return nil, err
}
cr, err := pb.JsonConsolidationRequestsToProto(body.ConsolidationRequests)
if err != nil {
return nil, err
}
return blocks.WrappedExecutionPayloadElectra(
&pb.ExecutionPayloadElectra{
ParentHash: header.ParentHash(),
FeeRecipient: header.FeeRecipient(),
StateRoot: header.StateRoot(),
ReceiptsRoot: header.ReceiptsRoot(),
LogsBloom: header.LogsBloom(),
PrevRandao: header.PrevRandao(),
BlockNumber: header.BlockNumber(),
GasLimit: header.GasLimit(),
GasUsed: header.GasUsed(),
Timestamp: header.Timestamp(),
ExtraData: header.ExtraData(),
BaseFeePerGas: header.BaseFeePerGas(),
BlockHash: header.BlockHash(),
Transactions: pb.RecastHexutilByteSlice(body.Transactions),
Withdrawals: body.Withdrawals,
ExcessBlobGas: ebg,
BlobGasUsed: bgu,
DepositRequests: dr,
WithdrawalRequests: wr,
ConsolidationRequests: cr,
}) // We can't get the block value and don't care about the block value for this instance
default:
return nil, fmt.Errorf("unknown execution block version for payload %d", bVersion)
}
@@ -761,7 +705,7 @@ func buildEmptyExecutionPayload(v int) (proto.Message, error) {
Transactions: make([][]byte, 0),
Withdrawals: make([]*pb.Withdrawal, 0),
}, nil
case version.Deneb:
case version.Deneb, version.Electra:
return &pb.ExecutionPayloadDeneb{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
@@ -775,22 +719,6 @@ func buildEmptyExecutionPayload(v int) (proto.Message, error) {
Transactions: make([][]byte, 0),
Withdrawals: make([]*pb.Withdrawal, 0),
}, nil
case version.Electra:
return &pb.ExecutionPayloadElectra{
ParentHash: make([]byte, fieldparams.RootLength),
FeeRecipient: make([]byte, fieldparams.FeeRecipientLength),
StateRoot: make([]byte, fieldparams.RootLength),
ReceiptsRoot: make([]byte, fieldparams.RootLength),
LogsBloom: make([]byte, fieldparams.LogsBloomLength),
PrevRandao: make([]byte, fieldparams.RootLength),
ExtraData: make([]byte, 0),
BaseFeePerGas: make([]byte, fieldparams.RootLength),
BlockHash: make([]byte, fieldparams.RootLength),
Transactions: make([][]byte, 0),
Withdrawals: make([]*pb.Withdrawal, 0),
WithdrawalRequests: make([]*pb.WithdrawalRequest, 0),
DepositRequests: make([]*pb.DepositRequest, 0),
}, nil
default:
return nil, errors.Wrapf(ErrUnsupportedVersion, "version=%s", version.String(v))
}

View File

@@ -320,72 +320,6 @@ func TestClient_HTTP(t *testing.T) {
blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)}
require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs)
})
t.Run(GetPayloadMethodV4, func(t *testing.T) {
payloadId := [8]byte{1}
want, ok := fix["ExecutionPayloadElectraWithValue"].(*pb.GetPayloadV4ResponseJson)
require.Equal(t, true, ok)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
defer func() {
require.NoError(t, r.Body.Close())
}()
enc, err := io.ReadAll(r.Body)
require.NoError(t, err)
jsonRequestString := string(enc)
reqArg, err := json.Marshal(pb.PayloadIDBytes(payloadId))
require.NoError(t, err)
// We expect the JSON string RPC request contains the right arguments.
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
resp := map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"result": want,
}
err = json.NewEncoder(w).Encode(resp)
require.NoError(t, err)
}))
defer srv.Close()
rpcClient, err := rpc.DialHTTP(srv.URL)
require.NoError(t, err)
defer rpcClient.Close()
client := &Service{}
client.rpcClient = rpcClient
// We call the RPC method via HTTP and expect a proper result.
resp, err := client.GetPayload(ctx, payloadId, 2*params.BeaconConfig().SlotsPerEpoch)
require.NoError(t, err)
require.Equal(t, true, resp.OverrideBuilder)
g, err := resp.ExecutionData.ExcessBlobGas()
require.NoError(t, err)
require.DeepEqual(t, uint64(3), g)
g, err = resp.ExecutionData.BlobGasUsed()
require.NoError(t, err)
require.DeepEqual(t, uint64(2), g)
commitments := [][]byte{bytesutil.PadTo([]byte("commitment1"), fieldparams.BLSPubkeyLength), bytesutil.PadTo([]byte("commitment2"), fieldparams.BLSPubkeyLength)}
require.DeepEqual(t, commitments, resp.BlobsBundle.KzgCommitments)
proofs := [][]byte{bytesutil.PadTo([]byte("proof1"), fieldparams.BLSPubkeyLength), bytesutil.PadTo([]byte("proof2"), fieldparams.BLSPubkeyLength)}
require.DeepEqual(t, proofs, resp.BlobsBundle.Proofs)
blobs := [][]byte{bytesutil.PadTo([]byte("a"), fieldparams.BlobLength), bytesutil.PadTo([]byte("b"), fieldparams.BlobLength)}
require.DeepEqual(t, blobs, resp.BlobsBundle.Blobs)
ede, ok := resp.ExecutionData.(interfaces.ExecutionDataElectra)
require.Equal(t, true, ok)
require.NotNil(t, ede.WithdrawalRequests())
wrequestsNotOverMax := len(ede.WithdrawalRequests()) <= int(params.BeaconConfig().MaxWithdrawalRequestsPerPayload)
require.Equal(t, true, wrequestsNotOverMax)
require.NotNil(t, ede.DepositRequests())
drequestsNotOverMax := len(ede.DepositRequests()) <= int(params.BeaconConfig().MaxDepositRequestsPerPayload)
require.Equal(t, true, drequestsNotOverMax)
require.NotNil(t, ede.ConsolidationRequests())
consolidationsNotOverMax := len(ede.ConsolidationRequests()) <= int(params.BeaconConfig().MaxConsolidationsRequestsPerPayload)
require.Equal(t, true, consolidationsNotOverMax)
})
t.Run(ForkchoiceUpdatedMethod+" VALID status", func(t *testing.T) {
forkChoiceState := &pb.ForkchoiceState{
HeadBlockHash: []byte("head"),
@@ -568,20 +502,6 @@ func TestClient_HTTP(t *testing.T) {
require.NoError(t, err)
require.DeepEqual(t, want.LatestValidHash, resp)
})
t.Run(NewPayloadMethodV4+" VALID status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
require.Equal(t, true, ok)
want, ok := fix["ValidPayloadStatus"].(*pb.PayloadStatus)
require.Equal(t, true, ok)
client := newPayloadV4Setup(t, want, execPayload)
// We call the RPC method via HTTP and expect a proper result.
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
require.NoError(t, err)
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
require.NoError(t, err)
require.DeepEqual(t, want.LatestValidHash, resp)
})
t.Run(NewPayloadMethod+" SYNCING status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
@@ -624,20 +544,6 @@ func TestClient_HTTP(t *testing.T) {
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
require.DeepEqual(t, []uint8(nil), resp)
})
t.Run(NewPayloadMethodV4+" SYNCING status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
require.Equal(t, true, ok)
want, ok := fix["SyncingStatus"].(*pb.PayloadStatus)
require.Equal(t, true, ok)
client := newPayloadV4Setup(t, want, execPayload)
// We call the RPC method via HTTP and expect a proper result.
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
require.NoError(t, err)
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
require.ErrorIs(t, ErrAcceptedSyncingPayloadStatus, err)
require.DeepEqual(t, []uint8(nil), resp)
})
t.Run(NewPayloadMethod+" INVALID_BLOCK_HASH status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
@@ -680,20 +586,6 @@ func TestClient_HTTP(t *testing.T) {
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
require.DeepEqual(t, []uint8(nil), resp)
})
t.Run(NewPayloadMethodV4+" INVALID_BLOCK_HASH status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
require.Equal(t, true, ok)
want, ok := fix["InvalidBlockHashStatus"].(*pb.PayloadStatus)
require.Equal(t, true, ok)
client := newPayloadV4Setup(t, want, execPayload)
// We call the RPC method via HTTP and expect a proper result.
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
require.NoError(t, err)
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
require.ErrorIs(t, ErrInvalidBlockHashPayloadStatus, err)
require.DeepEqual(t, []uint8(nil), resp)
})
t.Run(NewPayloadMethod+" INVALID status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
@@ -736,20 +628,6 @@ func TestClient_HTTP(t *testing.T) {
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
require.DeepEqual(t, want.LatestValidHash, resp)
})
t.Run(NewPayloadMethodV4+" INVALID status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayloadElectra"].(*pb.ExecutionPayloadElectra)
require.Equal(t, true, ok)
want, ok := fix["InvalidStatus"].(*pb.PayloadStatus)
require.Equal(t, true, ok)
client := newPayloadV4Setup(t, want, execPayload)
// We call the RPC method via HTTP and expect a proper result.
wrappedPayload, err := blocks.WrappedExecutionPayloadElectra(execPayload)
require.NoError(t, err)
resp, err := client.NewPayload(ctx, wrappedPayload, []common.Hash{}, &common.Hash{'a'})
require.ErrorIs(t, ErrInvalidPayloadStatus, err)
require.DeepEqual(t, want.LatestValidHash, resp)
})
t.Run(NewPayloadMethod+" UNKNOWN status", func(t *testing.T) {
execPayload, ok := fix["ExecutionPayload"].(*pb.ExecutionPayload)
require.Equal(t, true, ok)
@@ -1417,10 +1295,8 @@ func fixtures() map[string]interface{} {
"ExecutionPayload": s.ExecutionPayload,
"ExecutionPayloadCapella": s.ExecutionPayloadCapella,
"ExecutionPayloadDeneb": s.ExecutionPayloadDeneb,
"ExecutionPayloadElectra": s.ExecutionPayloadElectra,
"ExecutionPayloadCapellaWithValue": s.ExecutionPayloadWithValueCapella,
"ExecutionPayloadDenebWithValue": s.ExecutionPayloadWithValueDeneb,
"ExecutionPayloadElectraWithValue": s.ExecutionPayloadWithValueElectra,
"ValidPayloadStatus": s.ValidPayloadStatus,
"InvalidBlockHashStatus": s.InvalidBlockHashStatus,
"AcceptedStatus": s.AcceptedStatus,
@@ -1558,40 +1434,6 @@ func fixturesStruct() *payloadFixtures {
TargetPubkey: &tPubkey,
}
}
dr, err := pb.JsonDepositRequestsToProto(depositRequests)
if err != nil {
panic(err)
}
wr, err := pb.JsonWithdrawalRequestsToProto(withdrawalRequests)
if err != nil {
panic(err)
}
cr, err := pb.JsonConsolidationRequestsToProto(consolidationRequests)
if err != nil {
panic(err)
}
executionPayloadFixtureElectra := &pb.ExecutionPayloadElectra{
ParentHash: foo[:],
FeeRecipient: bar,
StateRoot: foo[:],
ReceiptsRoot: foo[:],
LogsBloom: baz,
PrevRandao: foo[:],
BlockNumber: 1,
GasLimit: 1,
GasUsed: 1,
Timestamp: 1,
ExtraData: foo[:],
BaseFeePerGas: bytesutil.PadTo(baseFeePerGas.Bytes(), fieldparams.RootLength),
BlockHash: foo[:],
Transactions: [][]byte{foo[:]},
Withdrawals: []*pb.Withdrawal{},
BlobGasUsed: 2,
ExcessBlobGas: 3,
DepositRequests: dr,
WithdrawalRequests: wr,
ConsolidationRequests: cr,
}
hexUint := hexutil.Uint64(1)
executionPayloadWithValueFixtureCapella := &pb.GetPayloadV2ResponseJson{
ExecutionPayload: &pb.ExecutionPayloadCapellaJSON{
@@ -1641,36 +1483,6 @@ func fixturesStruct() *payloadFixtures {
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
},
}
executionPayloadWithValueFixtureElectra := &pb.GetPayloadV4ResponseJson{
ShouldOverrideBuilder: true,
ExecutionPayload: &pb.ExecutionPayloadElectraJSON{
ParentHash: &common.Hash{'a'},
FeeRecipient: &common.Address{'b'},
StateRoot: &common.Hash{'c'},
ReceiptsRoot: &common.Hash{'d'},
LogsBloom: &hexutil.Bytes{'e'},
PrevRandao: &common.Hash{'f'},
BaseFeePerGas: "0x123",
BlockHash: &common.Hash{'g'},
Transactions: []hexutil.Bytes{{'h'}},
Withdrawals: []*pb.Withdrawal{},
BlockNumber: &hexUint,
GasLimit: &hexUint,
GasUsed: &hexUint,
Timestamp: &hexUint,
BlobGasUsed: &bgu,
ExcessBlobGas: &ebg,
DepositRequests: depositRequests,
WithdrawalRequests: withdrawalRequests,
ConsolidationRequests: consolidationRequests,
},
BlockValue: "0x11fffffffff",
BlobsBundle: &pb.BlobBundleJSON{
Commitments: []hexutil.Bytes{[]byte("commitment1"), []byte("commitment2")},
Proofs: []hexutil.Bytes{[]byte("proof1"), []byte("proof2")},
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
},
}
parent := bytesutil.PadTo([]byte("parentHash"), fieldparams.RootLength)
sha3Uncles := bytesutil.PadTo([]byte("sha3Uncles"), fieldparams.RootLength)
miner := bytesutil.PadTo([]byte("miner"), fieldparams.FeeRecipientLength)
@@ -1762,10 +1574,8 @@ func fixturesStruct() *payloadFixtures {
ExecutionPayloadCapella: executionPayloadFixtureCapella,
ExecutionPayloadDeneb: executionPayloadFixtureDeneb,
EmptyExecutionPayloadDeneb: emptyExecutionPayloadDeneb,
ExecutionPayloadElectra: executionPayloadFixtureElectra,
ExecutionPayloadWithValueCapella: executionPayloadWithValueFixtureCapella,
ExecutionPayloadWithValueDeneb: executionPayloadWithValueFixtureDeneb,
ExecutionPayloadWithValueElectra: executionPayloadWithValueFixtureElectra,
ValidPayloadStatus: validStatus,
InvalidBlockHashStatus: inValidBlockHashStatus,
AcceptedStatus: acceptedStatus,
@@ -1787,10 +1597,8 @@ type payloadFixtures struct {
ExecutionPayloadCapella *pb.ExecutionPayloadCapella
EmptyExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
ExecutionPayloadDeneb *pb.ExecutionPayloadDeneb
ExecutionPayloadElectra *pb.ExecutionPayloadElectra
ExecutionPayloadWithValueCapella *pb.GetPayloadV2ResponseJson
ExecutionPayloadWithValueDeneb *pb.GetPayloadV3ResponseJson
ExecutionPayloadWithValueElectra *pb.GetPayloadV4ResponseJson
ValidPayloadStatus *pb.PayloadStatus
InvalidBlockHashStatus *pb.PayloadStatus
AcceptedStatus *pb.PayloadStatus
@@ -2149,40 +1957,6 @@ func newPayloadV3Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.Execu
return service
}
func newPayloadV4Setup(t *testing.T, status *pb.PayloadStatus, payload *pb.ExecutionPayloadElectra) *Service {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
defer func() {
require.NoError(t, r.Body.Close())
}()
enc, err := io.ReadAll(r.Body)
require.NoError(t, err)
jsonRequestString := string(enc)
reqArg, err := json.Marshal(payload)
require.NoError(t, err)
// We expect the JSON string RPC request contains the right arguments.
require.Equal(t, true, strings.Contains(
jsonRequestString, string(reqArg),
))
resp := map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"result": status,
}
err = json.NewEncoder(w).Encode(resp)
require.NoError(t, err)
}))
rpcClient, err := rpc.DialHTTP(srv.URL)
require.NoError(t, err)
service := &Service{}
service.rpcClient = rpcClient
return service
}
func TestReconstructBlindedBlockBatch(t *testing.T) {
t.Run("empty response works", func(t *testing.T) {
ctx := context.Background()

View File

@@ -64,12 +64,6 @@ func payloadToBody(t *testing.T, ed interfaces.ExecutionData) *pb.ExecutionPaylo
for i := range txs {
body.Transactions = append(body.Transactions, txs[i])
}
eed, isElectra := ed.(interfaces.ExecutionDataElectra)
if isElectra {
body.DepositRequests = pb.ProtoDepositRequestsToJson(eed.DepositRequests())
body.WithdrawalRequests = pb.ProtoWithdrawalRequestsToJson(eed.WithdrawalRequests())
body.ConsolidationRequests = pb.ProtoConsolidationRequestsToJson(eed.ConsolidationRequests())
}
return body
}
@@ -132,7 +126,7 @@ func testBlindedBlockFixtures(t *testing.T) *blindedBlockFixtures {
afterSkipBlock, _ := util.GenerateTestDenebBlockWithSidecar(t, [32]byte{}, denebSlot(t)+3, 0, util.WithPayloadSetter(afterSkip))
fx.afterSkipDeneb = blindedBlockWithHeader(t, afterSkipBlock)
electra := fixturesStruct().ExecutionPayloadElectra
electra := fixturesStruct().ExecutionPayloadDeneb
electra.BlockHash = bytesutil.PadTo([]byte("electra"), 32)
electra.BlockNumber = 5
electraBlock, _ := util.GenerateTestElectraBlockWithSidecar(t, [32]byte{}, electraSlot(t), 0, util.WithElectraPayload(electra))
@@ -164,6 +158,7 @@ func TestPayloadBodiesViaUnblinder(t *testing.T) {
payload, err := bbr.payloadForHeader(fx.denebBlock.blinded.header, fx.denebBlock.blinded.block.Version())
require.NoError(t, err)
require.Equal(t, version.Deneb, fx.denebBlock.blinded.block.Version())
unblindFull, err := blocks.BuildSignedBeaconBlockFromExecutionPayload(fx.denebBlock.blinded.block, payload)
require.NoError(t, err)
testAssertReconstructedEquivalent(t, fx.denebBlock.full, unblindFull)

View File

@@ -19,6 +19,7 @@ go_library(
"//beacon-chain/state:go_default_library",
"//config/fieldparams:go_default_library",
"//consensus-types/forkchoice:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"@com_github_pkg_errors//:go_default_library",
],

View File

@@ -4,6 +4,7 @@ go_library(
name = "go_default_library",
srcs = [
"doc.go",
"epbs.go",
"errors.go",
"forkchoice.go",
"last_root.go",
@@ -31,6 +32,7 @@ go_library(
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/forkchoice:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//encoding/bytesutil:go_default_library",
"//monitoring/tracing/trace:go_default_library",
@@ -47,6 +49,7 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"epbs_test.go",
"ffg_update_test.go",
"forkchoice_test.go",
"last_root_test.go",
@@ -66,6 +69,7 @@ go_test(
"//beacon-chain/forkchoice/types:go_default_library",
"//beacon-chain/state:go_default_library",
"//beacon-chain/state/state-native:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types/blocks:go_default_library",
"//consensus-types/forkchoice:go_default_library",
@@ -78,5 +82,6 @@ go_test(
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//time/slots:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
],
)

View File

@@ -0,0 +1,80 @@
package doublylinkedtree
import (
"time"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
func (n *Node) isParentFull() bool {
// Finalized checkpoint is considered full
if n.parent == nil || n.parent.parent == nil {
return true
}
return n.parent.payloadHash != [32]byte{}
}
func (f *ForkChoice) GetPTCVote() primitives.PTCStatus {
highestNode := f.store.highestReceivedNode
if highestNode == nil {
return primitives.PAYLOAD_ABSENT
}
if slots.CurrentSlot(f.store.genesisTime) > highestNode.slot {
return primitives.PAYLOAD_ABSENT
}
if highestNode.payloadHash == [32]byte{} {
return primitives.PAYLOAD_ABSENT
}
if highestNode.withheld {
return primitives.PAYLOAD_WITHHELD
}
return primitives.PAYLOAD_PRESENT
}
// InsertPayloadEnvelope adds a full node to forkchoice from the given payload
// envelope.
func (f *ForkChoice) InsertPayloadEnvelope(envelope interfaces.ROExecutionPayloadEnvelope) error {
s := f.store
b, ok := s.nodeByRoot[envelope.BeaconBlockRoot()]
if !ok {
return ErrNilNode
}
e, err := envelope.Execution()
if err != nil {
return err
}
hash := [32]byte(e.BlockHash())
if _, ok = s.nodeByPayload[hash]; ok {
// We ignore nodes with the give payload hash already included
return nil
}
n := &Node{
slot: b.slot,
root: b.root,
payloadHash: hash,
parent: b.parent,
target: b.target,
children: make([]*Node, 0),
justifiedEpoch: b.justifiedEpoch,
unrealizedJustifiedEpoch: b.unrealizedJustifiedEpoch,
finalizedEpoch: b.finalizedEpoch,
unrealizedFinalizedEpoch: b.unrealizedFinalizedEpoch,
timestamp: uint64(time.Now().Unix()),
ptcVote: make([]primitives.PTCStatus, 0),
withheld: envelope.PayloadWithheld(),
optimistic: true,
}
if n.parent != nil {
n.parent.children = append(n.parent.children, n)
}
s.nodeByPayload[hash] = n
processedPayloadCount.Inc()
payloadCount.Set(float64(len(s.nodeByPayload)))
if b.slot == s.highestReceivedNode.slot {
s.highestReceivedNode = n
}
return nil
}

View File

@@ -0,0 +1,213 @@
package doublylinkedtree
import (
"context"
"testing"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestStore_Insert_PayloadContent(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
s := f.store
// The tree root is full
fr := [32]byte{}
n := s.nodeByRoot[fr]
require.Equal(t, true, n.isParentFull())
// Insert a child with a payload
cr := [32]byte{'a'}
cp := [32]byte{'p'}
n, err := s.insert(ctx, 1, cr, fr, cp, fr, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, s.treeRootNode, n.parent)
require.Equal(t, s.nodeByRoot[cr], n)
// Insert a grandchild without a payload
gr := [32]byte{'b'}
gn, err := s.insert(ctx, 2, gr, cr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gn.isParentFull())
require.Equal(t, n, gn.parent)
// Insert the payload of the same grandchild
gp := [32]byte{'q'}
gfn, err := s.insert(ctx, 2, gr, cr, gp, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gfn.isParentFull())
require.Equal(t, n, gfn.parent)
// Insert an empty great grandchild based on empty
ggr := [32]byte{'c'}
ggn, err := s.insert(ctx, 3, ggr, gr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, ggn.isParentFull())
require.Equal(t, gn, ggn.parent)
// Insert an empty great grandchild based on full
ggfr := [32]byte{'d'}
ggfn, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, gfn, ggfn.parent)
require.Equal(t, true, ggfn.isParentFull())
// Insert the payload for the great grandchild based on empty
ggp := [32]byte{'r'}
n, err = s.insert(ctx, 3, ggr, gr, ggp, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, n.isParentFull())
require.Equal(t, gn, n.parent)
// Insert the payload for the great grandchild based on full
ggfp := [32]byte{'s'}
n, err = s.insert(ctx, 3, ggfr, gr, ggfp, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, gfn, n.parent)
// Reinsert an empty node
ggfn2, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, ggfn, ggfn2)
// Reinsert a full node
n2, err := s.insert(ctx, 3, ggfr, gr, ggfp, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, n, n2)
}
func TestStore_Insert_PayloadEnvelope(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
s := f.store
// The tree root is full
fr := [32]byte{}
n := s.nodeByRoot[fr]
require.Equal(t, true, n.isParentFull())
// Insert a child
cr := [32]byte{'a'}
cp := [32]byte{'p'}
n, err := s.insert(ctx, 1, cr, fr, [32]byte{}, fr, 0, 0)
require.NoError(t, err)
require.Equal(t, true, n.isParentFull())
require.Equal(t, s.treeRootNode, n.parent)
require.Equal(t, s.nodeByRoot[cr], n)
// Insert its payload
p := &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: cp[:],
},
BeaconBlockRoot: cr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
np := s.nodeByPayload[cp]
require.Equal(t, np.root, n.root)
require.NotEqual(t, np, n)
// Insert a grandchild without a payload, it's parent is the full node,
// which is not the empty node
gr := [32]byte{'b'}
gn, err := s.insert(ctx, 2, gr, cr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, true, gn.isParentFull())
require.Equal(t, np, gn.parent)
// Insert the payload of the same grandchild
gp := [32]byte{'q'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: gp[:],
},
BeaconBlockRoot: gr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
gfn := s.nodeByPayload[gp]
require.Equal(t, true, gfn.isParentFull())
require.Equal(t, np, gfn.parent)
// Insert an empty great grandchild based on empty
ggr := [32]byte{'c'}
ggn, err := s.insert(ctx, 3, ggr, gr, fr, cp, 0, 0)
require.NoError(t, err)
require.Equal(t, false, ggn.isParentFull())
require.Equal(t, gn, ggn.parent)
// Insert an empty great grandchild based on full
ggfr := [32]byte{'d'}
ggfn, err := s.insert(ctx, 3, ggfr, gr, fr, gp, 0, 0)
require.NoError(t, err)
require.Equal(t, gfn, ggfn.parent)
require.Equal(t, true, ggfn.isParentFull())
// Insert the payload for the great grandchild based on empty
ggp := [32]byte{'r'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: ggp[:],
},
BeaconBlockRoot: ggr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
n = s.nodeByPayload[ggp]
require.Equal(t, false, n.isParentFull())
require.Equal(t, gn, n.parent)
// Insert the payload for the great grandchild based on full
ggfp := [32]byte{'s'}
p = &enginev1.ExecutionPayloadEnvelope{
Payload: &enginev1.ExecutionPayloadElectra{
BlockHash: ggfp[:],
},
BeaconBlockRoot: ggfr[:],
PayloadWithheld: false,
StateRoot: fr[:],
BlobKzgCommitments: make([][]byte, 0),
}
e, err = blocks.WrappedROExecutionPayloadEnvelope(p)
require.NoError(t, err)
require.NoError(t, f.InsertPayloadEnvelope(e))
n = s.nodeByPayload[ggfp]
require.Equal(t, true, n.isParentFull())
require.Equal(t, gfn, n.parent)
}
func TestGetPTCVote(t *testing.T) {
ctx := context.Background()
f := setup(0, 0)
s := f.store
require.NotNil(t, s.highestReceivedNode)
fr := [32]byte{}
// Insert a child with a payload
cr := [32]byte{'a'}
cp := [32]byte{'p'}
n, err := s.insert(ctx, 1, cr, fr, cp, fr, 0, 0)
require.NoError(t, err)
require.Equal(t, n, s.highestReceivedNode)
require.Equal(t, primitives.PAYLOAD_ABSENT, f.GetPTCVote())
driftGenesisTime(f, 1, 0)
require.Equal(t, primitives.PAYLOAD_PRESENT, f.GetPTCVote())
n.withheld = true
require.Equal(t, primitives.PAYLOAD_WITHHELD, f.GetPTCVote())
}

View File

@@ -80,7 +80,7 @@ func (f *ForkChoice) Head(
// ProcessAttestation processes attestation for vote accounting, it iterates around validator indices
// and update their votes accordingly.
func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, targetEpoch primitives.Epoch) {
func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []uint64, blockRoot [32]byte, attSlot primitives.Slot) {
_, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.ProcessAttestation")
defer span.End()
@@ -94,9 +94,9 @@ func (f *ForkChoice) ProcessAttestation(ctx context.Context, validatorIndices []
newVote := f.votes[index].nextRoot == params.BeaconConfig().ZeroHash &&
f.votes[index].currentRoot == params.BeaconConfig().ZeroHash
// Vote gets updated if it's newly allocated or high target epoch.
if newVote || targetEpoch > f.votes[index].nextEpoch {
f.votes[index].nextEpoch = targetEpoch
// Vote gets updated if it's newly allocated or higher attestation slot.
if newVote || attSlot > f.votes[index].slot {
f.votes[index].slot = attSlot
f.votes[index].nextRoot = blockRoot
}
}
@@ -115,8 +115,14 @@ func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, ro
return errNilBlockHeader
}
parentRoot := bytesutil.ToBytes32(bh.ParentRoot)
var payloadHash [32]byte
if state.Version() >= version.Bellatrix {
var payloadHash, parentHash [32]byte
if state.Version() >= version.EPBS {
latestHash, err := state.LatestBlockHash()
if err != nil {
return err
}
copy(parentHash[:], latestHash)
} else if state.Version() >= version.Bellatrix {
ph, err := state.LatestExecutionPayloadHeader()
if err != nil {
return err
@@ -135,7 +141,7 @@ func (f *ForkChoice) InsertNode(ctx context.Context, state state.BeaconState, ro
return errInvalidNilCheckpoint
}
finalizedEpoch := fc.Epoch
node, err := f.store.insert(ctx, slot, root, parentRoot, payloadHash, justifiedEpoch, finalizedEpoch)
node, err := f.store.insert(ctx, slot, root, parentRoot, payloadHash, parentHash, justifiedEpoch, finalizedEpoch)
if err != nil {
return err
}
@@ -170,6 +176,13 @@ func (f *ForkChoice) HasNode(root [32]byte) bool {
return ok
}
// HasHash returns true if the node with the given payload hash exists in fork choice store,
// false else wise.
func (f *ForkChoice) HasHash(hash [32]byte) bool {
_, ok := f.store.nodeByPayload[hash]
return ok
}
// IsCanonical returns true if the given root is part of the canonical chain.
func (f *ForkChoice) IsCanonical(root [32]byte) bool {
node, ok := f.store.nodeByRoot[root]
@@ -490,8 +503,12 @@ func (f *ForkChoice) InsertChain(ctx context.Context, chain []*forkchoicetypes.B
if err != nil {
return err
}
parentHash, err := blocks.GetBlockParentHash(b)
if err != nil {
return err
}
if _, err := f.store.insert(ctx,
b.Slot(), r, parentRoot, payloadHash,
b.Slot(), r, parentRoot, payloadHash, parentHash,
chain[i].JustifiedCheckpoint.Epoch, chain[i].FinalizedCheckpoint.Epoch); err != nil {
return err
}
@@ -691,3 +708,62 @@ func (f *ForkChoice) ParentRoot(root [32]byte) ([32]byte, error) {
}
return n.parent.root, nil
}
// UpdateVotesOnPayloadAttestation processes a new aggregated
// payload attestation message and updates
// the Payload Timeliness Committee (PTC) votes for the corresponding block.
func (s *Store) updateVotesOnPayloadAttestation(
payloadAttestation *ethpb.PayloadAttestation) error {
// Extract the attestation data and convert the beacon block root to a 32-byte array
data := payloadAttestation.Data
blockRoot := bytesutil.ToBytes32(data.BeaconBlockRoot)
// Check if the block exists in the store
node, ok := s.nodeByRoot[blockRoot]
if !ok || node == nil {
return ErrNilNode
}
// Update the PTC votes based on the attestation
// We only set the vote if it hasn't been set before
// to handle potential equivocations
for i := uint64(0); i < fieldparams.PTCSize; i++ {
if payloadAttestation.AggregationBits.BitAt(i) && node.ptcVote[i] == primitives.PAYLOAD_ABSENT {
node.ptcVote[i] = data.PayloadStatus
}
}
return nil
}
// updatePayloadBoosts checks the PTC votes for a given node and updates
// the payload reveal and withhold boost roots if the necessary thresholds are met.
func (s *Store) updatePayloadBoosts(node *Node) {
presentCount := 0
withheldCount := 0
// Count the number of PRESENT and WITHHELD votes
for _, vote := range node.ptcVote {
if vote == primitives.PAYLOAD_PRESENT {
presentCount++
} else if vote == primitives.PAYLOAD_WITHHELD {
withheldCount++
}
}
// If the number of PRESENT votes exceeds the threshold,
// update the payload reveal boost root
if presentCount > int(params.BeaconConfig().PayloadTimelyThreshold) {
s.payloadRevealBoostRoot = node.root
return
}
// If the number of WITHHELD votes exceeds the threshold,
// update the payload reveal boost root
if withheldCount > int(params.BeaconConfig().PayloadTimelyThreshold) {
if node.parent != nil {
s.payloadWithholdBoostRoot = node.parent.root
// A node is considered "full" if it has a non-zero payload hash
s.payloadWithholdBoostFull = node.parent.payloadHash != [32]byte{}
}
}
}

View File

@@ -6,14 +6,17 @@ import (
"testing"
"time"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice"
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/crypto/hash"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
@@ -22,7 +25,8 @@ import (
)
// prepareForkchoiceState prepares a beacon State with the given data to mock
// insert into forkchoice
// insert into forkchoice. This method prepares full states and blocks for
// bellatrix, it cannot be used for ePBS tests.
func prepareForkchoiceState(
_ context.Context,
slot primitives.Slot,
@@ -61,6 +65,14 @@ func prepareForkchoiceState(
return st, blockRoot, err
}
// Helper function to set all bits in a Bitvector512
func setAllBits(bv bitfield.Bitvector512) bitfield.Bitvector512 {
for i := 0; i < len(bv); i++ {
bv[i] = 0xFF
}
return bv
}
func TestForkChoice_UpdateBalancesPositiveChange(t *testing.T) {
f := setup(0, 0)
ctx := context.Background()
@@ -887,3 +899,227 @@ func TestForkchoiceParentRoot(t *testing.T) {
require.NoError(t, err)
require.Equal(t, zeroHash, root)
}
func TestStore_UpdateVotesOnPayloadAttestation(t *testing.T) {
tests := []struct {
name string
setupStore func(*Store)
payloadAttestation *ethpb.PayloadAttestation
wantErr bool
expectedPTCVotes []primitives.PTCStatus
expectedBoosts func(*Store) bool
}{
{
name: "Unknown block root",
setupStore: func(_ *Store) {},
payloadAttestation: &ethpb.PayloadAttestation{
Data: &ethpb.PayloadAttestationData{
BeaconBlockRoot: []byte{1, 2, 3},
},
},
wantErr: true,
},
{
name: "Update PTC votes - all present",
setupStore: func(s *Store) {
root := [32]byte{1, 2, 3}
s.nodeByRoot[root] = &Node{root: root, ptcVote: make([]primitives.PTCStatus, fieldparams.PTCSize)}
},
payloadAttestation: &ethpb.PayloadAttestation{
Data: &ethpb.PayloadAttestationData{
BeaconBlockRoot: []byte{1, 2, 3},
PayloadStatus: primitives.PAYLOAD_PRESENT,
},
AggregationBits: setAllBits(bitfield.NewBitvector512()),
},
expectedPTCVotes: func() []primitives.PTCStatus {
return makeVotes(fieldparams.PTCSize, primitives.PAYLOAD_PRESENT)
}(),
expectedBoosts: noBoosts,
},
{
name: "Update PTC votes - all withheld",
setupStore: func(s *Store) {
root := [32]byte{4, 5, 6}
s.nodeByRoot[root] = &Node{root: root, ptcVote: make([]primitives.PTCStatus, fieldparams.PTCSize)}
},
payloadAttestation: &ethpb.PayloadAttestation{
Data: &ethpb.PayloadAttestationData{
BeaconBlockRoot: []byte{4, 5, 6},
PayloadStatus: primitives.PAYLOAD_WITHHELD,
},
AggregationBits: setAllBits(bitfield.NewBitvector512()),
},
expectedPTCVotes: func() []primitives.PTCStatus {
return makeVotes(fieldparams.PTCSize, primitives.PAYLOAD_WITHHELD)
}(),
expectedBoosts: noBoosts,
},
{
name: "Update PTC votes - partial attestation",
setupStore: func(s *Store) {
root := [32]byte{7, 8, 9}
s.nodeByRoot[root] = &Node{root: root, ptcVote: make([]primitives.PTCStatus, fieldparams.PTCSize)}
},
payloadAttestation: &ethpb.PayloadAttestation{
Data: &ethpb.PayloadAttestationData{
BeaconBlockRoot: []byte{7, 8, 9},
PayloadStatus: primitives.PAYLOAD_PRESENT,
},
AggregationBits: func() bitfield.Bitvector512 {
bits := bitfield.NewBitvector512()
for i := 0; i < fieldparams.PTCSize/2; i++ {
bits.SetBitAt(uint64(i), true)
}
return bits
}(),
},
expectedPTCVotes: func() []primitives.PTCStatus {
votes := make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := 0; i < fieldparams.PTCSize/2; i++ {
votes[i] = primitives.PAYLOAD_PRESENT
}
return votes
}(),
expectedBoosts: noBoosts,
},
{
name: "Update PTC votes - no change for already set votes",
setupStore: func(s *Store) {
root := [32]byte{10, 11, 12}
votes := make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := range votes {
if i%2 == 0 {
votes[i] = primitives.PAYLOAD_PRESENT
}
}
s.nodeByRoot[root] = &Node{root: root, ptcVote: votes}
},
payloadAttestation: &ethpb.PayloadAttestation{
Data: &ethpb.PayloadAttestationData{
BeaconBlockRoot: []byte{10, 11, 12},
PayloadStatus: primitives.PAYLOAD_WITHHELD,
},
AggregationBits: setAllBits(bitfield.NewBitvector512()),
},
expectedPTCVotes: func() []primitives.PTCStatus {
votes := make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := range votes {
if i%2 == 0 {
votes[i] = primitives.PAYLOAD_PRESENT
} else {
votes[i] = primitives.PAYLOAD_WITHHELD
}
}
return votes
}(),
expectedBoosts: noBoosts,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Store{
nodeByRoot: make(map[[32]byte]*Node),
}
tt.setupStore(s)
err := s.updateVotesOnPayloadAttestation(tt.payloadAttestation)
if tt.wantErr {
require.ErrorIs(t, err, ErrNilNode, "Expected ErrNilNode")
} else {
require.NoError(t, err)
node := s.nodeByRoot[bytesutil.ToBytes32(tt.payloadAttestation.Data.BeaconBlockRoot)]
assert.DeepEqual(t, tt.expectedPTCVotes, node.ptcVote, "Unexpected PTC votes")
assert.Equal(t, tt.expectedBoosts(s), true, "Unexpected boost values")
}
})
}
}
func makeVotes(count int, status primitives.PTCStatus) []primitives.PTCStatus {
votes := make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := 0; i < count; i++ {
votes[i] = status
}
return votes
}
func noBoosts(s *Store) bool {
return s.payloadRevealBoostRoot == [32]byte{} &&
s.payloadWithholdBoostRoot == [32]byte{} &&
!s.payloadWithholdBoostFull
}
func TestStore_UpdatePayloadBoosts(t *testing.T) {
tests := []struct {
name string
setupNode func(*Node)
expectedRevealBoost [32]byte
expectedWithholdBoost [32]byte
expectedWithholdFull bool
}{
{
name: "No boost",
setupNode: func(n *Node) {
n.ptcVote = make([]primitives.PTCStatus, fieldparams.PTCSize)
},
},
{
name: "Reveal boost",
setupNode: func(n *Node) {
n.root = [32]byte{1, 2, 3}
n.ptcVote = make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := 0; i < int(params.BeaconConfig().PayloadTimelyThreshold)+1; i++ {
n.ptcVote[i] = primitives.PAYLOAD_PRESENT
}
},
expectedRevealBoost: [32]byte{1, 2, 3},
},
{
name: "Withhold boost",
setupNode: func(n *Node) {
n.ptcVote = make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := 0; i < int(params.BeaconConfig().PayloadTimelyThreshold)+1; i++ {
n.ptcVote[i] = primitives.PAYLOAD_WITHHELD
}
n.parent = &Node{root: [32]byte{4, 5, 6}, payloadHash: [32]byte{7, 8, 9}}
},
expectedWithholdBoost: [32]byte{4, 5, 6},
expectedWithholdFull: true,
},
{
name: "Reveal boost with early return",
setupNode: func(n *Node) {
n.root = [32]byte{1, 2, 3}
n.ptcVote = make([]primitives.PTCStatus, fieldparams.PTCSize)
for i := 0; i < int(params.BeaconConfig().PayloadTimelyThreshold)+1; i++ {
n.ptcVote[i] = primitives.PAYLOAD_PRESENT
}
// Set up conditions for withhold boost, which should not be applied due to early return
n.parent = &Node{root: [32]byte{4, 5, 6}, payloadHash: [32]byte{7, 8, 9}}
for i := int(params.BeaconConfig().PayloadTimelyThreshold) + 1; i < fieldparams.PTCSize; i++ {
n.ptcVote[i] = primitives.PAYLOAD_WITHHELD
}
},
expectedRevealBoost: [32]byte{1, 2, 3},
expectedWithholdBoost: [32]byte{}, // Should remain zero due to early return
expectedWithholdFull: false, // Should remain false due to early return
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &Store{}
n := &Node{}
tt.setupNode(n)
s.updatePayloadBoosts(n)
assert.Equal(t, tt.expectedRevealBoost, s.payloadRevealBoostRoot, "Unexpected reveal boost root")
assert.Equal(t, tt.expectedWithholdBoost, s.payloadWithholdBoostRoot, "Unexpected withhold boost root")
assert.Equal(t, tt.expectedWithholdFull, s.payloadWithholdBoostFull, "Unexpected withhold full status")
})
}
}

View File

@@ -18,7 +18,13 @@ var (
nodeCount = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "doublylinkedtree_node_count",
Help: "The number of nodes in the doubly linked tree based store structure.",
Help: "The number of nodes for blocks in the doubly linked tree based store structure.",
},
)
payloadCount = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "doublylinkedtree_payload_count",
Help: "The number of nodes for execution payloads in the doubly linked tree based store structure.",
},
)
headChangesCount = promauto.NewCounter(
@@ -39,6 +45,12 @@ var (
Help: "The number of times a block is processed for fork choice.",
},
)
processedPayloadCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "doublylinkedtree_payload_processed_count",
Help: "The number of times an execution payload is processed for fork choice.",
},
)
processedAttestationCount = promauto.NewCounter(
prometheus.CounterOpts{
Name: "doublylinkedtree_attestation_processed_count",

View File

@@ -9,6 +9,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/time/slots"
)
// Helper function to simulate the block being on time or delayed for proposer
@@ -61,7 +62,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
f.ProcessAttestation(ctx, []uint64{0}, newRoot, fEpoch)
fSlot, err := slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, []uint64{0}, newRoot, fSlot)
headRoot, err = f.Head(ctx)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 1")
@@ -87,7 +90,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
f.ProcessAttestation(ctx, []uint64{1}, newRoot, fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, []uint64{1}, newRoot, fSlot)
headRoot, err = f.Head(ctx)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 2")
@@ -115,7 +120,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
f.ProcessAttestation(ctx, []uint64{2}, newRoot, fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, []uint64{2}, newRoot, fSlot)
headRoot, err = f.Head(ctx)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
@@ -144,7 +151,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fSlot)
headRoot, err = f.Head(ctx)
require.NoError(t, err)
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
@@ -174,7 +183,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// Regression: process attestations for C, check that it
// becomes head, we need two attestations to have C.weight = 30 > 24 = D.weight
f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), fSlot)
headRoot, err = f.Head(ctx)
require.NoError(t, err)
assert.Equal(t, indexToHash(3), headRoot, "Incorrect head for justified epoch at slot 4")
@@ -235,10 +246,14 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// The maliciously withheld block has one vote.
votes := []uint64{1}
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
fSlot, err := slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fSlot)
// The honest block has one vote.
votes = []uint64{2}
f.ProcessAttestation(ctx, votes, honestBlock, fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, votes, honestBlock, fSlot)
// Ensure the head is STILL C, the honest block, as the honest block had proposer boost.
r, err = f.Head(ctx)
@@ -304,7 +319,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// An attestation is received for B that has more voting power than C with the proposer boost,
// allowing B to then become the head if their attestation has enough adversarial votes.
votes := []uint64{1, 2}
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
fSlot, err := slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fSlot)
// Expect the head to have switched to B.
r, err = f.Head(ctx)
@@ -379,7 +396,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
// An attestation for C is received at slot N+3.
votes := []uint64{1}
f.ProcessAttestation(ctx, votes, c, fEpoch)
fSlot, err := slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, votes, c, fSlot)
// A block D, building on B, is received at slot N+3. It should not be able to win without boosting.
dSlot := primitives.Slot(3)
@@ -419,7 +438,9 @@ func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
votes = []uint64{2}
f.ProcessAttestation(ctx, votes, d2, fEpoch)
fSlot, err = slots.EpochStart(fEpoch)
require.NoError(t, err)
f.ProcessAttestation(ctx, votes, d2, fSlot)
// Ensure D becomes the head thanks to boosting.
r, err = f.Head(ctx)
require.NoError(t, err)

View File

@@ -62,20 +62,32 @@ func (s *Store) head(ctx context.Context) ([32]byte, error) {
// insert registers a new block node to the fork choice store's node list.
// It then updates the new node's parent with the best child and descendant node.
func (s *Store) insert(ctx context.Context,
func (s *Store) insert(
ctx context.Context,
slot primitives.Slot,
root, parentRoot, payloadHash [fieldparams.RootLength]byte,
justifiedEpoch, finalizedEpoch primitives.Epoch) (*Node, error) {
root, parentRoot, payloadHash, parentHash [fieldparams.RootLength]byte,
justifiedEpoch, finalizedEpoch primitives.Epoch,
) (*Node, error) {
ctx, span := trace.StartSpan(ctx, "doublyLinkedForkchoice.insert")
defer span.End()
// Return if the block has been inserted into Store before.
if n, ok := s.nodeByRoot[root]; ok {
return n, nil
n, rootPresent := s.nodeByRoot[root]
m, hashPresent := s.nodeByPayload[payloadHash]
if rootPresent {
if payloadHash == [32]byte{} {
return n, nil
}
if hashPresent {
return m, nil
}
}
parent := s.nodeByRoot[parentRoot]
n := &Node{
fullParent := s.nodeByPayload[parentHash]
if fullParent != nil && parent != nil && fullParent.root == parent.root {
parent = fullParent
}
n = &Node{
slot: slot,
root: root,
parent: parent,
@@ -99,17 +111,22 @@ func (s *Store) insert(ctx context.Context,
}
}
s.nodeByPayload[payloadHash] = n
s.nodeByRoot[root] = n
if parent == nil {
if s.treeRootNode == nil {
s.treeRootNode = n
s.headNode = n
s.highestReceivedNode = n
} else {
} else if s.treeRootNode.root != n.root {
return n, errInvalidParentRoot
}
} else {
}
if !rootPresent {
s.nodeByRoot[root] = n
}
if !hashPresent {
s.nodeByPayload[payloadHash] = n
}
if parent != nil {
parent.children = append(parent.children, n)
// Apply proposer boost
timeNow := uint64(time.Now().Unix())
@@ -232,12 +249,12 @@ func (s *Store) tips() ([][32]byte, []primitives.Slot) {
return roots, slots
}
// HighestReceivedBlockSlot returns the highest slot received by the forkchoice
func (f *ForkChoice) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot returns the highest slot and root received by the forkchoice
func (f *ForkChoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
if f.store.highestReceivedNode == nil {
return 0
return 0, [32]byte{}
}
return f.store.highestReceivedNode.slot
return f.store.highestReceivedNode.slot, f.store.highestReceivedNode.root
}
// HighestReceivedBlockSlotDelay returns the number of slots that the highest

View File

@@ -133,7 +133,7 @@ func TestStore_Insert(t *testing.T) {
fc := &forkchoicetypes.Checkpoint{Epoch: 0}
s := &Store{nodeByRoot: nodeByRoot, treeRootNode: treeRootNode, nodeByPayload: nodeByPayload, justifiedCheckpoint: jc, finalizedCheckpoint: fc, highestReceivedNode: &Node{}}
payloadHash := [32]byte{'a'}
_, err := s.insert(context.Background(), 100, indexToHash(100), indexToHash(0), payloadHash, 1, 1)
_, err := s.insert(context.Background(), 100, indexToHash(100), indexToHash(0), payloadHash, payloadHash, 1, 1)
require.NoError(t, err)
assert.Equal(t, 2, len(s.nodeByRoot), "Did not insert block")
assert.Equal(t, (*Node)(nil), treeRootNode.parent, "Incorrect parent")
@@ -327,107 +327,116 @@ func TestForkChoice_ReceivedBlocksLastEpoch(t *testing.T) {
// Make sure it doesn't underflow
s.genesisTime = uint64(time.Now().Add(time.Duration(-1*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
_, err := s.insert(context.Background(), 1, [32]byte{'a'}, b, b, 1, 1)
_, err := s.insert(context.Background(), 1, [32]byte{'a'}, b, b, b, 1, 1)
require.NoError(t, err)
count, err := f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockSlot())
slot, _ := f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(1), slot)
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())
// 64
// Received block last epoch is 1
_, err = s.insert(context.Background(), 64, [32]byte{'A'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 64, [32]byte{'A'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration((-64*int64(params.BeaconConfig().SecondsPerSlot))-1) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(64), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(64), slot)
require.Equal(t, primitives.Slot(0), f.HighestReceivedBlockDelay())
// 64 65
// Received block last epoch is 2
_, err = s.insert(context.Background(), 65, [32]byte{'B'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 65, [32]byte{'B'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-66*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(2), count)
require.Equal(t, primitives.Slot(65), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(65), slot)
require.Equal(t, primitives.Slot(1), f.HighestReceivedBlockDelay())
// 64 65 66
// Received block last epoch is 3
_, err = s.insert(context.Background(), 66, [32]byte{'C'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 66, [32]byte{'C'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-66*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(3), count)
require.Equal(t, primitives.Slot(66), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(66), slot)
// 64 65 66
// 98
// Received block last epoch is 1
_, err = s.insert(context.Background(), 98, [32]byte{'D'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 98, [32]byte{'D'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-98*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(98), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(98), slot)
// 64 65 66
// 98
// 132
// Received block last epoch is 1
_, err = s.insert(context.Background(), 132, [32]byte{'E'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 132, [32]byte{'E'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)
// 64 65 66
// 98
// 132
// 99
// Received block last epoch is still 1. 99 is outside the window
_, err = s.insert(context.Background(), 99, [32]byte{'F'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 99, [32]byte{'F'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)
// 64 65 66
// 98
// 132
// 99 100
// Received block last epoch is still 1. 100 is at the same position as 132
_, err = s.insert(context.Background(), 100, [32]byte{'G'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 100, [32]byte{'G'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)
// 64 65 66
// 98
// 132
// 99 100 101
// Received block last epoch is 2. 101 is within the window
_, err = s.insert(context.Background(), 101, [32]byte{'H'}, b, b, 1, 1)
_, err = s.insert(context.Background(), 101, [32]byte{'H'}, b, b, b, 1, 1)
require.NoError(t, err)
s.genesisTime = uint64(time.Now().Add(time.Duration(-132*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()
require.NoError(t, err)
require.Equal(t, uint64(2), count)
require.Equal(t, primitives.Slot(132), f.HighestReceivedBlockSlot())
slot, _ = f.HighestReceivedBlockSlotRoot()
require.Equal(t, primitives.Slot(132), slot)
s.genesisTime = uint64(time.Now().Add(time.Duration(-134*int64(params.BeaconConfig().SecondsPerSlot)) * time.Second).Unix())
count, err = f.ReceivedBlocksLastEpoch()

View File

@@ -41,6 +41,9 @@ type Store struct {
highestReceivedNode *Node // The highest slot node.
receivedBlocksLastEpoch [fieldparams.SlotsPerEpoch]primitives.Slot // Using `highestReceivedSlot`. The slot of blocks received in the last epoch.
allTipsAreInvalid bool // tracks if all tips are not viable for head
payloadWithholdBoostRoot [fieldparams.RootLength]byte // the root of the block that receives the withhold boost
payloadWithholdBoostFull bool // Indicator of whether the block receiving the withhold boost is full or empty
payloadRevealBoostRoot [fieldparams.RootLength]byte // the root of the block that receives the reveal boost
}
// Node defines the individual block which includes its block parent, ancestor and how much weight accounted for it.
@@ -59,13 +62,15 @@ type Node struct {
balance uint64 // the balance that voted for this node directly
weight uint64 // weight of this node: the total balance including children
bestDescendant *Node // bestDescendant node of this node.
optimistic bool // whether the block has been fully validated or not
timestamp uint64 // The timestamp when the node was inserted.
ptcVote []primitives.PTCStatus // tracks the Payload Timeliness Committee (PTC) votes for the node
withheld bool // whether the builder sent a withheld message for this payload
optimistic bool // whether the block has been fully validated or not
}
// Vote defines an individual validator's vote.
type Vote struct {
currentRoot [fieldparams.RootLength]byte // current voting root.
nextRoot [fieldparams.RootLength]byte // next voting root.
nextEpoch primitives.Epoch // epoch of next voting period.
slot primitives.Slot // slot of the last vote by this validator
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
forkchoice2 "github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
)
@@ -21,6 +22,7 @@ type ForkChoicer interface {
Unlock()
HeadRetriever // to compute head.
BlockProcessor // to track new block for fork choice.
PayloadProcessor // to track new execution payload envelopes for forkchoice
AttestationProcessor // to track new attestation for fork choice.
Getter // to retrieve fork choice information.
Setter // to set fork choice information.
@@ -45,9 +47,13 @@ type BlockProcessor interface {
InsertChain(context.Context, []*forkchoicetypes.BlockAndCheckpoints) error
}
type PayloadProcessor interface {
InsertPayloadEnvelope(interfaces.ROExecutionPayloadEnvelope) error
}
// AttestationProcessor processes the attestation that's used for accounting fork choice.
type AttestationProcessor interface {
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Epoch)
ProcessAttestation(context.Context, []uint64, [32]byte, primitives.Slot)
}
// Getter returns fork choice related information.
@@ -63,7 +69,8 @@ type FastGetter interface {
FinalizedCheckpoint() *forkchoicetypes.Checkpoint
FinalizedPayloadBlockHash() [32]byte
HasNode([32]byte) bool
HighestReceivedBlockSlot() primitives.Slot
HasHash([32]byte) bool
HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte)
HighestReceivedBlockDelay() primitives.Slot
IsCanonical(root [32]byte) bool
IsOptimistic(root [32]byte) (bool, error)
@@ -81,6 +88,7 @@ type FastGetter interface {
UnrealizedJustifiedPayloadBlockHash() [32]byte
Weight(root [32]byte) (uint64, error)
ParentRoot(root [32]byte) ([32]byte, error)
GetPTCVote() primitives.PTCStatus
}
// Setter allows to set forkchoice information

View File

@@ -37,6 +37,13 @@ func (ro *ROForkChoice) HasNode(root [32]byte) bool {
return ro.getter.HasNode(root)
}
// HasHash delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HasHash(root [32]byte) bool {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.HasHash(root)
}
// ProposerBoost delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) ProposerBoost() [fieldparams.RootLength]byte {
ro.l.RLock()
@@ -107,11 +114,11 @@ func (ro *ROForkChoice) NodeCount() int {
return ro.getter.NodeCount()
}
// HighestReceivedBlockSlot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HighestReceivedBlockSlot() primitives.Slot {
// HighestReceivedBlockSlotRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.HighestReceivedBlockSlot()
return ro.getter.HighestReceivedBlockSlotRoot()
}
// HighestReceivedBlockDelay delegates to the underlying forkchoice call, under a lock.
@@ -176,3 +183,10 @@ func (ro *ROForkChoice) ParentRoot(root [32]byte) ([32]byte, error) {
defer ro.l.RUnlock()
return ro.getter.ParentRoot(root)
}
// GetPTCVote delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) GetPTCVote() primitives.PTCStatus {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.GetPTCVote()
}

View File

@@ -18,6 +18,7 @@ const (
rlockCalled
runlockCalled
hasNodeCalled
hasHashCalled
proposerBoostCalled
isCanonicalCalled
finalizedCheckpointCalled
@@ -28,7 +29,7 @@ const (
justifiedPayloadBlockHashCalled
unrealizedJustifiedPayloadBlockHashCalled
nodeCountCalled
highestReceivedBlockSlotCalled
highestReceivedBlockSlotRootCalled
highestReceivedBlockDelayCalled
receivedBlocksLastEpochCalled
weightCalled
@@ -38,6 +39,7 @@ const (
lastRootCalled
targetRootForEpochCalled
parentRootCalled
getPTCVoteCalled
)
func _discard(t *testing.T, e error) {
@@ -111,9 +113,9 @@ func TestROLocking(t *testing.T) {
cb: func(g FastGetter) { g.NodeCount() },
},
{
name: "highestReceivedBlockSlotCalled",
call: highestReceivedBlockSlotCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockSlot() },
name: "highestReceivedBlockSlotRootCalled",
call: highestReceivedBlockSlotRootCalled,
cb: func(g FastGetter) { g.HighestReceivedBlockSlotRoot() },
},
{
name: "highestReceivedBlockDelayCalled",
@@ -197,6 +199,11 @@ func (ro *mockROForkchoice) HasNode(_ [32]byte) bool {
return false
}
func (ro *mockROForkchoice) HasHash(_ [32]byte) bool {
ro.calls = append(ro.calls, hasHashCalled)
return false
}
func (ro *mockROForkchoice) ProposerBoost() [fieldparams.RootLength]byte {
ro.calls = append(ro.calls, proposerBoostCalled)
return [fieldparams.RootLength]byte{}
@@ -247,9 +254,9 @@ func (ro *mockROForkchoice) NodeCount() int {
return 0
}
func (ro *mockROForkchoice) HighestReceivedBlockSlot() primitives.Slot {
ro.calls = append(ro.calls, highestReceivedBlockSlotCalled)
return 0
func (ro *mockROForkchoice) HighestReceivedBlockSlotRoot() (primitives.Slot, [32]byte) {
ro.calls = append(ro.calls, highestReceivedBlockSlotRootCalled)
return 0, [32]byte{}
}
func (ro *mockROForkchoice) HighestReceivedBlockDelay() primitives.Slot {
@@ -297,3 +304,8 @@ func (ro *mockROForkchoice) ParentRoot(_ [32]byte) ([32]byte, error) {
ro.calls = append(ro.calls, parentRootCalled)
return [32]byte{}, nil
}
func (ro *mockROForkchoice) GetPTCVote() primitives.PTCStatus {
ro.calls = append(ro.calls, getPTCVoteCalled)
return primitives.PAYLOAD_ABSENT
}

View File

@@ -101,6 +101,9 @@ type BeaconNode struct {
blsToExecPool blstoexec.PoolManager
depositCache cache.DepositCache
trackedValidatorsCache *cache.TrackedValidatorsCache
payloadAttestationCache *cache.PayloadAttestationCache
payloadEnvelopeCache *sync.Map
executionHeaderCache *cache.ExecutionPayloadHeaders
payloadIDCache *cache.PayloadIDCache
stateFeed *event.Feed
blockFeed *event.Feed
@@ -151,6 +154,9 @@ func New(cliCtx *cli.Context, cancel context.CancelFunc, opts ...Option) (*Beaco
syncCommitteePool: synccommittee.NewPool(),
blsToExecPool: blstoexec.NewPool(),
trackedValidatorsCache: cache.NewTrackedValidatorsCache(),
payloadAttestationCache: &cache.PayloadAttestationCache{},
payloadEnvelopeCache: &sync.Map{},
executionHeaderCache: cache.NewExecutionPayloadHeaders(),
payloadIDCache: cache.NewPayloadIDCache(),
slasherBlockHeadersFeed: new(event.Feed),
slasherAttestationsFeed: new(event.Feed),
@@ -761,6 +767,8 @@ func (b *BeaconNode) registerBlockchainService(fc forkchoice.ForkChoicer, gs *st
blockchain.WithSyncComplete(syncComplete),
blockchain.WithBlobStorage(b.BlobStorage),
blockchain.WithTrackedValidatorsCache(b.trackedValidatorsCache),
blockchain.WithPayloadAttestationCache(b.payloadAttestationCache),
blockchain.WithPayloadEnvelopeCache(b.payloadEnvelopeCache),
blockchain.WithPayloadIDCache(b.payloadIDCache),
blockchain.WithSyncChecker(b.syncChecker),
)
@@ -838,6 +846,9 @@ func (b *BeaconNode) registerSyncService(initialSyncComplete chan struct{}, bFil
regularsync.WithStateGen(b.stateGen),
regularsync.WithSlasherAttestationsFeed(b.slasherAttestationsFeed),
regularsync.WithSlasherBlockHeadersFeed(b.slasherBlockHeadersFeed),
regularsync.WithPayloadAttestationCache(b.payloadAttestationCache),
regularsync.WithExecutionPayloadHeaderCache(b.executionHeaderCache),
regularsync.WithPayloadEnvelopeCache(b.payloadEnvelopeCache),
regularsync.WithPayloadReconstructor(web3Service),
regularsync.WithClockWaiter(b.clockWaiter),
regularsync.WithInitialSyncComplete(initialSyncComplete),
@@ -974,6 +985,8 @@ func (b *BeaconNode) registerRPCService(router *http.ServeMux) error {
FinalizationFetcher: chainService,
BlockReceiver: chainService,
BlobReceiver: chainService,
PayloadAttestationReceiver: chainService,
ExecutionPayloadReceiver: chainService,
AttestationReceiver: chainService,
GenesisTimeFetcher: chainService,
GenesisFetcher: chainService,

View File

@@ -30,6 +30,7 @@ go_library(
"service.go",
"subnets.go",
"topics.go",
"topics_epbs.go",
"utils.go",
"watch_peers.go",
],
@@ -69,6 +70,7 @@ go_library(
"//monitoring/tracing/trace:go_default_library",
"//network:go_default_library",
"//network/forks:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/metadata:go_default_library",
"//runtime:go_default_library",
@@ -159,6 +161,7 @@ go_test(
"//encoding/bytesutil:go_default_library",
"//network:go_default_library",
"//network/forks:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/eth/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/testing:go_default_library",
@@ -166,6 +169,7 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"//testing/util:go_default_library",
"//testing/util/random:go_default_library",
"//time:go_default_library",
"//time/slots:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
@@ -179,16 +183,16 @@ go_test(
"@com_github_libp2p_go_libp2p//core/network:go_default_library",
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
"@com_github_libp2p_go_libp2p//core/protocol:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/host/blank:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/net/swarm/testing:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/security/noise:go_default_library",
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
"@com_github_libp2p_go_libp2p_pubsub//pb:go_default_library",
"@com_github_multiformats_go_multiaddr//:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@com_github_sirupsen_logrus//hooks/test:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
"@org_golang_google_protobuf//reflect/protoreflect:go_default_library",
],
)

View File

@@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/p2p/discover"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/libp2p/go-libp2p/core/host"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/go-bitfield"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/peers"
@@ -25,7 +26,9 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/prysmaticlabs/prysm/v5/testing/util"
"github.com/prysmaticlabs/prysm/v5/testing/util/random"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
)
func TestService_Broadcast(t *testing.T) {
@@ -520,3 +523,80 @@ func TestService_BroadcastBlob(t *testing.T) {
require.NoError(t, p.BroadcastBlob(ctx, subnet, blobSidecar))
require.Equal(t, false, util.WaitTimeout(&wg, 1*time.Second), "Failed to receive pubsub within 1s")
}
func TestService_BroadcastExecutionPayloadHeader(t *testing.T) {
msg := random.SignedExecutionPayloadHeader(t)
testBroadcast(t, SignedExecutionPayloadHeaderTopicFormat, msg)
}
func TestService_BroadcastExecutionPayloadEnvelope(t *testing.T) {
msg := random.SignedExecutionPayloadEnvelope(t)
testBroadcast(t, SignedExecutionPayloadEnvelopeTopicFormat, msg)
}
func TestService_BroadcastPayloadAttestationMessage(t *testing.T) {
msg := random.PayloadAttestationMessage(t)
testBroadcast(t, PayloadAttestationMessageTopicFormat, msg)
}
func testBroadcast(t *testing.T, topicFormat string, msg interface{}) {
// Create two peers and let them connect.
p1 := p2ptest.NewTestP2P(t)
p2 := p2ptest.NewTestP2P(t)
p1.Connect(p2)
if len(p1.BHost.Network().Peers()) == 0 {
t.Fatal("No peers")
}
// Create a `Service` for the first peer.
s1 := &Service{
host: p1.BHost,
pubsub: p1.PubSub(),
joinedTopics: map[string]*pubsub.Topic{},
cfg: &Config{},
genesisTime: time.Now(),
genesisValidatorsRoot: bytesutil.PadTo([]byte{'A'}, 32),
peers: peers.NewStatus(context.Background(), &peers.StatusConfig{
ScorerParams: &scorers.Config{},
}),
}
// The second peer subscribes to the topic.
digest, err := s1.currentForkDigest()
require.NoError(t, err)
topic := fmt.Sprintf(topicFormat, digest) + s1.Encoding().ProtocolSuffix()
subscription, err := p2.SubscribeToTopic(topic)
require.NoError(t, err)
time.Sleep(50 * time.Millisecond) // Wait for libp2p to be set up.
// Start a goroutine listening for a pubsub message.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
incomingMessage, err := subscription.Next(ctx)
require.NoError(t, err)
result, ok := msg.(ssz.Unmarshaler)
require.Equal(t, true, ok)
require.NoError(t, s1.Encoding().DecodeGossip(incomingMessage.Data, result))
require.DeepEqual(t, result, msg)
}()
// An attempt to broadcast a message unmapped to a topic should fail.
ctx := context.Background()
require.ErrorContains(t, "message type is not mapped to a PubSub topic", s1.Broadcast(ctx, nil))
// The first peer broadcasts the message to the second peer.
require.NoError(t, s1.Broadcast(ctx, msg.(protoreflect.ProtoMessage)))
// Wait for one second for the message to be delivered and processed.
if util.WaitTimeout(&wg, 1*time.Second) {
t.Error("Failed to receive pubsub within 1s")
}
}

View File

@@ -5,8 +5,8 @@ import (
"fmt"
"testing"
bh "github.com/libp2p/go-libp2p/p2p/host/blank"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p/core/network"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
@@ -29,8 +29,10 @@ func TestDialRelayNode_InvalidPeerString(t *testing.T) {
func TestDialRelayNode_OK(t *testing.T) {
ctx := context.Background()
relay := bh.NewBlankHost(swarmt.GenSwarm(t))
host := bh.NewBlankHost(swarmt.GenSwarm(t))
relay, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(t, err)
host, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(t, err)
relayAddr := fmt.Sprintf("%s/p2p/%s", relay.Addrs()[0], relay.ID().String())
assert.NoError(t, dialRelayNode(ctx, host, relayAddr), "Unexpected error when dialing relay node")

View File

@@ -5,6 +5,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"google.golang.org/protobuf/proto"
)
@@ -22,6 +23,9 @@ var gossipTopicMappings = map[string]func() proto.Message{
SyncCommitteeSubnetTopicFormat: func() proto.Message { return &ethpb.SyncCommitteeMessage{} },
BlsToExecutionChangeSubnetTopicFormat: func() proto.Message { return &ethpb.SignedBLSToExecutionChange{} },
BlobSubnetTopicFormat: func() proto.Message { return &ethpb.BlobSidecar{} },
SignedExecutionPayloadHeaderTopicFormat: func() proto.Message { return &enginev1.SignedExecutionPayloadHeader{} },
SignedExecutionPayloadEnvelopeTopicFormat: func() proto.Message { return &enginev1.SignedExecutionPayloadEnvelope{} },
PayloadAttestationMessageTopicFormat: func() proto.Message { return &ethpb.PayloadAttestationMessage{} },
}
// GossipTopicMappings is a function to return the assigned data type
@@ -104,4 +108,8 @@ func init() {
GossipTypeMapping[reflect.TypeOf(&ethpb.AttestationElectra{})] = AttestationSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.AttesterSlashingElectra{})] = AttesterSlashingSubnetTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.SignedAggregateAttestationAndProofElectra{})] = AggregateAndProofSubnetTopicFormat
// Handle ePBS objects.
GossipTypeMapping[reflect.TypeOf(&enginev1.SignedExecutionPayloadHeader{})] = SignedExecutionPayloadHeaderTopicFormat
GossipTypeMapping[reflect.TypeOf(&enginev1.SignedExecutionPayloadEnvelope{})] = SignedExecutionPayloadEnvelopeTopicFormat
GossipTypeMapping[reflect.TypeOf(&ethpb.PayloadAttestationMessage{})] = PayloadAttestationMessageTopicFormat
}

View File

@@ -7,8 +7,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/config/params"
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/testing/assert"
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
func TestMappingHasNoDuplicates(t *testing.T) {
@@ -30,17 +32,20 @@ func TestGossipTopicMappings_CorrectType(t *testing.T) {
capellaForkEpoch := primitives.Epoch(300)
denebForkEpoch := primitives.Epoch(400)
electraForkEpoch := primitives.Epoch(500)
epbsForkEpoch := primitives.Epoch(600)
bCfg.AltairForkEpoch = altairForkEpoch
bCfg.BellatrixForkEpoch = bellatrixForkEpoch
bCfg.CapellaForkEpoch = capellaForkEpoch
bCfg.DenebForkEpoch = denebForkEpoch
bCfg.ElectraForkEpoch = electraForkEpoch
bCfg.EPBSForkEpoch = epbsForkEpoch
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.AltairForkVersion)] = primitives.Epoch(100)
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.BellatrixForkVersion)] = primitives.Epoch(200)
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.CapellaForkVersion)] = primitives.Epoch(300)
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.DenebForkVersion)] = primitives.Epoch(400)
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.ElectraForkVersion)] = primitives.Epoch(500)
bCfg.ForkVersionSchedule[bytesutil.ToBytes4(bCfg.EPBSForkVersion)] = primitives.Epoch(600)
params.OverrideBeaconConfig(bCfg)
// Phase 0
@@ -126,4 +131,15 @@ func TestGossipTopicMappings_CorrectType(t *testing.T) {
pMessage = GossipTopicMappings(AggregateAndProofSubnetTopicFormat, electraForkEpoch)
_, ok = pMessage.(*ethpb.SignedAggregateAttestationAndProofElectra)
assert.Equal(t, true, ok)
// Epbs fork
pMessage = GossipTopicMappings(SignedExecutionPayloadHeaderTopicFormat, epbsForkEpoch)
_, ok = pMessage.(*enginev1.SignedExecutionPayloadHeader)
require.Equal(t, true, ok)
pMessage = GossipTopicMappings(SignedExecutionPayloadEnvelopeTopicFormat, epbsForkEpoch)
_, ok = pMessage.(*enginev1.SignedExecutionPayloadEnvelope)
require.Equal(t, true, ok)
pMessage = GossipTopicMappings(PayloadAttestationMessageTopicFormat, epbsForkEpoch)
_, ok = pMessage.(*ethpb.PayloadAttestationMessage)
require.Equal(t, true, ok)
}

View File

@@ -22,9 +22,11 @@ go_library(
"//beacon-chain/p2p/peers/scorers:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
"//proto/prysm/v1alpha1/metadata:go_default_library",
"//testing/require:go_default_library",
"@com_github_ethereum_go_ethereum//crypto:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enode:go_default_library",
"@com_github_ethereum_go_ethereum//p2p/enr:go_default_library",
"@com_github_libp2p_go_libp2p//:go_default_library",
"@com_github_libp2p_go_libp2p//core:go_default_library",
"@com_github_libp2p_go_libp2p//core/connmgr:go_default_library",
"@com_github_libp2p_go_libp2p//core/control:go_default_library",
@@ -34,8 +36,7 @@ go_library(
"@com_github_libp2p_go_libp2p//core/peer:go_default_library",
"@com_github_libp2p_go_libp2p//core/peerstore:go_default_library",
"@com_github_libp2p_go_libp2p//core/protocol:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/host/blank:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/net/swarm/testing:go_default_library",
"@com_github_libp2p_go_libp2p//p2p/transport/tcp:go_default_library",
"@com_github_libp2p_go_libp2p_pubsub//:go_default_library",
"@com_github_multiformats_go_multiaddr//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/p2p/enr"
"github.com/libp2p/go-libp2p"
pubsub "github.com/libp2p/go-libp2p-pubsub"
core "github.com/libp2p/go-libp2p/core"
"github.com/libp2p/go-libp2p/core/control"
@@ -18,8 +19,7 @@ import (
"github.com/libp2p/go-libp2p/core/network"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/protocol"
bhost "github.com/libp2p/go-libp2p/p2p/host/blank"
swarmt "github.com/libp2p/go-libp2p/p2p/net/swarm/testing"
"github.com/libp2p/go-libp2p/p2p/transport/tcp"
"github.com/multiformats/go-multiaddr"
ssz "github.com/prysmaticlabs/fastssz"
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/encoder"
@@ -27,6 +27,7 @@ import (
"github.com/prysmaticlabs/prysm/v5/beacon-chain/p2p/peers/scorers"
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1/metadata"
"github.com/prysmaticlabs/prysm/v5/testing/require"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
)
@@ -52,7 +53,8 @@ type TestP2P struct {
// NewTestP2P initializes a new p2p test service.
func NewTestP2P(t *testing.T) *TestP2P {
ctx := context.Background()
h := bhost.NewBlankHost(swarmt.GenSwarm(t, swarmt.OptDisableQUIC))
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}), libp2p.Transport(tcp.NewTCPTransport), libp2p.DefaultListenAddrs)
require.NoError(t, err)
ps, err := pubsub.NewFloodSub(ctx, h,
pubsub.WithMessageSigning(false),
pubsub.WithStrictSignatureVerification(false),
@@ -86,13 +88,17 @@ func (p *TestP2P) Connect(b *TestP2P) {
}
func connect(a, b host.Host) error {
pinfo := b.Peerstore().PeerInfo(b.ID())
pinfo := peer.AddrInfo{
ID: b.ID(),
Addrs: b.Addrs(),
}
return a.Connect(context.Background(), pinfo)
}
// ReceiveRPC simulates an incoming RPC.
func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t))
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(p.t, err)
if err := connect(h, p.BHost); err != nil {
p.t.Fatalf("Failed to connect two peers for RPC: %v", err)
}
@@ -122,7 +128,8 @@ func (p *TestP2P) ReceiveRPC(topic string, msg proto.Message) {
// ReceivePubSub simulates an incoming message over pubsub on a given topic.
func (p *TestP2P) ReceivePubSub(topic string, msg proto.Message) {
h := bhost.NewBlankHost(swarmt.GenSwarm(p.t))
h, err := libp2p.New(libp2p.ResourceManager(&network.NullResourceManager{}))
require.NoError(p.t, err)
ps, err := pubsub.NewFloodSub(context.Background(), h,
pubsub.WithMessageSigning(false),
pubsub.WithStrictSignatureVerification(false),

View File

@@ -0,0 +1,11 @@
package p2p
const (
GossipSignedExecutionPayloadHeader = "signed_execution_payload_header"
GossipSignedExecutionPayloadEnvelope = "signed_execution_payload_envelope"
GossipPayloadAttestationMessage = "payload_attestation_message"
SignedExecutionPayloadHeaderTopicFormat = GossipProtocolAndDigest + GossipSignedExecutionPayloadHeader
SignedExecutionPayloadEnvelopeTopicFormat = GossipProtocolAndDigest + GossipSignedExecutionPayloadEnvelope
PayloadAttestationMessageTopicFormat = GossipProtocolAndDigest + GossipPayloadAttestationMessage
)

View File

@@ -1023,7 +1023,8 @@ func (s *Server) validateConsensus(ctx context.Context, blk interfaces.ReadOnlyS
}
func (s *Server) validateEquivocation(blk interfaces.ReadOnlyBeaconBlock) error {
if s.ForkchoiceFetcher.HighestReceivedBlockSlot() == blk.Slot() {
slot, _ := s.ForkchoiceFetcher.HighestReceivedBlockSlotRoot()
if slot == blk.Slot() {
return errors.Wrapf(errEquivocatedBlock, "block for slot %d already exists in fork choice", blk.Slot())
}
return nil

View File

@@ -20,6 +20,10 @@ import (
"github.com/prysmaticlabs/prysm/v5/testing/require"
)
// Variables defined in the placeholderFields will not be tested in `TestGetSpec`.
// These are variables that we don't use in Prysm. (i.e. future hardfork, light client... etc)
var placeholderFields = []string{"DOMAIN_BEACON_BUILDER", "DOMAIN_PTC_ATTESTER"}
func TestGetDepositContract(t *testing.T) {
params.SetupTestConfigCleanup(t)
config := params.BeaconConfig().Copy()
@@ -79,6 +83,8 @@ func TestGetSpec(t *testing.T) {
config.DenebForkEpoch = 105
config.ElectraForkVersion = []byte("ElectraForkVersion")
config.ElectraForkEpoch = 107
config.EPBSForkVersion = []byte("EPBSForkVersion")
config.EPBSForkEpoch = 109
config.BLSWithdrawalPrefixByte = byte('b')
config.ETH1AddressWithdrawalPrefixByte = byte('c')
config.GenesisDelay = 24
@@ -188,7 +194,7 @@ func TestGetSpec(t *testing.T) {
data, ok := resp.Data.(map[string]interface{})
require.Equal(t, true, ok)
assert.Equal(t, 154, len(data))
assert.Equal(t, 164, len(data))
for k, v := range data {
t.Run(k, func(t *testing.T) {
switch k {
@@ -266,6 +272,10 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, "0x"+hex.EncodeToString([]byte("ElectraForkVersion")), v)
case "ELECTRA_FORK_EPOCH":
assert.Equal(t, "107", v)
case "EPBS_FORK_VERSION":
assert.Equal(t, "0x"+hex.EncodeToString([]byte("EPBSForkVersion")), v)
case "EPBS_FORK_EPOCH":
assert.Equal(t, "109", v)
case "MIN_ANCHOR_POW_BLOCK_DIFFICULTY":
assert.Equal(t, "1000", v)
case "BLS_WITHDRAWAL_PREFIX":
@@ -523,7 +533,25 @@ func TestGetSpec(t *testing.T) {
assert.Equal(t, "92", v)
case "MAX_DEPOSIT_REQUESTS_PER_PAYLOAD":
assert.Equal(t, "93", v)
case "PROPOSER_SCORE_BOOST_EPBS":
assert.Equal(t, "20", v)
case "PAYLOAD_REVEAL_BOOST":
assert.Equal(t, "40", v)
case "PAYLOAD_WITHHOLD_BOOST":
assert.Equal(t, "40", v)
case "PAYLOAD_TIMELY_THRESHOLD":
assert.Equal(t, "256", v)
case "INTERVALS_PER_SLOT_EPBS":
assert.Equal(t, "4", v)
case "MIN_BUILDER_BALANCE":
assert.Equal(t, "0", v)
default:
for _, pf := range placeholderFields {
if k == pf {
t.Logf("Skipping placeholder field: %s", k)
return
}
}
t.Errorf("Incorrect key: %s", k)
}
})

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