mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-09 21:38:05 -05:00
Compare commits
100 Commits
debugging-
...
epbs-inter
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b2bf5096b | ||
|
|
ba24247b05 | ||
|
|
1c47aa69de | ||
|
|
eaa6c6ef90 | ||
|
|
5f5544a32b | ||
|
|
01e66eb809 | ||
|
|
7fe4b1835c | ||
|
|
ce0130a7b8 | ||
|
|
b22febf463 | ||
|
|
05ba83b67b | ||
|
|
a316053688 | ||
|
|
7c6a92e0ef | ||
|
|
2f3cf1e3f7 | ||
|
|
42fa9b8b61 | ||
|
|
56dfaf8bbd | ||
|
|
2e107f49df | ||
|
|
be4c426130 | ||
|
|
1040f5a132 | ||
|
|
1fb1789b83 | ||
|
|
fa8b1f1709 | ||
|
|
2acf161184 | ||
|
|
35e6126896 | ||
|
|
baae0c015a | ||
|
|
af9c08fb45 | ||
|
|
887b1bd3b5 | ||
|
|
3613931b46 | ||
|
|
305f545088 | ||
|
|
1e14c0df6f | ||
|
|
a927ca8b1c | ||
|
|
e889572c26 | ||
|
|
ef21d64ed9 | ||
|
|
b17e331f1e | ||
|
|
3beda885d2 | ||
|
|
4470a4ed52 | ||
|
|
5c1285f9c0 | ||
|
|
9134802943 | ||
|
|
25e86efa3d | ||
|
|
d75bcf673a | ||
|
|
31c3b4668f | ||
|
|
87a88ebe66 | ||
|
|
36f51234ca | ||
|
|
f8c8530b73 | ||
|
|
23b32ffe39 | ||
|
|
4879a2d0c7 | ||
|
|
81272e8102 | ||
|
|
cc5794b1fa | ||
|
|
5fc4688584 | ||
|
|
836474450d | ||
|
|
fafbb54dc1 | ||
|
|
ea0b439748 | ||
|
|
79eeef162b | ||
|
|
976b9e9a9e | ||
|
|
81e5e5519c | ||
|
|
ff1483888b | ||
|
|
4c3e68ea40 | ||
|
|
a2e66899b2 | ||
|
|
cc8135aa5b | ||
|
|
56a64f5dfb | ||
|
|
72bf445e6b | ||
|
|
293d083a4e | ||
|
|
a737a44cc0 | ||
|
|
6d9b7054da | ||
|
|
6c0c4ae49a | ||
|
|
8bc3793822 | ||
|
|
ad16d4a030 | ||
|
|
92f980c679 | ||
|
|
3ab3366787 | ||
|
|
c61f1299e3 | ||
|
|
619a8cfd1d | ||
|
|
35d9817781 | ||
|
|
6a5d13df60 | ||
|
|
12803fb6ce | ||
|
|
5451ca836a | ||
|
|
3ede8bec91 | ||
|
|
23c4609293 | ||
|
|
f46a118e39 | ||
|
|
732ce21ef4 | ||
|
|
9ae97786c5 | ||
|
|
66d1bb54f6 | ||
|
|
4d98049054 | ||
|
|
d5ff25b59d | ||
|
|
a265cf08fa | ||
|
|
f2ade3caff | ||
|
|
e6ffc0701e | ||
|
|
61c296e075 | ||
|
|
f264680739 | ||
|
|
8fe024f6a1 | ||
|
|
6b7dd833a3 | ||
|
|
060527032b | ||
|
|
a29ecb6bbe | ||
|
|
54656e172f | ||
|
|
97c8adb003 | ||
|
|
2fe8614115 | ||
|
|
09accc7132 | ||
|
|
53f1f11c6d | ||
|
|
48fe9d9c4d | ||
|
|
4386c244e1 | ||
|
|
7ac522d8ff | ||
|
|
52cf3a155d | ||
|
|
83ed320826 |
33
.github/workflows/changelog.yml
vendored
33
.github/workflows/changelog.yml
vendored
@@ -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
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -16,6 +16,8 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- Added SubmitPoolAttesterSlashingV2 endpoint.
|
||||
- Added SubmitAggregateAndProofsRequestV2 endpoint.
|
||||
- Updated the `beacon-chain/monitor` package to Electra. [PR](https://github.com/prysmaticlabs/prysm/pull/14562)
|
||||
- Added ListAttestationsV2 endpoint.
|
||||
- Add ability to rollback node's internal state during processing.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -28,6 +30,18 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
- Return false from HasBlock if the block is being synced.
|
||||
- Cleanup forkchoice on failed insertions.
|
||||
- Use read only validator for core processing to avoid unnecessary copying.
|
||||
- Use ROBlock across block processing pipeline.
|
||||
- Added missing Eth-Consensus-Version headers to GetBlockAttestationsV2 and GetAttesterSlashingsV2 endpoints.
|
||||
- When instantiating new validators, explicit set `Slashed` to false and move `EffectiveBalance` to match struct definition.
|
||||
- Updated pgo profile for beacon chain with holesky data. This improves the profile guided
|
||||
optimizations in the go compiler.
|
||||
- Use read only state when computing the active validator list.
|
||||
- Simplified `ExitedValidatorIndices`.
|
||||
- Simplified `EjectedValidatorIndices`.
|
||||
- `engine_newPayloadV4`,`engine_getPayloadV4` are changes due to new execution request serialization decisions, [PR](https://github.com/prysmaticlabs/prysm/pull/14580)
|
||||
- Use ROBlock earlier in block syncing pipeline.
|
||||
- Changed the signature of `ProcessPayload`
|
||||
- Only Build the Protobuf state once during serialization
|
||||
|
||||
### Deprecated
|
||||
|
||||
@@ -36,11 +50,22 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve
|
||||
### Removed
|
||||
|
||||
- Removed finalized validator index cache, no longer needed.
|
||||
- Removed validator queue position log on key reload and wait for activation.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed mesh size by appending `gParams.Dhi = gossipSubDhi`
|
||||
- Fix skipping partial withdrawals count.
|
||||
- wait for the async StreamEvent writer to exit before leaving the http handler, avoiding race condition panics [pr](https://github.com/prysmaticlabs/prysm/pull/14557)
|
||||
- Certain deb files were returning a 404 which made building new docker images without an existing
|
||||
cache impossible. This has been fixed with updates to rules_oci and bazel-lib.
|
||||
- Fixed an issue where the length check between block body KZG commitments and the existing cache from the database was incompatible.
|
||||
- Fix `--backfill-oldest-slot` handling - this flag was totally broken, the code would always backfill to the default slot [pr](https://github.com/prysmaticlabs/prysm/pull/14584)
|
||||
- Fix keymanager API should return corrected error format for malformed tokens
|
||||
- Fix keymanager API so that get keys returns an empty response instead of a 500 error when using an unsupported keystore.
|
||||
- Small log imporvement, removing some redundant or duplicate logs
|
||||
- EIP7521 - Fixes withdrawal bug by accounting for pending partial withdrawals and deducting already withdrawn amounts from the sweep balance. [PR](https://github.com/prysmaticlabs/prysm/pull/14578)
|
||||
|
||||
|
||||
### Security
|
||||
|
||||
@@ -94,6 +119,7 @@ Updating to this release is recommended at your convenience.
|
||||
- fastssz version bump (better error messages).
|
||||
- SSE implementation that sheds stuck clients. [pr](https://github.com/prysmaticlabs/prysm/pull/14413)
|
||||
- Added GetPoolAttesterSlashingsV2 endpoint.
|
||||
- Use engine API get-blobs for block subscriber to reduce block import latency and potentially reduce bandwidth.
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -101,9 +101,9 @@ http_archive(
|
||||
|
||||
http_archive(
|
||||
name = "aspect_bazel_lib",
|
||||
sha256 = "f5ea76682b209cc0bd90d0f5a3b26d2f7a6a2885f0c5f615e72913f4805dbb0d",
|
||||
strip_prefix = "bazel-lib-2.5.0",
|
||||
url = "https://github.com/aspect-build/bazel-lib/releases/download/v2.5.0/bazel-lib-v2.5.0.tar.gz",
|
||||
sha256 = "a272d79bb0ac6b6965aa199b1f84333413452e87f043b53eca7f347a23a478e8",
|
||||
strip_prefix = "bazel-lib-2.9.3",
|
||||
url = "https://github.com/bazel-contrib/bazel-lib/releases/download/v2.9.3/bazel-lib-v2.9.3.tar.gz",
|
||||
)
|
||||
|
||||
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies", "aspect_bazel_lib_register_toolchains")
|
||||
@@ -165,7 +165,7 @@ load("@rules_oci//oci:pull.bzl", "oci_pull")
|
||||
oci_pull(
|
||||
name = "linux_debian11_multiarch_base", # Debian bullseye
|
||||
digest = "sha256:b82f113425c5b5c714151aaacd8039bc141821cdcd3c65202d42bdf9c43ae60b", # 2023-12-12
|
||||
image = "gcr.io/distroless/cc-debian11",
|
||||
image = "gcr.io/prysmaticlabs/distroless/cc-debian11",
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64/v8",
|
||||
|
||||
@@ -21,7 +21,8 @@ type GetCommitteesResponse struct {
|
||||
}
|
||||
|
||||
type ListAttestationsResponse struct {
|
||||
Data []*Attestation `json:"data"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Data json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type SubmitAttestationsRequest struct {
|
||||
|
||||
@@ -6,9 +6,11 @@ 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",
|
||||
"execution_engine_epbs.go",
|
||||
"forkchoice_update_execution.go",
|
||||
"head.go",
|
||||
"head_sync_committee_info.go",
|
||||
@@ -25,6 +27,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 +47,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 +102,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 +115,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 +131,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 +185,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",
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/forkchoice"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
@@ -42,14 +43,15 @@ 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
|
||||
InsertNode(context.Context, state.BeaconState, consensus_blocks.ROBlock) error
|
||||
ForkChoiceDump(context.Context) (*forkchoice.Dump, error)
|
||||
NewSlot(context.Context, primitives.Slot) error
|
||||
ProposerBoost() [32]byte
|
||||
RecentBlockSlot(root [32]byte) (primitives.Slot, error)
|
||||
IsCanonical(ctx context.Context, blockRoot [32]byte) (bool, error)
|
||||
GetPTCVote(root [32]byte) primitives.PTCStatus
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -242,7 +250,7 @@ func (s *Service) HeadValidatorsIndices(ctx context.Context, epoch primitives.Ep
|
||||
if !s.hasHeadState() {
|
||||
return []primitives.ValidatorIndex{}, nil
|
||||
}
|
||||
return helpers.ActiveValidatorIndices(ctx, s.headState(ctx), epoch)
|
||||
return helpers.ActiveValidatorIndices(ctx, s.headStateReadOnly(ctx), epoch)
|
||||
}
|
||||
|
||||
// HeadGenesisValidatorsRoot returns genesis validators root of the head state.
|
||||
@@ -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) {
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"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 +31,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
|
||||
@@ -44,10 +46,10 @@ func (s *Service) ReceivedBlocksLastEpoch() (uint64, error) {
|
||||
}
|
||||
|
||||
// InsertNode is a wrapper for node insertion which is self locked
|
||||
func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, root [32]byte) error {
|
||||
func (s *Service) InsertNode(ctx context.Context, st state.BeaconState, block consensus_blocks.ROBlock) error {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, root)
|
||||
return s.cfg.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
|
||||
// ForkChoiceDump returns the corresponding value from forkchoice
|
||||
@@ -99,3 +101,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)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
@@ -36,9 +37,10 @@ func prepareForkchoiceState(
|
||||
blockRoot [32]byte,
|
||||
parentRoot [32]byte,
|
||||
payloadHash [32]byte,
|
||||
parentHash [32]byte,
|
||||
justified *ethpb.Checkpoint,
|
||||
finalized *ethpb.Checkpoint,
|
||||
) (state.BeaconState, [32]byte, error) {
|
||||
) (state.BeaconState, consensus_blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
@@ -59,7 +61,27 @@ func prepareForkchoiceState(
|
||||
|
||||
base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...)
|
||||
st, err := state_native.InitializeFromProtoBellatrix(base)
|
||||
return st, blockRoot, err
|
||||
if err != nil {
|
||||
return nil, consensus_blocks.ROBlock{}, err
|
||||
}
|
||||
blk := ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyBellatrix{
|
||||
ExecutionPayload: &enginev1.ExecutionPayload{
|
||||
BlockHash: payloadHash[:],
|
||||
ParentHash: parentHash[:],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, consensus_blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
func TestHeadRoot_Nil(t *testing.T) {
|
||||
@@ -122,9 +144,9 @@ func TestUnrealizedJustifiedBlockHash(t *testing.T) {
|
||||
service := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}}
|
||||
ojc := ðpb.Checkpoint{Root: []byte{'j'}}
|
||||
ofc := ðpb.Checkpoint{Root: []byte{'f'}}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
service.cfg.ForkChoiceStore.SetBalancesByRooter(func(_ context.Context, _ [32]byte) ([]uint64, error) { return []uint64{}, nil })
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{Epoch: 6, Root: [32]byte{'j'}}))
|
||||
|
||||
@@ -316,24 +338,24 @@ func TestService_ChainHeads(t *testing.T) {
|
||||
c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}}
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, [32]byte{'B'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 103, [32]byte{'d'}, [32]byte{'a'}, [32]byte{'D'}, [32]byte{'C'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'b'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 104, [32]byte{'e'}, [32]byte{'b'}, [32]byte{'E'}, [32]byte{'D'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
|
||||
roots, slots := c.ChainHeads()
|
||||
require.Equal(t, 3, len(roots))
|
||||
@@ -413,12 +435,12 @@ func TestService_IsOptimistic(t *testing.T) {
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}, head: &head{root: [32]byte{'b'}}}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
|
||||
opt, err := c.IsOptimistic(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -449,12 +471,12 @@ func TestService_IsOptimisticForRoot(t *testing.T) {
|
||||
c := &Service{cfg: &config{ForkChoiceStore: doublylinkedtree.New()}, head: &head{root: [32]byte{'b'}}}
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, 100, [32]byte{'a'}, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
st, roblock, err = prepareForkchoiceState(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, c.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
|
||||
opt, err := c.IsOptimisticForRoot(ctx, [32]byte{'a'})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
18
beacon-chain/blockchain/epbs_test.go
Normal file
18
beacon-chain/blockchain/epbs_test.go
Normal 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 := ¤tlySyncingPayload{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))
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -2,6 +2,7 @@ package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -27,6 +28,8 @@ 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:
|
||||
@@ -91,6 +94,14 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
|
||||
log.WithError(err).Error("Could not set head root to invalid")
|
||||
return nil, nil
|
||||
}
|
||||
if len(invalidRoots) == 0 {
|
||||
log.WithFields(logrus.Fields{
|
||||
"slot": headBlk.Slot(),
|
||||
"blockRoot": fmt.Sprintf("%#x", bytesutil.Trunc(headRoot[:])),
|
||||
}).Warn("invalid payload")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := s.removeInvalidBlockAndState(ctx, invalidRoots); err != nil {
|
||||
log.WithError(err).Error("Could not remove invalid block and state")
|
||||
return nil, nil
|
||||
@@ -105,6 +116,7 @@ func (s *Service) notifyForkchoiceUpdate(ctx context.Context, arg *fcuConfig) (*
|
||||
}).Warn("Pruned invalid blocks, could not update head root")
|
||||
return nil, invalidBlock{error: ErrInvalidPayload, root: arg.headRoot, invalidAncestorRoots: invalidRoots}
|
||||
}
|
||||
|
||||
b, err := s.getBlock(ctx, r)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get head block")
|
||||
@@ -320,6 +332,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
}
|
||||
|
||||
// Get timestamp.
|
||||
log.WithFields(logrus.Fields{"slot": slot, "stateslot": st.Slot()}).Info("about to compute attributes")
|
||||
t, err := slots.ToTime(uint64(s.genesisTime.Unix()), slot)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get timestamp to get payload attribute")
|
||||
@@ -328,7 +341,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState,
|
||||
|
||||
var attr payloadattribute.Attributer
|
||||
switch st.Version() {
|
||||
case version.Deneb, version.Electra:
|
||||
case version.Deneb, version.Electra, version.EPBS:
|
||||
withdrawals, _, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get expected withdrawals to get payload attribute")
|
||||
@@ -407,7 +420,13 @@ func kzgCommitmentsToVersionedHashes(body interfaces.ReadOnlyBeaconBlockBody) ([
|
||||
|
||||
versionedHashes := make([]common.Hash, len(commitments))
|
||||
for i, commitment := range commitments {
|
||||
versionedHashes[i] = primitives.ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
versionedHashes[i] = ConvertKzgCommitmentToVersionedHash(commitment)
|
||||
}
|
||||
return versionedHashes, nil
|
||||
}
|
||||
|
||||
func ConvertKzgCommitmentToVersionedHash(commitment []byte) common.Hash {
|
||||
versionedHash := sha256.Sum256(commitment)
|
||||
versionedHash[0] = blobCommitmentVersionKZG
|
||||
return versionedHash
|
||||
}
|
||||
|
||||
68
beacon-chain/blockchain/execution_engine_epbs.go
Normal file
68
beacon-chain/blockchain/execution_engine_epbs.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// notifyForkchoiceUpdate signals execution engine the fork choice updates. Execution engine should:
|
||||
// 1. Re-organizes the execution payload chain and corresponding state to make head_block_hash the head.
|
||||
// 2. Applies finality to the execution state: it irreversibly persists the chain of all execution payloads and corresponding state, up to and including finalized_block_hash.
|
||||
func (s *Service) notifyForkchoiceUpdateEPBS(ctx context.Context, blockhash [32]byte, attributes payloadattribute.Attributer) (*enginev1.PayloadIDBytes, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "blockChain.notifyForkchoiceUpdateEPBS")
|
||||
defer span.End()
|
||||
|
||||
finalizedHash := s.cfg.ForkChoiceStore.FinalizedPayloadBlockHash()
|
||||
justifiedHash := s.cfg.ForkChoiceStore.UnrealizedJustifiedPayloadBlockHash()
|
||||
fcs := &enginev1.ForkchoiceState{
|
||||
HeadBlockHash: blockhash[:],
|
||||
SafeBlockHash: justifiedHash[:],
|
||||
FinalizedBlockHash: finalizedHash[:],
|
||||
}
|
||||
if attributes == nil {
|
||||
attributes = payloadattribute.EmptyWithVersion(version.EPBS)
|
||||
}
|
||||
log.WithFields(logrus.Fields{
|
||||
"Version": attributes.Version(),
|
||||
"Timestamp": attributes.Timestamp(),
|
||||
"blockHash": fmt.Sprintf("%#x", blockhash),
|
||||
}).Info("calling FCU V3 with attributes")
|
||||
|
||||
payloadID, lastValidHash, err := s.cfg.ExecutionEngineCaller.ForkchoiceUpdated(ctx, fcs, attributes)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
|
||||
forkchoiceUpdatedOptimisticNodeCount.Inc()
|
||||
log.WithFields(logrus.Fields{
|
||||
"headPayloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(blockhash[:])),
|
||||
"finalizedPayloadBlockHash": fmt.Sprintf("%#x", bytesutil.Trunc(finalizedHash[:])),
|
||||
}).Info("Called fork choice updated with optimistic block")
|
||||
return payloadID, nil
|
||||
case errors.Is(err, execution.ErrInvalidPayloadStatus):
|
||||
log.WithError(err).Info("forkchoice updated to invalid block")
|
||||
return nil, invalidBlock{error: ErrInvalidPayload, root: [32]byte(lastValidHash)}
|
||||
default:
|
||||
log.WithError(err).Error(ErrUndefinedExecutionEngineError)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
forkchoiceUpdatedValidNodeCount.Inc()
|
||||
// If the forkchoice update call has an attribute, update the payload ID cache.
|
||||
hasAttr := attributes != nil && !attributes.IsEmpty()
|
||||
if hasAttr && payloadID == nil && !features.Get().PrepareAllPayloads {
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockHash": fmt.Sprintf("%#x", blockhash),
|
||||
}).Error("Received nil payload ID on VALID engine response")
|
||||
}
|
||||
return payloadID, nil
|
||||
}
|
||||
@@ -46,13 +46,13 @@ func Test_NotifyForkchoiceUpdate_GetPayloadAttrErrorCanContinue(t *testing.T) {
|
||||
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, [32]byte{'B'}, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -104,13 +104,13 @@ func Test_NotifyForkchoiceUpdate(t *testing.T) {
|
||||
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, [32]byte{'B'}, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -287,16 +287,16 @@ func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) {
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{}))
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, [32]byte{'B'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, [32]byte{'C'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -316,10 +316,8 @@ func Test_NotifyForkchoiceUpdate_NIlLVH(t *testing.T) {
|
||||
headRoot: brd,
|
||||
}
|
||||
_, err = service.notifyForkchoiceUpdate(ctx, a)
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
require.Equal(t, brd, InvalidBlockRoot(err))
|
||||
require.Equal(t, brd, InvalidAncestorRoots(err)[0])
|
||||
require.Equal(t, 1, len(InvalidAncestorRoots(err)))
|
||||
// The incoming block is not invalid because the empty node is still valid on ePBS.
|
||||
require.Equal(t, false, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
//
|
||||
@@ -398,28 +396,28 @@ func Test_NotifyForkchoiceUpdateRecursive_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, &forkchoicetypes.Checkpoint{}))
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 1, bra, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
|
||||
bState, _ := util.DeterministicGenesisState(t, 10)
|
||||
require.NoError(t, beaconDB.SaveState(ctx, bState, bra))
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, brb, bra, [32]byte{'B'}, [32]byte{'A'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 3, brc, brb, [32]byte{'C'}, [32]byte{'B'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 4, brd, brc, [32]byte{'D'}, [32]byte{'C'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 5, bre, brb, [32]byte{'E'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 5, bre, brb, [32]byte{'E'}, [32]byte{'D'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 6, brf, bre, [32]byte{'F'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 6, brf, bre, [32]byte{'F'}, [32]byte{'E'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 7, brg, bre, [32]byte{'G'}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 7, brg, bre, [32]byte{'G'}, [32]byte{'F'}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -512,10 +510,10 @@ func Test_NotifyNewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, r, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, r, [32]byte{}, [32]byte{'A'}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -692,7 +690,7 @@ func Test_NotifyNewPayload(t *testing.T) {
|
||||
}
|
||||
service.cfg.ExecutionEngineCaller = e
|
||||
root := [32]byte{'a'}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, root, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, root, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
postVersion, postHeader, err := getStateVersionAndPayload(tt.postState)
|
||||
@@ -759,17 +757,17 @@ func Test_reportInvalidBlock(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
ctx, _, fcs := tr.ctx, tr.db, tr.fcs
|
||||
jcp := ðpb.Checkpoint{}
|
||||
st, root, err := prepareForkchoiceState(ctx, 0, [32]byte{'A'}, [32]byte{}, [32]byte{'a'}, jcp, jcp)
|
||||
st, root, err := prepareForkchoiceState(ctx, 0, [32]byte{'A'}, [32]byte{}, [32]byte{'a'}, [32]byte{}, jcp, jcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
st, root, err = prepareForkchoiceState(ctx, 1, [32]byte{'B'}, [32]byte{'A'}, [32]byte{'b'}, jcp, jcp)
|
||||
st, root, err = prepareForkchoiceState(ctx, 1, [32]byte{'B'}, [32]byte{'A'}, [32]byte{'b'}, [32]byte{'a'}, jcp, jcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'C'}, [32]byte{'B'}, [32]byte{'c'}, jcp, jcp)
|
||||
st, root, err = prepareForkchoiceState(ctx, 2, [32]byte{'C'}, [32]byte{'B'}, [32]byte{'c'}, [32]byte{'b'}, jcp, jcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
|
||||
st, root, err = prepareForkchoiceState(ctx, 3, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'d'}, jcp, jcp)
|
||||
st, root, err = prepareForkchoiceState(ctx, 3, [32]byte{'D'}, [32]byte{'C'}, [32]byte{'d'}, [32]byte{'c'}, jcp, jcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
|
||||
@@ -931,7 +929,7 @@ func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
||||
fjc := &forkchoicetypes.Checkpoint{Epoch: 0, Root: params.BeaconConfig().ZeroHash}
|
||||
require.NoError(t, fcs.UpdateJustifiedCheckpoint(ctx, fjc))
|
||||
require.NoError(t, fcs.UpdateFinalizedCheckpoint(fjc))
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, genesisRoot, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, genesisRoot, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
fcs.SetOriginRoot(genesisRoot)
|
||||
@@ -965,7 +963,7 @@ func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, opStateSummary))
|
||||
tenjc := ðpb.Checkpoint{Epoch: 10, Root: genesisRoot[:]}
|
||||
tenfc := ðpb.Checkpoint{Epoch: 10, Root: genesisRoot[:]}
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 320, opRoot, genesisRoot, params.BeaconConfig().ZeroHash, tenjc, tenfc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 320, opRoot, genesisRoot, params.BeaconConfig().ZeroHash, [32]byte{}, tenjc, tenfc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
assert.NoError(t, beaconDB.SaveGenesisBlockRoot(ctx, opRoot))
|
||||
@@ -994,7 +992,7 @@ func Test_UpdateLastValidatedCheckpoint(t *testing.T) {
|
||||
require.NoError(t, beaconDB.SaveStateSummary(ctx, validSummary))
|
||||
twentyjc := ðpb.Checkpoint{Epoch: 20, Root: validRoot[:]}
|
||||
twentyfc := ðpb.Checkpoint{Epoch: 20, Root: validRoot[:]}
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 640, validRoot, genesisRoot, params.BeaconConfig().ZeroHash, twentyjc, twentyfc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 640, validRoot, genesisRoot, params.BeaconConfig().ZeroHash, [32]byte{}, twentyjc, twentyfc)
|
||||
require.NoError(t, err)
|
||||
fcs.SetBalancesByRooter(func(_ context.Context, _ [32]byte) ([]uint64, error) { return []uint64{}, nil })
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
@@ -1056,8 +1054,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)
|
||||
@@ -1135,9 +1133,14 @@ func TestComputePayloadAttribute(t *testing.T) {
|
||||
// Cache hit, advance state, no fee recipient
|
||||
slot := primitives.Slot(1)
|
||||
service.cfg.PayloadIDCache.Set(slot, [32]byte{}, [8]byte{})
|
||||
blk := util.NewBeaconBlockBellatrix()
|
||||
signed, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(t, err)
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(signed, [32]byte{'a'})
|
||||
require.NoError(t, err)
|
||||
cfg := &postBlockProcessConfig{
|
||||
ctx: ctx,
|
||||
blockRoot: [32]byte{'a'},
|
||||
ctx: ctx,
|
||||
roblock: roblock,
|
||||
}
|
||||
fcu := &fcuConfig{
|
||||
headState: st,
|
||||
|
||||
@@ -122,13 +122,13 @@ func TestService_forkchoiceUpdateWithExecution_SameHeadRootNewProposer(t *testin
|
||||
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 1, altairBlkRoot, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 2, bellatrixBlkRoot, altairBlkRoot, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -164,10 +164,10 @@ func TestShouldOverrideFCU(t *testing.T) {
|
||||
headRoot := [32]byte{'b'}
|
||||
parentRoot := [32]byte{'a'}
|
||||
ojc := ðpb.Checkpoint{}
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, parentRoot, [32]byte{}, [32]byte{}, ojc, ojc)
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, parentRoot, [32]byte{}, [32]byte{}, [32]byte{}, ojc, ojc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
st, root, err = prepareForkchoiceState(ctx, 2, headRoot, parentRoot, [32]byte{}, ojc, ojc)
|
||||
st, root, err = prepareForkchoiceState(ctx, 2, headRoot, parentRoot, [32]byte{}, [32]byte{}, ojc, ojc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, root))
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestSaveHead_Different(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, oldBlock.Block().Slot(), oldRoot, oldBlock.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, oldBlock.Block().Slot(), oldRoot, oldBlock.Block().ParentRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
service.head = &head{
|
||||
@@ -63,11 +63,11 @@ func TestSaveHead_Different(t *testing.T) {
|
||||
wsb := util.SaveBlock(t, context.Background(), service.cfg.BeaconDB, newHeadSignedBlock)
|
||||
newRoot, err := newHeadBlock.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, slots.PrevSlot(wsb.Block().Slot()), wsb.Block().ParentRoot(), service.cfg.ForkChoiceStore.CachedHeadRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, slots.PrevSlot(wsb.Block().Slot()), wsb.Block().ParentRoot(), service.cfg.ForkChoiceStore.CachedHeadRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
headState, err := util.NewBeaconState()
|
||||
@@ -101,7 +101,7 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, oldBlock.Block().Slot(), oldRoot, oldBlock.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, oldBlock.Block().Slot(), oldRoot, oldBlock.Block().ParentRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
service.head = &head{
|
||||
@@ -110,7 +110,7 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
}
|
||||
|
||||
reorgChainParent := [32]byte{'B'}
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 0, reorgChainParent, oldRoot, oldBlock.Block().ParentRoot(), ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, 0, reorgChainParent, oldRoot, oldBlock.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestSaveHead_Different_Reorg(t *testing.T) {
|
||||
wsb := util.SaveBlock(t, context.Background(), service.cfg.BeaconDB, newHeadSignedBlock)
|
||||
newRoot, err := newHeadBlock.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
headState, err := util.NewBeaconState()
|
||||
@@ -238,11 +238,11 @@ func TestRetrieveHead_ReadOnly(t *testing.T) {
|
||||
wsb := util.SaveBlock(t, context.Background(), service.cfg.BeaconDB, newHeadSignedBlock)
|
||||
newRoot, err := newHeadBlock.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, slots.PrevSlot(wsb.Block().Slot()), wsb.Block().ParentRoot(), service.cfg.ForkChoiceStore.CachedHeadRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, slots.PrevSlot(wsb.Block().Slot()), wsb.Block().ParentRoot(), service.cfg.ForkChoiceStore.CachedHeadRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err = prepareForkchoiceState(ctx, wsb.Block().Slot(), newRoot, wsb.Block().ParentRoot(), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
headState, err := util.NewBeaconState()
|
||||
@@ -304,7 +304,7 @@ func TestSaveOrphanedAtts(t *testing.T) {
|
||||
for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk3, blk4} {
|
||||
r, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
util.SaveBlock(t, ctx, beaconDB, blk)
|
||||
@@ -381,7 +381,7 @@ func TestSaveOrphanedOps(t *testing.T) {
|
||||
for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk3, blk4} {
|
||||
r, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
util.SaveBlock(t, ctx, beaconDB, blk)
|
||||
@@ -451,7 +451,7 @@ func TestSaveOrphanedAtts_CanFilter(t *testing.T) {
|
||||
for _, blk := range []*ethpb.SignedBeaconBlockCapella{blkG, blk1, blk2, blk4} {
|
||||
r, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
util.SaveBlock(t, ctx, beaconDB, blk)
|
||||
@@ -509,7 +509,7 @@ func TestSaveOrphanedAtts_DoublyLinkedTrie(t *testing.T) {
|
||||
for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk3, blk4} {
|
||||
r, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
util.SaveBlock(t, ctx, beaconDB, blk)
|
||||
@@ -568,7 +568,7 @@ func TestSaveOrphanedAtts_CanFilter_DoublyLinkedTrie(t *testing.T) {
|
||||
for _, blk := range []*ethpb.SignedBeaconBlock{blkG, blk1, blk2, blk4} {
|
||||
r, err := blk.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r, bytesutil.ToBytes32(blk.Block.ParentRoot), [32]byte{}, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
util.SaveBlock(t, ctx, beaconDB, blk)
|
||||
@@ -583,7 +583,7 @@ func TestUpdateHead_noSavedChanges(t *testing.T) {
|
||||
ctx, beaconDB, fcs := tr.ctx, tr.db, tr.fcs
|
||||
|
||||
ojp := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, [32]byte{}, ojp, ojp)
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, [32]byte{}, [32]byte{}, ojp, ojp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, blkRoot))
|
||||
|
||||
@@ -603,7 +603,7 @@ func TestUpdateHead_noSavedChanges(t *testing.T) {
|
||||
headRoot := service.headRoot()
|
||||
require.Equal(t, [32]byte{}, headRoot)
|
||||
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 0, bellatrixBlkRoot, [32]byte{}, [32]byte{}, fcp, fcp)
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, 0, bellatrixBlkRoot, [32]byte{}, [32]byte{}, [32]byte{}, fcp, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, fcs.InsertNode(ctx, st, blkRoot))
|
||||
fcs.SetBalancesByRooter(func(context.Context, [32]byte) ([]uint64, error) { return []uint64{1, 2}, nil })
|
||||
|
||||
@@ -45,28 +45,44 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error {
|
||||
}
|
||||
log = log.WithField("syncBitsCount", agg.SyncCommitteeBits.Count())
|
||||
}
|
||||
if b.Version() >= version.Bellatrix {
|
||||
p, err := b.Body().Execution()
|
||||
if b.Version() >= version.EPBS {
|
||||
sh, err := b.Body().SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log = log.WithField("payloadHash", fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash())))
|
||||
txs, err := p.Transactions()
|
||||
switch {
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedField):
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
log = log.WithField("txCount", len(txs))
|
||||
txsPerSlotCount.Set(float64(len(txs)))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Deneb {
|
||||
kzgs, err := b.Body().BlobKzgCommitments()
|
||||
header, err := sh.Header()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get blob KZG commitments")
|
||||
} else if len(kzgs) > 0 {
|
||||
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
||||
return err
|
||||
}
|
||||
log = log.WithFields(logrus.Fields{"payloadHash": fmt.Sprintf("%#x", header.BlockHash()),
|
||||
"builderIndex": header.BuilderIndex(),
|
||||
"value": header.Value(),
|
||||
"blobKzgCommitmentsRoot": fmt.Sprintf("%#x", header.BlobKzgCommitmentsRoot()),
|
||||
})
|
||||
} else {
|
||||
if b.Version() >= version.Bellatrix {
|
||||
p, err := b.Body().Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log = log.WithField("payloadHash", fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash())))
|
||||
txs, err := p.Transactions()
|
||||
switch {
|
||||
case errors.Is(err, consensus_types.ErrUnsupportedField):
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
log = log.WithField("txCount", len(txs))
|
||||
txsPerSlotCount.Set(float64(len(txs)))
|
||||
}
|
||||
}
|
||||
if b.Version() >= version.Deneb {
|
||||
kzgs, err := b.Body().BlobKzgCommitments()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get blob KZG commitments")
|
||||
} else if len(kzgs) > 0 {
|
||||
log = log.WithField("kzgCommitmentCount", len(kzgs))
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Info("Finished applying state transition")
|
||||
@@ -112,6 +128,9 @@ func logBlockSyncStatus(block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte
|
||||
|
||||
// logs payload related data every slot.
|
||||
func logPayload(block interfaces.ReadOnlyBeaconBlock) error {
|
||||
if block.Version() >= version.EPBS {
|
||||
return nil
|
||||
}
|
||||
isExecutionBlk, err := blocks.IsExecutionBlock(block.Body())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not determine if block is execution block")
|
||||
|
||||
@@ -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()",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -32,18 +32,18 @@ func TestStore_OnAttestation_ErrorConditions(t *testing.T) {
|
||||
util.SaveBlock(t, ctx, beaconDB, blkWithoutState)
|
||||
|
||||
cp := ðpb.Checkpoint{}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, cp, cp)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, 0, [32]byte{}, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, cp, cp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
|
||||
blkWithStateBadAtt := util.NewBeaconBlock()
|
||||
blkWithStateBadAtt.Block.Slot = 1
|
||||
r, err := blkWithStateBadAtt.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
cp = ðpb.Checkpoint{Root: r[:]}
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, blkWithStateBadAtt.Block.Slot, r, [32]byte{}, params.BeaconConfig().ZeroHash, cp, cp)
|
||||
st, roblock, err = prepareForkchoiceState(ctx, blkWithStateBadAtt.Block.Slot, r, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, cp, cp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
util.SaveBlock(t, ctx, beaconDB, blkWithStateBadAtt)
|
||||
BlkWithStateBadAttRoot, err := blkWithStateBadAtt.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
@@ -139,9 +139,9 @@ func TestStore_OnAttestation_Ok_DoublyLinkedTree(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, copied, tRoot))
|
||||
ojc := ðpb.Checkpoint{Epoch: 0, Root: tRoot[:]}
|
||||
ofc := ðpb.Checkpoint{Epoch: 0, Root: tRoot[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, roblock, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, roblock))
|
||||
require.NoError(t, service.OnAttestation(ctx, att[0], 0))
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestService_GetRecentPreState(t *testing.T) {
|
||||
err = s.SetFinalizedCheckpoint(cp0)
|
||||
require.NoError(t, err)
|
||||
|
||||
st, root, err := prepareForkchoiceState(ctx, 31, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0)
|
||||
st, root, err := prepareForkchoiceState(ctx, 31, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, [32]byte{}, cp0, cp0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
service.head = &head{
|
||||
@@ -202,7 +202,7 @@ func TestService_GetAttPreState_Concurrency(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, s, bytesutil.ToBytes32([]byte{'A'})))
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: ckRoot}))
|
||||
|
||||
st, root, err := prepareForkchoiceState(ctx, 100, [32]byte(cp1.Root), [32]byte{}, [32]byte{'R'}, cp1, cp1)
|
||||
st, root, err := prepareForkchoiceState(ctx, 100, [32]byte(cp1.Root), [32]byte{}, [32]byte{'R'}, [32]byte{}, cp1, cp1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
|
||||
@@ -259,7 +259,7 @@ func TestStore_SaveCheckpointState(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, s, bytesutil.ToBytes32([]byte{'A'})))
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bytesutil.PadTo([]byte{'A'}, fieldparams.RootLength)}))
|
||||
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, [32]byte(cp1.Root), [32]byte{}, [32]byte{'R'}, cp1, cp1)
|
||||
st, root, err := prepareForkchoiceState(ctx, 1, [32]byte(cp1.Root), [32]byte{}, [32]byte{'R'}, [32]byte{}, cp1, cp1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
s1, err := service.getAttPreState(ctx, cp1)
|
||||
@@ -273,7 +273,7 @@ func TestStore_SaveCheckpointState(t *testing.T) {
|
||||
_, err = service.getAttPreState(ctx, cp2)
|
||||
require.ErrorContains(t, "epoch 2 root 0x4200000000000000000000000000000000000000000000000000000000000000: not a checkpoint in forkchoice", err)
|
||||
|
||||
st, root, err = prepareForkchoiceState(ctx, 33, [32]byte(cp2.Root), [32]byte(cp1.Root), [32]byte{'R'}, cp2, cp2)
|
||||
st, root, err = prepareForkchoiceState(ctx, 33, [32]byte(cp2.Root), [32]byte(cp1.Root), [32]byte{'R'}, [32]byte{}, cp2, cp2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
|
||||
@@ -298,7 +298,7 @@ func TestStore_SaveCheckpointState(t *testing.T) {
|
||||
cp3 := ðpb.Checkpoint{Epoch: 1, Root: bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)}
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, s, bytesutil.ToBytes32([]byte{'C'})))
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveStateSummary(ctx, ðpb.StateSummary{Root: bytesutil.PadTo([]byte{'C'}, fieldparams.RootLength)}))
|
||||
st, root, err = prepareForkchoiceState(ctx, 31, [32]byte(cp3.Root), [32]byte(cp2.Root), [32]byte{'P'}, cp2, cp2)
|
||||
st, root, err = prepareForkchoiceState(ctx, 31, [32]byte(cp3.Root), [32]byte(cp2.Root), [32]byte{'P'}, [32]byte{}, cp2, cp2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root))
|
||||
|
||||
@@ -318,10 +318,9 @@ func TestStore_UpdateCheckpointState(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
checkpoint := ðpb.Checkpoint{Epoch: epoch, Root: r1[:]}
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, baseState, bytesutil.ToBytes32(checkpoint.Root)))
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, blk.Block.Slot, r1, [32]byte{}, params.BeaconConfig().ZeroHash, checkpoint, checkpoint)
|
||||
st, roblock, err := prepareForkchoiceState(ctx, blk.Block.Slot, r1, [32]byte{}, params.BeaconConfig().ZeroHash, [32]byte{}, checkpoint, checkpoint)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, r1))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
returned, err := service.getAttPreState(ctx, checkpoint)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, params.BeaconConfig().SlotsPerEpoch.Mul(uint64(checkpoint.Epoch)), returned.Slot(), "Incorrectly returned base state")
|
||||
@@ -337,10 +336,9 @@ func TestStore_UpdateCheckpointState(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
newCheckpoint := ðpb.Checkpoint{Epoch: epoch, Root: r2[:]}
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, baseState, bytesutil.ToBytes32(newCheckpoint.Root)))
|
||||
st, blkRoot, err = prepareForkchoiceState(ctx, blk.Block.Slot, r2, r1, params.BeaconConfig().ZeroHash, newCheckpoint, newCheckpoint)
|
||||
st, roblock, err = prepareForkchoiceState(ctx, blk.Block.Slot, r2, r1, params.BeaconConfig().ZeroHash, [32]byte{}, newCheckpoint, newCheckpoint)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, r2))
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, roblock))
|
||||
returned, err = service.getAttPreState(ctx, newCheckpoint)
|
||||
require.NoError(t, err)
|
||||
s, err := slots.EpochStart(newCheckpoint.Epoch)
|
||||
|
||||
@@ -46,8 +46,7 @@ var initialSyncBlockCacheSize = uint64(2 * params.BeaconConfig().SlotsPerEpoch)
|
||||
// process the beacon block after validating the state transition function
|
||||
type postBlockProcessConfig struct {
|
||||
ctx context.Context
|
||||
signed interfaces.ReadOnlySignedBeaconBlock
|
||||
blockRoot [32]byte
|
||||
roblock consensusblocks.ROBlock
|
||||
headRoot [32]byte
|
||||
postState state.BeaconState
|
||||
isValidPayload bool
|
||||
@@ -61,31 +60,34 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
ctx, span := trace.StartSpan(cfg.ctx, "blockChain.onBlock")
|
||||
defer span.End()
|
||||
cfg.ctx = ctx
|
||||
if err := consensusblocks.BeaconBlockIsNil(cfg.signed); err != nil {
|
||||
if err := consensusblocks.BeaconBlockIsNil(cfg.roblock); err != nil {
|
||||
return invalidBlock{error: err}
|
||||
}
|
||||
startTime := time.Now()
|
||||
fcuArgs := &fcuConfig{}
|
||||
|
||||
if s.inRegularSync() {
|
||||
defer s.handleSecondFCUCall(cfg, fcuArgs)
|
||||
if cfg.roblock.Version() < version.EPBS {
|
||||
defer s.handleSecondFCUCall(cfg, fcuArgs)
|
||||
}
|
||||
}
|
||||
defer s.sendLightClientFeeds(cfg)
|
||||
defer s.sendStateFeedOnBlock(cfg)
|
||||
defer reportProcessingTime(startTime)
|
||||
defer reportAttestationInclusion(cfg.signed.Block())
|
||||
defer reportAttestationInclusion(cfg.roblock.Block())
|
||||
|
||||
err := s.cfg.ForkChoiceStore.InsertNode(ctx, cfg.postState, cfg.blockRoot)
|
||||
err := s.cfg.ForkChoiceStore.InsertNode(ctx, cfg.postState, cfg.roblock)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not insert block %d to fork choice store", cfg.signed.Block().Slot())
|
||||
s.rollbackBlock(ctx, cfg.roblock.Root())
|
||||
return errors.Wrapf(err, "could not insert block %d to fork choice store", cfg.roblock.Block().Slot())
|
||||
}
|
||||
if err := s.handleBlockAttestations(ctx, cfg.signed.Block(), cfg.postState); err != nil {
|
||||
if err := s.handleBlockAttestations(ctx, cfg.roblock.Block(), cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not handle block's attestations")
|
||||
}
|
||||
|
||||
s.InsertSlashingsToForkChoiceStore(ctx, cfg.signed.Block().Body().AttesterSlashings())
|
||||
s.InsertSlashingsToForkChoiceStore(ctx, cfg.roblock.Block().Body().AttesterSlashings())
|
||||
if cfg.isValidPayload {
|
||||
if err := s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, cfg.blockRoot); err != nil {
|
||||
if err := s.cfg.ForkChoiceStore.SetOptimisticToValid(ctx, cfg.roblock.Root()); err != nil {
|
||||
return errors.Wrap(err, "could not set optimistic block to valid")
|
||||
}
|
||||
}
|
||||
@@ -95,10 +97,22 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error {
|
||||
log.WithError(err).Warn("Could not update head")
|
||||
}
|
||||
newBlockHeadElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
|
||||
if cfg.headRoot != cfg.blockRoot {
|
||||
s.logNonCanonicalBlockReceived(cfg.blockRoot, cfg.headRoot)
|
||||
if cfg.headRoot != cfg.roblock.Root() {
|
||||
s.logNonCanonicalBlockReceived(cfg.roblock.Root(), cfg.headRoot)
|
||||
return nil
|
||||
}
|
||||
if cfg.roblock.Version() >= version.EPBS {
|
||||
if err := s.saveHead(ctx, cfg.headRoot, cfg.roblock, cfg.postState); err != nil {
|
||||
log.WithError(err).Error("could not save head")
|
||||
}
|
||||
if err := s.pruneAttsFromPool(cfg.roblock); err != nil {
|
||||
log.WithError(err).Error("could not prune attestations from pool")
|
||||
}
|
||||
|
||||
// update the NSC and handle epoch boundaries here since we do
|
||||
// not send FCU at all
|
||||
return s.updateCachesPostBlockProcessing(cfg)
|
||||
}
|
||||
if err := s.getFCUArgs(cfg, fcuArgs); err != nil {
|
||||
log.WithError(err).Error("Could not get forkchoice update argument")
|
||||
return nil
|
||||
@@ -154,7 +168,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
}
|
||||
|
||||
// Fill in missing blocks
|
||||
if err := s.fillInForkChoiceMissingBlocks(ctx, blks[0].Block(), preState.CurrentJustifiedCheckpoint(), preState.FinalizedCheckpoint()); err != nil {
|
||||
if err := s.fillInForkChoiceMissingBlocks(ctx, blks[0], preState.CurrentJustifiedCheckpoint(), preState.FinalizedCheckpoint()); err != nil {
|
||||
return errors.Wrap(err, "could not fill in missing blocks to forkchoice")
|
||||
}
|
||||
|
||||
@@ -234,7 +248,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
if err := avs.IsDataAvailable(ctx, s.CurrentSlot(), b); err != nil {
|
||||
return errors.Wrapf(err, "could not validate blob data availability at slot %d", b.Block().Slot())
|
||||
}
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b.Block(),
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b,
|
||||
JustifiedCheckpoint: jCheckpoints[i],
|
||||
FinalizedCheckpoint: fCheckpoints[i]}
|
||||
pendingNodes[len(blks)-i-1] = args
|
||||
@@ -279,7 +293,7 @@ func (s *Service) onBlockBatch(ctx context.Context, blks []consensusblocks.ROBlo
|
||||
return errors.Wrap(err, "could not insert batch to forkchoice")
|
||||
}
|
||||
// Insert the last block to forkchoice
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(ctx, preState, lastBR); err != nil {
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(ctx, preState, lastB); err != nil {
|
||||
return errors.Wrap(err, "could not insert last block in batch to forkchoice")
|
||||
}
|
||||
// Set their optimistic status
|
||||
@@ -375,7 +389,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
|
||||
}
|
||||
@@ -478,9 +492,15 @@ func (s *Service) runLateBlockTasks() {
|
||||
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 3
|
||||
ticker := slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
epbs := params.BeaconConfig().EPBSForkEpoch
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C():
|
||||
case slot := <-ticker.C():
|
||||
if slots.ToEpoch(slot) == epbs && slot%32 == 0 {
|
||||
ticker.Done()
|
||||
attThreshold := params.BeaconConfig().SecondsPerSlot / 4
|
||||
ticker = slots.NewSlotTickerWithOffset(s.genesisTime, time.Duration(attThreshold)*time.Second, params.BeaconConfig().SecondsPerSlot)
|
||||
}
|
||||
s.lateBlockTasks(s.ctx)
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Context closed, exiting routine")
|
||||
@@ -651,24 +671,36 @@ func (s *Service) lateBlockTasks(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
if headState.Version() >= version.EPBS {
|
||||
bh, err := headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve latest block hash")
|
||||
return
|
||||
}
|
||||
_, err = s.notifyForkchoiceUpdateEPBS(ctx, [32]byte(bh), attribute)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
} else {
|
||||
s.headLock.RLock()
|
||||
headBlock, err := s.headBlock()
|
||||
if err != nil {
|
||||
s.headLock.RUnlock()
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve head block")
|
||||
return
|
||||
}
|
||||
s.headLock.RUnlock()
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to retrieve head block")
|
||||
return
|
||||
}
|
||||
s.headLock.RUnlock()
|
||||
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: headRoot,
|
||||
headBlock: headBlock,
|
||||
attributes: attribute,
|
||||
}
|
||||
_, err = s.notifyForkchoiceUpdate(ctx, fcuArgs)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: headRoot,
|
||||
headBlock: headBlock,
|
||||
attributes: attribute,
|
||||
}
|
||||
_, err = s.notifyForkchoiceUpdate(ctx, fcuArgs)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("could not perform late block tasks: failed to update forkchoice with engine")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,3 +720,15 @@ func (s *Service) handleInvalidExecutionError(ctx context.Context, err error, bl
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// In the event of an issue processing a block we rollback changes done to the db and our caches
|
||||
// to always ensure that the node's internal state is consistent.
|
||||
func (s *Service) rollbackBlock(ctx context.Context, blockRoot [32]byte) {
|
||||
log.Warnf("Rolling back insertion of block with root %#x due to processing error", blockRoot)
|
||||
if err := s.cfg.StateGen.DeleteStateFromCaches(ctx, blockRoot); err != nil {
|
||||
log.WithError(err).Errorf("Could not delete state from caches with block root %#x", blockRoot)
|
||||
}
|
||||
if err := s.cfg.BeaconDB.DeleteBlock(ctx, blockRoot); err != nil {
|
||||
log.WithError(err).Errorf("Could not delete block with block root %#x", blockRoot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
lightclient "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/light-client"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/pkg/errors"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
@@ -42,7 +44,7 @@ func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) er
|
||||
if !s.inRegularSync() {
|
||||
return nil
|
||||
}
|
||||
slot := cfg.signed.Block().Slot()
|
||||
slot := cfg.roblock.Block().Slot()
|
||||
if slots.WithinVotingWindow(uint64(s.genesisTime.Unix()), slot) {
|
||||
return nil
|
||||
}
|
||||
@@ -50,9 +52,9 @@ func (s *Service) getFCUArgs(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) er
|
||||
}
|
||||
|
||||
func (s *Service) getFCUArgsEarlyBlock(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if cfg.blockRoot == cfg.headRoot {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
fcuArgs.headState = cfg.postState
|
||||
fcuArgs.headBlock = cfg.signed
|
||||
fcuArgs.headBlock = cfg.roblock
|
||||
fcuArgs.headRoot = cfg.headRoot
|
||||
fcuArgs.proposingSlot = s.CurrentSlot() + 1
|
||||
return nil
|
||||
@@ -96,7 +98,7 @@ func (s *Service) fcuArgsNonCanonicalBlock(cfg *postBlockProcessConfig, fcuArgs
|
||||
|
||||
// sendStateFeedOnBlock sends an event that a new block has been synced
|
||||
func (s *Service) sendStateFeedOnBlock(cfg *postBlockProcessConfig) {
|
||||
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(cfg.blockRoot)
|
||||
optimistic, err := s.cfg.ForkChoiceStore.IsOptimistic(cfg.roblock.Root())
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("Could not check if block is optimistic")
|
||||
optimistic = true
|
||||
@@ -105,9 +107,9 @@ func (s *Service) sendStateFeedOnBlock(cfg *postBlockProcessConfig) {
|
||||
s.cfg.StateNotifier.StateFeed().Send(&feed.Event{
|
||||
Type: statefeed.BlockProcessed,
|
||||
Data: &statefeed.BlockProcessedData{
|
||||
Slot: cfg.signed.Block().Slot(),
|
||||
BlockRoot: cfg.blockRoot,
|
||||
SignedBlock: cfg.signed,
|
||||
Slot: cfg.roblock.Block().Slot(),
|
||||
BlockRoot: cfg.roblock.Root(),
|
||||
SignedBlock: cfg.roblock,
|
||||
Verified: true,
|
||||
Optimistic: optimistic,
|
||||
},
|
||||
@@ -117,7 +119,7 @@ func (s *Service) sendStateFeedOnBlock(cfg *postBlockProcessConfig) {
|
||||
// sendLightClientFeeds sends the light client feeds when feature flag is enabled.
|
||||
func (s *Service) sendLightClientFeeds(cfg *postBlockProcessConfig) {
|
||||
if features.Get().EnableLightClient {
|
||||
if _, err := s.sendLightClientOptimisticUpdate(cfg.ctx, cfg.signed, cfg.postState); err != nil {
|
||||
if _, err := s.sendLightClientOptimisticUpdate(cfg.ctx, cfg.roblock, cfg.postState); err != nil {
|
||||
log.WithError(err).Error("Failed to send light client optimistic update")
|
||||
}
|
||||
|
||||
@@ -125,7 +127,7 @@ func (s *Service) sendLightClientFeeds(cfg *postBlockProcessConfig) {
|
||||
finalized := s.ForkChoicer().FinalizedCheckpoint()
|
||||
|
||||
// LightClientFinalityUpdate needs super majority
|
||||
s.tryPublishLightClientFinalityUpdate(cfg.ctx, cfg.signed, finalized, cfg.postState)
|
||||
s.tryPublishLightClientFinalityUpdate(cfg.ctx, cfg.roblock, finalized, cfg.postState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,20 +254,21 @@ func (s *Service) sendLightClientOptimisticUpdate(ctx context.Context, signed in
|
||||
// before sending FCU to the engine.
|
||||
func (s *Service) updateCachesPostBlockProcessing(cfg *postBlockProcessConfig) error {
|
||||
slot := cfg.postState.Slot()
|
||||
if err := transition.UpdateNextSlotCache(cfg.ctx, cfg.blockRoot[:], cfg.postState); err != nil {
|
||||
root := cfg.roblock.Root()
|
||||
if err := transition.UpdateNextSlotCache(cfg.ctx, root[:], cfg.postState); err != nil {
|
||||
return errors.Wrap(err, "could not update next slot state cache")
|
||||
}
|
||||
if !slots.IsEpochEnd(slot) {
|
||||
return nil
|
||||
}
|
||||
return s.handleEpochBoundary(cfg.ctx, slot, cfg.postState, cfg.blockRoot[:])
|
||||
return s.handleEpochBoundary(cfg.ctx, slot, cfg.postState, root[:])
|
||||
}
|
||||
|
||||
// handleSecondFCUCall handles a second call to FCU when syncing a new block.
|
||||
// This is useful when proposing in the next block and we want to defer the
|
||||
// computation of the next slot shuffling.
|
||||
func (s *Service) handleSecondFCUCall(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) {
|
||||
if (fcuArgs.attributes == nil || fcuArgs.attributes.IsEmpty()) && cfg.headRoot == cfg.blockRoot {
|
||||
if (fcuArgs.attributes == nil || fcuArgs.attributes.IsEmpty()) && cfg.headRoot == cfg.roblock.Root() {
|
||||
go s.sendFCUWithAttributes(cfg, fcuArgs)
|
||||
}
|
||||
}
|
||||
@@ -281,7 +284,7 @@ func reportProcessingTime(startTime time.Time) {
|
||||
// called on blocks that arrive after the attestation voting window, or in a
|
||||
// background routine after syncing early blocks.
|
||||
func (s *Service) computePayloadAttributes(cfg *postBlockProcessConfig, fcuArgs *fcuConfig) error {
|
||||
if cfg.blockRoot == cfg.headRoot {
|
||||
if cfg.roblock.Root() == cfg.headRoot {
|
||||
if err := s.updateCachesPostBlockProcessing(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,7 +305,32 @@ func (s *Service) getBlockPreState(ctx context.Context, b interfaces.ReadOnlyBea
|
||||
return nil, err
|
||||
}
|
||||
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, b.ParentRoot())
|
||||
parentRoot := b.ParentRoot()
|
||||
s.ForkChoicer().RLock()
|
||||
parentSlot, err := s.ForkChoicer().Slot(parentRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parentIsEPBS := slots.ToEpoch(parentSlot) >= params.BeaconConfig().EPBSForkEpoch
|
||||
s.ForkChoicer().RUnlock()
|
||||
if parentIsEPBS && b.Version() >= version.EPBS {
|
||||
s.ForkChoicer().RLock()
|
||||
parentHash := s.ForkChoicer().HashForBlockRoot(parentRoot)
|
||||
s.ForkChoicer().RUnlock()
|
||||
signedBid, err := b.Body().SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get signed execution payload header")
|
||||
}
|
||||
bid, err := signedBid.Header()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution payload header")
|
||||
}
|
||||
if parentHash == bid.ParentBlockHash() {
|
||||
// It's based on full, use the state by hash
|
||||
parentRoot = parentHash
|
||||
}
|
||||
}
|
||||
preState, err := s.cfg.StateGen.StateByRoot(ctx, parentRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not get pre state for slot %d", b.Slot())
|
||||
}
|
||||
@@ -438,7 +466,7 @@ func (s *Service) ancestorByDB(ctx context.Context, r [32]byte, slot primitives.
|
||||
|
||||
// This retrieves missing blocks from DB (ie. the blocks that couldn't be received over sync) and inserts them to fork choice store.
|
||||
// This is useful for block tree visualizer and additional vote accounting.
|
||||
func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, blk interfaces.ReadOnlyBeaconBlock,
|
||||
func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, signed interfaces.ReadOnlySignedBeaconBlock,
|
||||
fCheckpoint, jCheckpoint *ethpb.Checkpoint) error {
|
||||
pendingNodes := make([]*forkchoicetypes.BlockAndCheckpoints, 0)
|
||||
|
||||
@@ -448,10 +476,15 @@ func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, blk interfa
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pendingNodes = append(pendingNodes, &forkchoicetypes.BlockAndCheckpoints{Block: blk,
|
||||
// The first block can have a bogus root since the block is not inserted in forkchoice
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(signed, [32]byte{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pendingNodes = append(pendingNodes, &forkchoicetypes.BlockAndCheckpoints{Block: roblock,
|
||||
JustifiedCheckpoint: jCheckpoint, FinalizedCheckpoint: fCheckpoint})
|
||||
// As long as parent node is not in fork choice store, and parent node is in DB.
|
||||
root := blk.ParentRoot()
|
||||
root := roblock.Block().ParentRoot()
|
||||
for !s.cfg.ForkChoiceStore.HasNode(root) && s.cfg.BeaconDB.HasBlock(ctx, root) {
|
||||
b, err := s.getBlock(ctx, root)
|
||||
if err != nil {
|
||||
@@ -460,8 +493,12 @@ func (s *Service) fillInForkChoiceMissingBlocks(ctx context.Context, blk interfa
|
||||
if b.Block().Slot() <= fSlot {
|
||||
break
|
||||
}
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(b, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root = b.Block().ParentRoot()
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: b.Block(),
|
||||
args := &forkchoicetypes.BlockAndCheckpoints{Block: roblock,
|
||||
JustifiedCheckpoint: jCheckpoint,
|
||||
FinalizedCheckpoint: fCheckpoint}
|
||||
pendingNodes = append(pendingNodes, args)
|
||||
|
||||
@@ -140,14 +140,13 @@ func TestFillForkChoiceMissingBlocks_CanSave(t *testing.T) {
|
||||
// the parent of the last block inserted is the tree node.
|
||||
fcp := ðpb.Checkpoint{Epoch: 0, Root: service.originBlockRoot[:]}
|
||||
r0 := bytesutil.ToBytes32(roots[0])
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, fcp, fcp)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, [32]byte{}, fcp, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
fcp2 := &forkchoicetypes.Checkpoint{Epoch: 0, Root: r0}
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.UpdateFinalizedCheckpoint(fcp2))
|
||||
|
||||
err = service.fillInForkChoiceMissingBlocks(
|
||||
context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
context.Background(), wsb, beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
require.NoError(t, err)
|
||||
|
||||
// 5 nodes from the block tree 1. B0 - B3 - B4 - B6 - B8
|
||||
@@ -183,14 +182,14 @@ func TestFillForkChoiceMissingBlocks_RootsMatch(t *testing.T) {
|
||||
// the parent of the last block inserted is the tree node.
|
||||
fcp := ðpb.Checkpoint{Epoch: 0, Root: service.originBlockRoot[:]}
|
||||
r0 := bytesutil.ToBytes32(roots[0])
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, fcp, fcp)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, r0, service.originBlockRoot, [32]byte{}, [32]byte{}, fcp, fcp)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
fcp2 := &forkchoicetypes.Checkpoint{Epoch: 0, Root: r0}
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.UpdateFinalizedCheckpoint(fcp2))
|
||||
|
||||
err = service.fillInForkChoiceMissingBlocks(
|
||||
context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
context.Background(), wsb, beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
require.NoError(t, err)
|
||||
|
||||
// 5 nodes from the block tree 1. B0 - B3 - B4 - B6 - B8
|
||||
@@ -246,7 +245,7 @@ func TestFillForkChoiceMissingBlocks_FilterFinalized(t *testing.T) {
|
||||
// Set finalized epoch to 2.
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.UpdateFinalizedCheckpoint(&forkchoicetypes.Checkpoint{Epoch: 2, Root: r64}))
|
||||
err = service.fillInForkChoiceMissingBlocks(
|
||||
context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
context.Background(), wsb, beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
require.NoError(t, err)
|
||||
|
||||
// There should be 1 node: block 65
|
||||
@@ -279,7 +278,7 @@ func TestFillForkChoiceMissingBlocks_FinalizedSibling(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = service.fillInForkChoiceMissingBlocks(
|
||||
context.Background(), wsb.Block(), beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
context.Background(), wsb, beaconState.FinalizedCheckpoint(), beaconState.CurrentJustifiedCheckpoint())
|
||||
require.Equal(t, ErrNotDescendantOfFinalized.Error(), err.Error())
|
||||
}
|
||||
|
||||
@@ -463,7 +462,7 @@ func TestAncestor_CanUseForkchoice(t *testing.T) {
|
||||
beaconBlock.Block.ParentRoot = bytesutil.PadTo(b.Block.ParentRoot, 32)
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
st, blkRoot, err := prepareForkchoiceState(context.Background(), b.Block.Slot, r, bytesutil.ToBytes32(b.Block.ParentRoot), params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, blkRoot, err := prepareForkchoiceState(context.Background(), b.Block.Slot, r, bytesutil.ToBytes32(b.Block.ParentRoot), params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
}
|
||||
@@ -503,7 +502,7 @@ func TestAncestor_CanUseDB(t *testing.T) {
|
||||
util.SaveBlock(t, context.Background(), beaconDB, beaconBlock)
|
||||
}
|
||||
|
||||
st, blkRoot, err := prepareForkchoiceState(context.Background(), 200, r200, r200, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
st, blkRoot, err := prepareForkchoiceState(context.Background(), 200, r200, r200, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
|
||||
@@ -566,7 +565,9 @@ func TestOnBlock_CanFinalize_WithOnTick(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, r, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
require.NoError(t, service.updateJustificationOnBlock(ctx, preState, postState, currStoreJustifiedEpoch))
|
||||
_, err = service.updateFinalizationOnBlock(ctx, preState, postState, currStoreFinalizedEpoch)
|
||||
require.NoError(t, err)
|
||||
@@ -614,7 +615,9 @@ func TestOnBlock_CanFinalize(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, r, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
require.NoError(t, service.updateJustificationOnBlock(ctx, preState, postState, currStoreJustifiedEpoch))
|
||||
_, err = service.updateFinalizationOnBlock(ctx, preState, postState, currStoreFinalizedEpoch)
|
||||
require.NoError(t, err)
|
||||
@@ -640,7 +643,9 @@ func TestOnBlock_CanFinalize(t *testing.T) {
|
||||
|
||||
func TestOnBlock_NilBlock(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
err := service.postBlockProcess(&postBlockProcessConfig{tr.ctx, nil, [32]byte{}, [32]byte{}, nil, true})
|
||||
signed := &consensusblocks.SignedBeaconBlock{}
|
||||
roblock := consensusblocks.ROBlock{ReadOnlySignedBeaconBlock: signed}
|
||||
err := service.postBlockProcess(&postBlockProcessConfig{tr.ctx, roblock, [32]byte{}, nil, true})
|
||||
require.Equal(t, true, IsInvalidBlock(err))
|
||||
}
|
||||
|
||||
@@ -688,7 +693,9 @@ func TestOnBlock_CallNewPayloadAndForkchoiceUpdated(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, r, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, r, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
testState, err = service.cfg.StateGen.StateByRoot(ctx, r)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -1102,7 +1109,7 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
logHook := logTest.NewGlobal()
|
||||
for i := 0; i < 10; i++ {
|
||||
fc := ðpb.Checkpoint{}
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, wsb1.Block().ParentRoot(), [32]byte{}, [32]byte{}, fc, fc)
|
||||
st, blkRoot, err := prepareForkchoiceState(ctx, 0, wsb1.Block().ParentRoot(), [32]byte{}, [32]byte{}, [32]byte{}, fc, fc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blkRoot))
|
||||
var wg sync.WaitGroup
|
||||
@@ -1114,7 +1121,9 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb1)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb1, r1, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb1, r1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
lock.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
@@ -1124,7 +1133,9 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb2)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb2, r2, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb2, r2)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
lock.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
@@ -1134,7 +1145,9 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb3)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb3, r3, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb3, r3)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
lock.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
@@ -1144,7 +1157,9 @@ func TestOnBlock_ProcessBlocksParallel(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb4)
|
||||
require.NoError(t, err)
|
||||
lock.Lock()
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb4, r4, [32]byte{}, postState, true}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb4, r4)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
lock.Unlock()
|
||||
wg.Done()
|
||||
}()
|
||||
@@ -1219,7 +1234,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
}
|
||||
|
||||
for i := 6; i < 12; i++ {
|
||||
@@ -1237,7 +1254,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1256,7 +1275,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// Check that we haven't justified the second epoch yet
|
||||
@@ -1278,7 +1299,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, firstInvalidRoot, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, firstInvalidRoot, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
jc = service.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
require.Equal(t, primitives.Epoch(2), jc.Epoch)
|
||||
@@ -1306,7 +1329,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
// Check that forkchoice's head is the last invalid block imported. The
|
||||
// store's headroot is the previous head (since the invalid block did
|
||||
@@ -1335,7 +1360,9 @@ func TestStore_NoViableHead_FCU(t *testing.T) {
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, true})
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
require.NoError(t, err)
|
||||
// Check the newly imported block is head, it justified the right
|
||||
// checkpoint and the node is no longer optimistic
|
||||
@@ -1397,7 +1424,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
}
|
||||
|
||||
for i := 6; i < 12; i++ {
|
||||
@@ -1415,7 +1444,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1435,7 +1466,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// Check that we haven't justified the second epoch yet
|
||||
@@ -1457,7 +1490,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, firstInvalidRoot, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, firstInvalidRoot, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, firstInvalidRoot)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
jc = service.cfg.ForkChoiceStore.JustifiedCheckpoint()
|
||||
require.Equal(t, primitives.Epoch(2), jc.Epoch)
|
||||
@@ -1485,7 +1520,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
// Check that forkchoice's head and store's headroot are the previous head (since the invalid block did
|
||||
// not finish importing and it was never imported to forkchoice). Check
|
||||
@@ -1513,7 +1550,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) {
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, true})
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
require.NoError(t, err)
|
||||
// Check the newly imported block is head, it justified the right
|
||||
// checkpoint and the node is no longer optimistic
|
||||
@@ -1578,7 +1617,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
}
|
||||
|
||||
for i := 6; i < 12; i++ {
|
||||
@@ -1597,7 +1638,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1616,7 +1659,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, lastValidRoot, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
// save the post state and the payload Hash of this block since it will
|
||||
// be the LVH
|
||||
@@ -1643,7 +1688,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, invalidRoots[i-13], wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, invalidRoots[i-13], [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, invalidRoots[i-13])
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
// Check that we have justified the second epoch
|
||||
@@ -1669,7 +1716,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
// Check that forkchoice's head and store's headroot are the previous head (since the invalid block did
|
||||
@@ -1708,7 +1757,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, true}))
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true}))
|
||||
// Check that the head is still INVALID and the node is still optimistic
|
||||
require.Equal(t, invalidHeadRoot, service.cfg.ForkChoiceStore.CachedHeadRoot())
|
||||
optimistic, err = service.IsOptimistic(ctx)
|
||||
@@ -1731,7 +1782,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, true})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
require.NoError(t, err)
|
||||
st, err = service.cfg.StateGen.StateByRoot(ctx, root)
|
||||
require.NoError(t, err)
|
||||
@@ -1757,7 +1810,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) {
|
||||
postState, err = service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, true})
|
||||
roblock, err = consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, true})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, root, service.cfg.ForkChoiceStore.CachedHeadRoot())
|
||||
sjc = service.CurrentJustifiedCheckpt()
|
||||
@@ -1813,7 +1868,9 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
}
|
||||
|
||||
for i := 6; i < 12; i++ {
|
||||
@@ -1831,7 +1888,9 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -1850,7 +1909,9 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, lastValidRoot, wsb, postState))
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, lastValidRoot, [32]byte{}, postState, false})
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, lastValidRoot)
|
||||
require.NoError(t, err)
|
||||
err = service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false})
|
||||
require.NoError(t, err)
|
||||
// save the post state and the payload Hash of this block since it will
|
||||
// be the LVH
|
||||
@@ -1879,7 +1940,9 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
require.NoError(t, service.updateJustificationOnBlock(ctx, preState, postState, currStoreJustifiedEpoch))
|
||||
_, err = service.updateFinalizationOnBlock(ctx, preState, postState, currStoreFinalizedEpoch)
|
||||
require.NoError(t, err)
|
||||
@@ -1905,7 +1968,9 @@ func TestNoViableHead_Reboot(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root)
|
||||
rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
_, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb)
|
||||
require.ErrorContains(t, "received an INVALID payload from execution engine", err)
|
||||
|
||||
// Check that the headroot/state are not in DB and restart the node
|
||||
@@ -1995,7 +2060,9 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
|
||||
st, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -2059,7 +2126,9 @@ func TestOnBlock_HandleBlockAttestations(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, root, [32]byte{}, postState, false}))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
|
||||
st, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
@@ -2209,11 +2278,11 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, [32]byte{'a'})
|
||||
require.NoError(t, err)
|
||||
cfg := &postBlockProcessConfig{
|
||||
ctx: ctx,
|
||||
signed: wsb,
|
||||
blockRoot: [32]byte{'a'},
|
||||
roblock: roblock,
|
||||
postState: st,
|
||||
isValidPayload: true,
|
||||
}
|
||||
@@ -2223,11 +2292,64 @@ func Test_getFCUArgs(t *testing.T) {
|
||||
require.ErrorContains(t, "block does not exist", err)
|
||||
|
||||
// canonical branch
|
||||
cfg.headRoot = cfg.blockRoot
|
||||
cfg.headRoot = cfg.roblock.Root()
|
||||
fcuArgs = &fcuConfig{}
|
||||
err = s.getFCUArgs(cfg, fcuArgs)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cfg.blockRoot, fcuArgs.headRoot)
|
||||
require.Equal(t, cfg.roblock.Root(), fcuArgs.headRoot)
|
||||
}
|
||||
|
||||
func TestRollbackBlock(t *testing.T) {
|
||||
service, tr := minimalTestService(t)
|
||||
ctx := tr.ctx
|
||||
|
||||
st, keys := util.DeterministicGenesisState(t, 64)
|
||||
stateRoot, err := st.HashTreeRoot(ctx)
|
||||
require.NoError(t, err, "Could not hash genesis state")
|
||||
|
||||
require.NoError(t, service.saveGenesisData(ctx, st))
|
||||
|
||||
genesis := blocks.NewGenesisBlock(stateRoot[:])
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(genesis)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb), "Could not save genesis block")
|
||||
parentRoot, err := genesis.Block.HashTreeRoot()
|
||||
require.NoError(t, err, "Could not get signing root")
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, st, parentRoot), "Could not save genesis state")
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveHeadBlockRoot(ctx, parentRoot), "Could not save genesis state")
|
||||
|
||||
st, err = service.HeadState(ctx)
|
||||
require.NoError(t, err)
|
||||
b, err := util.GenerateFullBlock(st, keys, util.DefaultBlockGenConfig(), 1)
|
||||
require.NoError(t, err)
|
||||
wsb, err = consensusblocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
root, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
preState, err := service.getBlockPreState(ctx, wsb.Block())
|
||||
require.NoError(t, err)
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, root, wsb, postState))
|
||||
|
||||
require.Equal(t, true, service.cfg.BeaconDB.HasBlock(ctx, root))
|
||||
hasState, err := service.cfg.StateGen.HasState(ctx, root)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, hasState)
|
||||
|
||||
// Set invalid parent root to trigger forkchoice error.
|
||||
wsb.SetParentRoot([]byte("bad"))
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, root)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Rollback block insertion into db and caches.
|
||||
require.ErrorContains(t, fmt.Sprintf("could not insert block %d to fork choice store", roblock.Block().Slot()), service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
|
||||
// The block should no longer exist.
|
||||
require.Equal(t, false, service.cfg.BeaconDB.HasBlock(ctx, root))
|
||||
hasState, err = service.cfg.StateGen.HasState(ctx, root)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, false, hasState)
|
||||
}
|
||||
|
||||
func fakeCommitments(n int) [][]byte {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
payloadattribute "github.com/prysmaticlabs/prysm/v5/consensus-types/payload-attribute"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
@@ -147,15 +148,35 @@ func (s *Service) UpdateHead(ctx context.Context, proposingSlot primitives.Slot)
|
||||
return
|
||||
}
|
||||
newAttHeadElapsedTime.Observe(float64(time.Since(start).Milliseconds()))
|
||||
var attributes payloadattribute.Attributer
|
||||
if s.inRegularSync() {
|
||||
attributes = s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:])
|
||||
}
|
||||
if headState.Version() >= version.EPBS {
|
||||
bh, err := headState.LatestBlockHash()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not get latest block hash")
|
||||
return
|
||||
}
|
||||
_, err = s.notifyForkchoiceUpdateEPBS(ctx, [32]byte(bh), attributes)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not notify forkchoice update")
|
||||
}
|
||||
if err := s.saveHead(ctx, newHeadRoot, headBlock, headState); err != nil {
|
||||
log.WithError(err).Error("could not save head")
|
||||
return
|
||||
}
|
||||
if err := s.pruneAttsFromPool(headBlock); err != nil {
|
||||
log.WithError(err).Error("could not prune attestations from pool")
|
||||
}
|
||||
return
|
||||
}
|
||||
fcuArgs := &fcuConfig{
|
||||
headState: headState,
|
||||
headRoot: newHeadRoot,
|
||||
headBlock: headBlock,
|
||||
proposingSlot: proposingSlot,
|
||||
}
|
||||
if s.inRegularSync() {
|
||||
fcuArgs.attributes = s.getPayloadAttribute(ctx, headState, proposingSlot, newHeadRoot[:])
|
||||
}
|
||||
if fcuArgs.attributes != nil && s.shouldOverrideFCU(newHeadRoot, proposingSlot) {
|
||||
return
|
||||
}
|
||||
@@ -177,7 +198,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
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
forkchoicetypes "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
@@ -42,11 +43,11 @@ func TestVerifyLMDFFGConsistent(t *testing.T) {
|
||||
|
||||
f := service.cfg.ForkChoiceStore
|
||||
fc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, r32, err := prepareForkchoiceState(ctx, 32, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, fc, fc)
|
||||
state, r32, err := prepareForkchoiceState(ctx, 32, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, [32]byte{}, fc, fc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, r32))
|
||||
|
||||
state, r33, err := prepareForkchoiceState(ctx, 33, [32]byte{'b'}, r32, params.BeaconConfig().ZeroHash, fc, fc)
|
||||
state, r33, err := prepareForkchoiceState(ctx, 33, [32]byte{'b'}, r32.Root(), params.BeaconConfig().ZeroHash, [32]byte{}, fc, fc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, r33))
|
||||
|
||||
@@ -54,10 +55,12 @@ func TestVerifyLMDFFGConsistent(t *testing.T) {
|
||||
a := util.NewAttestation()
|
||||
a.Data.Target.Epoch = 1
|
||||
a.Data.Target.Root = []byte{'c'}
|
||||
a.Data.BeaconBlockRoot = r33[:]
|
||||
r33Root := r33.Root()
|
||||
a.Data.BeaconBlockRoot = r33Root[:]
|
||||
require.ErrorContains(t, wanted, service.VerifyLmdFfgConsistency(context.Background(), a))
|
||||
|
||||
a.Data.Target.Root = r32[:]
|
||||
r32Root := r32.Root()
|
||||
a.Data.Target.Root = r32Root[:]
|
||||
err = service.VerifyLmdFfgConsistency(context.Background(), a)
|
||||
require.NoError(t, err, "Could not verify LMD and FFG votes to be consistent")
|
||||
}
|
||||
@@ -80,7 +83,7 @@ func TestProcessAttestations_Ok(t *testing.T) {
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveState(ctx, copied, tRoot))
|
||||
ofc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
ojc := ðpb.Checkpoint{Root: params.BeaconConfig().ZeroHash[:]}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, ojc, ofc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, tRoot, tRoot, params.BeaconConfig().ZeroHash, [32]byte{}, ojc, ofc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
attsToSave := make([]ethpb.Att, len(atts))
|
||||
@@ -116,7 +119,9 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, tRoot, [32]byte{}, postState, false}))
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
copied, err = service.cfg.StateGen.StateByRoot(ctx, tRoot)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, fcs.NodeCount())
|
||||
@@ -142,7 +147,7 @@ func TestService_ProcessAttestationsAndUpdateHead(t *testing.T) {
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, service.cfg.BeaconDB, b)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, ojc, ojc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, [32]byte{}, ojc, ojc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
require.Equal(t, 3, fcs.NodeCount())
|
||||
@@ -176,7 +181,9 @@ func TestService_UpdateHead_NoAtts(t *testing.T) {
|
||||
postState, err := service.validateStateTransition(ctx, preState, wsb)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.savePostStateInfo(ctx, tRoot, wsb, postState))
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, wsb, tRoot, [32]byte{}, postState, false}))
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(wsb, tRoot)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.postBlockProcess(&postBlockProcessConfig{ctx, roblock, [32]byte{}, postState, false}))
|
||||
require.Equal(t, 2, fcs.NodeCount())
|
||||
require.NoError(t, service.cfg.BeaconDB.SaveBlock(ctx, wsb))
|
||||
require.Equal(t, tRoot, service.head.root)
|
||||
@@ -189,7 +196,7 @@ func TestService_UpdateHead_NoAtts(t *testing.T) {
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
util.SaveBlock(t, ctx, service.cfg.BeaconDB, b)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, ojc, ojc)
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 2, r, service.originBlockRoot, [32]byte{'b'}, [32]byte{}, ojc, ojc)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, state, blkRoot))
|
||||
require.Equal(t, 3, fcs.NodeCount())
|
||||
|
||||
@@ -17,6 +17,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/features"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
@@ -39,18 +41,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.ROSignedExecutionPayloadEnvelope, 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.ROSignedExecutionPayloadEnvelope, _ 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)
|
||||
@@ -83,13 +97,34 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
}
|
||||
|
||||
currentCheckpoints := s.saveCurrentCheckpoints(preState)
|
||||
postState, isValidPayload, err := s.validateExecutionAndConsensus(ctx, preState, blockCopy, blockRoot)
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(blockCopy, blockRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daWaitedTime, err := s.handleDA(ctx, blockCopy, blockRoot, avs)
|
||||
if err != nil {
|
||||
return err
|
||||
var postState state.BeaconState
|
||||
var isValidPayload bool
|
||||
var daWaitedTime time.Duration
|
||||
if blockCopy.Version() >= version.EPBS {
|
||||
postState, err = s.validateStateTransition(ctx, preState, roblock)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not validate state transition")
|
||||
}
|
||||
optimistic, err := s.IsOptimisticForRoot(ctx, roblock.Block().ParentRoot())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if parent is optimistic")
|
||||
}
|
||||
// if the parent is not optimistic then we can set the block as
|
||||
// not optimistic.
|
||||
isValidPayload = !optimistic
|
||||
} else {
|
||||
postState, isValidPayload, err = s.validateExecutionAndConsensus(ctx, preState, roblock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daWaitedTime, err = s.handleDA(ctx, blockCopy, blockRoot, avs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Defragment the state before continuing block processing.
|
||||
s.defragmentState(postState)
|
||||
@@ -102,8 +137,7 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig
|
||||
}
|
||||
args := &postBlockProcessConfig{
|
||||
ctx: ctx,
|
||||
signed: blockCopy,
|
||||
blockRoot: blockRoot,
|
||||
roblock: roblock,
|
||||
postState: postState,
|
||||
isValidPayload: isValidPayload,
|
||||
}
|
||||
@@ -184,8 +218,7 @@ func (s *Service) updateCheckpoints(
|
||||
func (s *Service) validateExecutionAndConsensus(
|
||||
ctx context.Context,
|
||||
preState state.BeaconState,
|
||||
block interfaces.SignedBeaconBlock,
|
||||
blockRoot [32]byte,
|
||||
block consensusblocks.ROBlock,
|
||||
) (state.BeaconState, bool, error) {
|
||||
preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState)
|
||||
if err != nil {
|
||||
@@ -204,7 +237,7 @@ func (s *Service) validateExecutionAndConsensus(
|
||||
var isValidPayload bool
|
||||
eg.Go(func() error {
|
||||
var err error
|
||||
isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, block, blockRoot)
|
||||
isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, block)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not notify the engine of the new payload")
|
||||
}
|
||||
@@ -555,16 +588,16 @@ func (s *Service) sendBlockAttestationsToSlasher(signed interfaces.ReadOnlySigne
|
||||
}
|
||||
|
||||
// validateExecutionOnBlock notifies the engine of the incoming block execution payload and returns true if the payload is valid
|
||||
func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, signed interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte) (bool, error) {
|
||||
isValidPayload, err := s.notifyNewPayload(ctx, ver, header, signed)
|
||||
func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, block consensusblocks.ROBlock) (bool, error) {
|
||||
isValidPayload, err := s.notifyNewPayload(ctx, ver, header, block)
|
||||
if err != nil {
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
err = s.handleInvalidExecutionError(ctx, err, blockRoot, signed.Block().ParentRoot())
|
||||
err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot())
|
||||
s.cfg.ForkChoiceStore.Unlock()
|
||||
return false, err
|
||||
}
|
||||
if signed.Version() < version.Capella && isValidPayload {
|
||||
if err := s.validateMergeTransitionBlock(ctx, ver, header, signed); err != nil {
|
||||
if block.Block().Version() < version.Capella && isValidPayload {
|
||||
if err := s.validateMergeTransitionBlock(ctx, ver, header, block); err != nil {
|
||||
return isValidPayload, err
|
||||
}
|
||||
}
|
||||
|
||||
244
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
244
beacon-chain/blockchain/receive_execution_payload_envelope.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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/core/transition"
|
||||
"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, signed interfaces.ROSignedExecutionPayloadEnvelope, _ das.AvailabilityStore) error {
|
||||
receivedTime := time.Now()
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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")
|
||||
}
|
||||
log.WithField("slot", preState.Slot()).Info("receiving envelope with prestate slot")
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
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
|
||||
}
|
||||
daStartTime := time.Now()
|
||||
// TODO: Add DA check
|
||||
daWaitedTime := time.Since(daStartTime)
|
||||
dataAvailWaitedTime.Observe(float64(daWaitedTime.Milliseconds()))
|
||||
if err := s.savePostPayload(ctx, signed, preState); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.insertPayloadEnvelope(envelope); err != nil {
|
||||
return errors.Wrap(err, "could not insert payload to forkchoice")
|
||||
}
|
||||
if isValidPayload {
|
||||
s.ForkChoicer().Lock()
|
||||
if err := s.ForkChoicer().SetOptimisticToValid(ctx, root); err != nil {
|
||||
s.ForkChoicer().Unlock()
|
||||
return errors.Wrap(err, "could not set optimistic payload to valid")
|
||||
}
|
||||
s.ForkChoicer().Unlock()
|
||||
}
|
||||
|
||||
headRoot, err := s.HeadRoot(ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not get headroot to compute attributes")
|
||||
return nil
|
||||
}
|
||||
logWithFields := logrus.WithFields(logrus.Fields{
|
||||
"blockRoot": fmt.Sprintf("%#x", root),
|
||||
"stateRoot": fmt.Sprintf("%#x", envelope.StateRoot()),
|
||||
"builder": envelope.BuilderIndex(),
|
||||
})
|
||||
if bytes.Equal(headRoot, root[:]) {
|
||||
slot := envelope.Slot()
|
||||
if slot == 0 {
|
||||
slot = preState.Slot()
|
||||
}
|
||||
attr := s.getPayloadAttribute(ctx, preState, slot+1, headRoot)
|
||||
execution, err := envelope.Execution()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("could not get execution data")
|
||||
return nil
|
||||
}
|
||||
blockHash := [32]byte(execution.BlockHash())
|
||||
payloadID, err := s.notifyForkchoiceUpdateEPBS(ctx, blockHash, attr)
|
||||
if err != nil {
|
||||
if IsInvalidBlock(err) {
|
||||
// TODO handle the lvh here
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if attr != nil && !attr.IsEmpty() && payloadID != nil {
|
||||
var pid [8]byte
|
||||
copy(pid[:], payloadID[:])
|
||||
log.WithFields(logrus.Fields{
|
||||
"blockRoot": fmt.Sprintf("%#x", bytesutil.Trunc(headRoot)),
|
||||
"headSlot": slot,
|
||||
"payloadID": fmt.Sprintf("%#x", bytesutil.Trunc(payloadID[:])),
|
||||
}).Info("Forkchoice updated with payload attributes for proposal")
|
||||
s.cfg.PayloadIDCache.Set(slot+1, root, pid)
|
||||
}
|
||||
// simply update the headstate in head
|
||||
s.headLock.Lock()
|
||||
s.head.state = preState.Copy()
|
||||
s.headLock.Unlock()
|
||||
// update the NSC with the hash for the full block
|
||||
if err := transition.UpdateNextSlotCache(ctx, blockHash[:], preState); err != nil {
|
||||
log.WithError(err).Error("could not update next slot cache with payload")
|
||||
}
|
||||
logWithFields = logWithFields.WithFields(logrus.Fields{
|
||||
"blockHash": fmt.Sprintf("%#x", blockHash),
|
||||
})
|
||||
}
|
||||
timeWithoutDaWait := time.Since(receivedTime) - daWaitedTime
|
||||
executionEngineProcessingTime.Observe(float64(timeWithoutDaWait.Milliseconds()))
|
||||
logWithFields.Info("Successfully processed execution payload envelope")
|
||||
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)
|
||||
requests := envelope.ExecutionRequests()
|
||||
lastValidHash, err := s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, &pr, requests)
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Service) savePostPayload(ctx context.Context, signed interfaces.ROSignedExecutionPayloadEnvelope, st state.BeaconState) error {
|
||||
if err := s.cfg.BeaconDB.SaveBlindPayloadEnvelope(ctx, signed); err != nil {
|
||||
return err
|
||||
}
|
||||
envelope, err := signed.Envelope()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
execution, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := envelope.BeaconBlockRoot()
|
||||
if err := s.cfg.StateGen.SaveState(ctx, [32]byte(execution.BlockHash()), st); err != nil {
|
||||
log.Warnf("Rolling back insertion of block with root %#x", r)
|
||||
if err := s.cfg.BeaconDB.DeleteBlock(ctx, r); err != nil {
|
||||
log.WithError(err).Errorf("Could not delete block with block root %#x", r)
|
||||
}
|
||||
return errors.Wrap(err, "could not save state")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
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),
|
||||
ExecutionRequests: &enginev1.ExecutionRequests{},
|
||||
}
|
||||
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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import (
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
@@ -65,6 +66,7 @@ type Service struct {
|
||||
syncComplete chan struct{}
|
||||
blobNotifiers *blobNotifierMap
|
||||
blockBeingSynced *currentlySyncingBlock
|
||||
payloadBeingSynced *currentlySyncingPayload
|
||||
blobStorage *filesystem.BlobStorage
|
||||
lastPublishedLightClientEpoch primitives.Epoch
|
||||
}
|
||||
@@ -75,6 +77,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 +183,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
blobNotifiers: bn,
|
||||
cfg: &config{},
|
||||
blockBeingSynced: ¤tlySyncingBlock{roots: make(map[[32]byte]struct{})},
|
||||
payloadBeingSynced: ¤tlySyncingPayload{roots: make(map[[32]byte]primitives.PTCStatus)},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
if err := opt(srv); err != nil {
|
||||
@@ -303,7 +308,15 @@ func (s *Service) StartFromSavedState(saved state.BeaconState) error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized checkpoint state")
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(s.ctx, st, fRoot); err != nil {
|
||||
finalizedBlock, err := s.cfg.BeaconDB.Block(s.ctx, fRoot)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get finalized checkpoint block")
|
||||
}
|
||||
roblock, err := consensus_blocks.NewROBlockWithRoot(finalizedBlock, fRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(s.ctx, st, roblock); err != nil {
|
||||
return errors.Wrap(err, "could not insert finalized block to forkchoice")
|
||||
}
|
||||
if !features.Get().EnableStartOptimistic {
|
||||
@@ -515,7 +528,11 @@ func (s *Service) saveGenesisData(ctx context.Context, genesisState state.Beacon
|
||||
|
||||
s.cfg.ForkChoiceStore.Lock()
|
||||
defer s.cfg.ForkChoiceStore.Unlock()
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(ctx, genesisState, genesisBlkRoot); err != nil {
|
||||
gb, err := consensus_blocks.NewROBlockWithRoot(genesisBlk, genesisBlkRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.cfg.ForkChoiceStore.InsertNode(ctx, genesisState, gb); err != nil {
|
||||
log.WithError(err).Fatal("Could not process genesis block for fork choice")
|
||||
}
|
||||
s.cfg.ForkChoiceStore.SetOriginRoot(genesisBlkRoot)
|
||||
@@ -542,7 +559,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
|
||||
}
|
||||
|
||||
@@ -376,14 +376,18 @@ func TestHasBlock_ForkChoiceAndDB_DoublyLinkedTree(t *testing.T) {
|
||||
cfg: &config{ForkChoiceStore: doublylinkedtree.New(), BeaconDB: beaconDB},
|
||||
}
|
||||
b := util.NewBeaconBlock()
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(t, err)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, r))
|
||||
require.NoError(t, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, roblock))
|
||||
|
||||
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) {
|
||||
@@ -453,7 +457,11 @@ func BenchmarkHasBlockForkChoiceStore_DoublyLinkedTree(b *testing.B) {
|
||||
require.NoError(b, err)
|
||||
beaconState, err := util.NewBeaconState()
|
||||
require.NoError(b, err)
|
||||
require.NoError(b, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, r))
|
||||
wsb, err := consensusblocks.NewSignedBeaconBlock(blk)
|
||||
require.NoError(b, err)
|
||||
roblock, err := consensusblocks.NewROBlockWithRoot(wsb, r)
|
||||
require.NoError(b, err)
|
||||
require.NoError(b, s.cfg.ForkChoiceStore.InsertNode(ctx, beaconState, roblock))
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@@ -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__",
|
||||
|
||||
@@ -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,7 +573,7 @@ func prepareForkchoiceState(
|
||||
payloadHash [32]byte,
|
||||
justified *ethpb.Checkpoint,
|
||||
finalized *ethpb.Checkpoint,
|
||||
) (state.BeaconState, [32]byte, error) {
|
||||
) (state.BeaconState, blocks.ROBlock, error) {
|
||||
blockHeader := ðpb.BeaconBlockHeader{
|
||||
ParentRoot: parentRoot[:],
|
||||
}
|
||||
@@ -588,7 +594,26 @@ func prepareForkchoiceState(
|
||||
|
||||
base.BlockRoots[0] = append(base.BlockRoots[0], blockRoot[:]...)
|
||||
st, err := state_native.InitializeFromProtoBellatrix(base)
|
||||
return st, blockRoot, err
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
blk := ðpb.SignedBeaconBlockBellatrix{
|
||||
Block: ðpb.BeaconBlockBellatrix{
|
||||
Slot: slot,
|
||||
ParentRoot: parentRoot[:],
|
||||
Body: ðpb.BeaconBlockBodyBellatrix{
|
||||
ExecutionPayload: &enginev1.ExecutionPayload{
|
||||
BlockHash: payloadHash[:],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
signed, err := blocks.NewSignedBeaconBlock(blk)
|
||||
if err != nil {
|
||||
return nil, blocks.ROBlock{}, err
|
||||
}
|
||||
roblock, err := blocks.NewROBlockWithRoot(signed, blockRoot)
|
||||
return st, roblock, err
|
||||
}
|
||||
|
||||
// CachedHeadRoot mocks the same method in the chain service
|
||||
@@ -622,18 +647,18 @@ 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
|
||||
func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, root [32]byte) error {
|
||||
func (s *ChainService) InsertNode(ctx context.Context, st state.BeaconState, block blocks.ROBlock) error {
|
||||
if s.ForkChoiceStore != nil {
|
||||
return s.ForkChoiceStore.InsertNode(ctx, st, root)
|
||||
return s.ForkChoiceStore.InsertNode(ctx, st, block)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -687,3 +712,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
|
||||
}
|
||||
|
||||
25
beacon-chain/blockchain/testing/mock_epbs.go
Normal file
25
beacon-chain/blockchain/testing/mock_epbs.go
Normal 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
|
||||
}
|
||||
8
beacon-chain/cache/BUILD.bazel
vendored
8
beacon-chain/cache/BUILD.bazel
vendored
@@ -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",
|
||||
|
||||
132
beacon-chain/cache/payload_attestation.go
vendored
Normal file
132
beacon-chain/cache/payload_attestation.go
vendored
Normal 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 := ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: bytesutil.SafeCopyBytes(att.Data.BeaconBlockRoot),
|
||||
Slot: att.Data.Slot,
|
||||
PayloadStatus: att.Data.PayloadStatus,
|
||||
}
|
||||
return ð.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{}
|
||||
}
|
||||
143
beacon-chain/cache/payload_attestation_test.go
vendored
Normal file
143
beacon-chain/cache/payload_attestation_test.go
vendored
Normal 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 := ð.PayloadAttestationMessage{
|
||||
Signature: bls.NewAggregateSignature().Marshal(),
|
||||
Data: ð.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 := ð.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 := ð.PayloadAttestationMessage{
|
||||
Signature: bls.NewAggregateSignature().Marshal(),
|
||||
Data: ð.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)
|
||||
})
|
||||
}
|
||||
76
beacon-chain/cache/signed_execution_header.go
vendored
Normal file
76
beacon-chain/cache/signed_execution_header.go
vendored
Normal 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
|
||||
}
|
||||
243
beacon-chain/cache/signed_execution_header_test.go
vendored
Normal file
243
beacon-chain/cache/signed_execution_header_test.go
vendored
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -187,11 +187,12 @@ func AddValidatorToRegistry(beaconState state.BeaconState, pubKey []byte, withdr
|
||||
// return Validator(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// effective_balance=effective_balance,
|
||||
// slashed=False,
|
||||
// activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
||||
// activation_epoch=FAR_FUTURE_EPOCH,
|
||||
// exit_epoch=FAR_FUTURE_EPOCH,
|
||||
// withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
// effective_balance=effective_balance,
|
||||
// )
|
||||
func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount uint64) *ethpb.Validator {
|
||||
effectiveBalance := amount - (amount % params.BeaconConfig().EffectiveBalanceIncrement)
|
||||
@@ -202,10 +203,11 @@ func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount
|
||||
return ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
EffectiveBalance: effectiveBalance,
|
||||
Slashed: false,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: effectiveBalance,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -222,6 +223,42 @@ func NewGenesisBlockForState(ctx context.Context, st state.BeaconState) (interfa
|
||||
},
|
||||
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(ðpb.SignedBeaconBlockEpbs{
|
||||
Block: ðpb.BeaconBlockEpbs{
|
||||
ParentRoot: params.BeaconConfig().ZeroHash[:],
|
||||
StateRoot: root[:],
|
||||
Body: ðpb.BeaconBlockBodyEpbs{
|
||||
RandaoReveal: make([]byte, 96),
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
},
|
||||
Graffiti: make([]byte, 32),
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncCommitteeLength/8),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
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[:],
|
||||
})
|
||||
default:
|
||||
return nil, ErrUnrecognizedState
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ func IsMergeTransitionComplete(st state.BeaconState) (bool, error) {
|
||||
//
|
||||
// return block.body.execution_payload != ExecutionPayload()
|
||||
func IsExecutionBlock(body interfaces.ReadOnlyBeaconBlockBody) (bool, error) {
|
||||
if body.Version() >= version.Capella {
|
||||
return true, nil
|
||||
}
|
||||
if body == nil {
|
||||
return false, errors.New("nil block body")
|
||||
}
|
||||
@@ -202,24 +205,24 @@ func ValidatePayload(st state.BeaconState, payload interfaces.ExecutionData) err
|
||||
// block_hash=payload.block_hash,
|
||||
// transactions_root=hash_tree_root(payload.transactions),
|
||||
// )
|
||||
func ProcessPayload(st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (state.BeaconState, error) {
|
||||
func ProcessPayload(st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
payload, err := body.Execution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := verifyBlobCommitmentCount(body); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := ValidatePayloadWhenMergeCompletes(st, payload); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := ValidatePayload(st, payload); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if err := st.SetLatestExecutionPayloadHeader(payload); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return st, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func verifyBlobCommitmentCount(body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
@@ -239,12 +242,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
|
||||
}
|
||||
|
||||
@@ -587,8 +587,7 @@ func Test_ProcessPayload(t *testing.T) {
|
||||
ExecutionPayload: tt.payload,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
st, err := blocks.ProcessPayload(st, body)
|
||||
if err != nil {
|
||||
if err := blocks.ProcessPayload(st, body); err != nil {
|
||||
require.Equal(t, tt.err.Error(), err.Error())
|
||||
} else {
|
||||
require.Equal(t, tt.err, err)
|
||||
@@ -619,8 +618,7 @@ func Test_ProcessPayloadCapella(t *testing.T) {
|
||||
ExecutionPayload: payload,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = blocks.ProcessPayload(st, body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, blocks.ProcessPayload(st, body))
|
||||
}
|
||||
|
||||
func Test_ProcessPayload_Blinded(t *testing.T) {
|
||||
@@ -677,8 +675,7 @@ func Test_ProcessPayload_Blinded(t *testing.T) {
|
||||
ExecutionPayloadHeader: p,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
st, err := blocks.ProcessPayload(st, body)
|
||||
if err != nil {
|
||||
if err := blocks.ProcessPayload(st, body); err != nil {
|
||||
require.Equal(t, tt.err.Error(), err.Error())
|
||||
} else {
|
||||
require.Equal(t, tt.err, err)
|
||||
|
||||
@@ -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,98 +115,66 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si
|
||||
return val, 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:
|
||||
// expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251]
|
||||
//
|
||||
// assert len(payload.withdrawals) == len(expected_withdrawals)
|
||||
//
|
||||
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
|
||||
// assert withdrawal == expected_withdrawal
|
||||
// decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
|
||||
//
|
||||
// # Update pending partial withdrawals [New in Electra:EIP7251]
|
||||
// state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:]
|
||||
//
|
||||
// # Update the next withdrawal index if this block contained withdrawals
|
||||
// if len(expected_withdrawals) != 0:
|
||||
// latest_withdrawal = expected_withdrawals[-1]
|
||||
// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
|
||||
//
|
||||
// # Update the next validator index to start the next withdrawal sweep
|
||||
// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
|
||||
// # Next sweep starts after the latest withdrawal's validator index
|
||||
// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
|
||||
// state.next_withdrawal_validator_index = next_validator_index
|
||||
// else:
|
||||
// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals
|
||||
// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
|
||||
// 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) {
|
||||
expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get expected withdrawals")
|
||||
}
|
||||
|
||||
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 nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
return errors.Wrap(err, "could not get withdrawals root")
|
||||
}
|
||||
wdRoot = bytesutil.ToBytes32(r)
|
||||
copy(wdRoot[:], r)
|
||||
} else {
|
||||
wds, err := executionData.Withdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get withdrawals")
|
||||
return 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))
|
||||
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 nil, errors.Wrap(err, "could not get withdrawals root")
|
||||
return 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)
|
||||
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 nil, errors.Wrap(err, "could not decrease balance")
|
||||
return errors.Wrap(err, "could not decrease balance")
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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 nil, errors.Wrap(err, "could not set next withdrawal index")
|
||||
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 nil, errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
return errors.Wrap(err, "could not get next withdrawal validator index")
|
||||
}
|
||||
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
|
||||
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
|
||||
@@ -217,7 +185,86 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution
|
||||
}
|
||||
}
|
||||
if err := st.SetNextWithdrawalValidatorIndex(nextValidatorIndex); err != nil {
|
||||
return nil, errors.Wrap(err, "could not set next withdrawal validator index")
|
||||
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]
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
// assert len(payload.withdrawals) == len(expected_withdrawals)
|
||||
//
|
||||
// for expected_withdrawal, withdrawal in zip(expected_withdrawals, payload.withdrawals):
|
||||
// assert withdrawal == expected_withdrawal
|
||||
// decrease_balance(state, withdrawal.validator_index, withdrawal.amount)
|
||||
//
|
||||
// # Update pending partial withdrawals [New in Electra:EIP7251]
|
||||
// state.pending_partial_withdrawals = state.pending_partial_withdrawals[processed_partial_withdrawals_count:]
|
||||
//
|
||||
// # Update the next withdrawal index if this block contained withdrawals
|
||||
// if len(expected_withdrawals) != 0:
|
||||
// latest_withdrawal = expected_withdrawals[-1]
|
||||
// state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1)
|
||||
//
|
||||
// # Update the next validator index to start the next withdrawal sweep
|
||||
// if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD:
|
||||
// # Next sweep starts after the latest withdrawal's validator index
|
||||
// next_validator_index = ValidatorIndex((expected_withdrawals[-1].validator_index + 1) % len(state.validators))
|
||||
// state.next_withdrawal_validator_index = next_validator_index
|
||||
// else:
|
||||
// # Advance sweep by the max length of the sweep if there was not a full set of withdrawals
|
||||
// next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP
|
||||
// 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 st, nil
|
||||
}
|
||||
}
|
||||
|
||||
expectedWithdrawals, partialWithdrawalsCount, err := st.ExpectedWithdrawals()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get expected withdrawals")
|
||||
}
|
||||
|
||||
expectedRoot, err := ssz.WithdrawalSliceRoot(expectedWithdrawals, fieldparams.MaxWithdrawalsPerPayload)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get expected withdrawals root")
|
||||
}
|
||||
|
||||
if st.Version() >= version.EPBS {
|
||||
err = st.SetLastWithdrawalsRoot(expectedRoot[:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not set withdrawals root")
|
||||
}
|
||||
} else {
|
||||
if err := checkWithdrawalsAgainstPayload(executionData, len(expectedWithdrawals), expectedRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := processWithdrawalStateTransition(st, expectedWithdrawals, partialWithdrawalsCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -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 := ðpb.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 := ðpb.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 := ðpb.BeaconStateCapella{
|
||||
Fork: ðpb.Fork{
|
||||
|
||||
@@ -10,7 +10,6 @@ go_library(
|
||||
"effective_balance_updates.go",
|
||||
"registry_updates.go",
|
||||
"transition.go",
|
||||
"transition_no_verify_sig.go",
|
||||
"upgrade.go",
|
||||
"validator.go",
|
||||
"withdrawals.go",
|
||||
|
||||
@@ -508,11 +508,12 @@ func AddValidatorToRegistry(beaconState state.BeaconState, pubKey []byte, withdr
|
||||
// validator = Validator(
|
||||
// pubkey=pubkey,
|
||||
// withdrawal_credentials=withdrawal_credentials,
|
||||
// effective_balance=Gwei(0),
|
||||
// slashed=False,
|
||||
// activation_eligibility_epoch=FAR_FUTURE_EPOCH,
|
||||
// activation_epoch=FAR_FUTURE_EPOCH,
|
||||
// exit_epoch=FAR_FUTURE_EPOCH,
|
||||
// withdrawable_epoch=FAR_FUTURE_EPOCH,
|
||||
// effective_balance=Gwei(0),
|
||||
// )
|
||||
//
|
||||
// # [Modified in Electra:EIP7251]
|
||||
@@ -524,11 +525,12 @@ func GetValidatorFromDeposit(pubKey []byte, withdrawalCredentials []byte, amount
|
||||
validator := ðpb.Validator{
|
||||
PublicKey: pubKey,
|
||||
WithdrawalCredentials: withdrawalCredentials,
|
||||
EffectiveBalance: 0,
|
||||
Slashed: false,
|
||||
ActivationEligibilityEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ActivationEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
WithdrawableEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
EffectiveBalance: 0,
|
||||
}
|
||||
v, err := state_native.NewValidator(validator)
|
||||
if err != nil {
|
||||
|
||||
52
beacon-chain/core/epbs/BUILD.bazel
Normal file
52
beacon-chain/core/epbs/BUILD.bazel
Normal file
@@ -0,0 +1,52 @@
|
||||
load("@prysm//tools/go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attestation.go",
|
||||
"execution_payload_envelope.go",
|
||||
"execution_payload_header.go",
|
||||
"operations.go",
|
||||
"payload_attestation.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//beacon-chain/core/altair:go_default_library",
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/electra:go_default_library",
|
||||
"//beacon-chain/core/helpers:go_default_library",
|
||||
"//beacon-chain/core/signing:go_default_library",
|
||||
"//beacon-chain/core/validators:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/interfaces:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/bls:go_default_library",
|
||||
"//network/forks:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots: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",
|
||||
],
|
||||
)
|
||||
13
beacon-chain/core/epbs/attestation.go
Normal file
13
beacon-chain/core/epbs/attestation.go
Normal 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
|
||||
}
|
||||
93
beacon-chain/core/epbs/attestation_test.go
Normal file
93
beacon-chain/core/epbs/attestation_test.go
Normal 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)
|
||||
}
|
||||
126
beacon-chain/core/epbs/execution_payload_envelope.go
Normal file
126
beacon-chain/core/epbs/execution_payload_envelope.go
Normal 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 := UpdateHeaderAndVerify(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 UpdateHeaderAndVerify(
|
||||
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
|
||||
}
|
||||
109
beacon-chain/core/epbs/execution_payload_envelope_test.go
Normal file
109
beacon-chain/core/epbs/execution_payload_envelope_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
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[:]
|
||||
p.ExecutionRequests = &enginev1.ExecutionRequests{}
|
||||
e, err := blocks.WrappedROExecutionPayloadEnvelope(p)
|
||||
require.NoError(t, err)
|
||||
validators := make([]*ethpb.Validator, 0)
|
||||
stpb := ðpb.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 := ðpb.BeaconStateEPBS{Slot: 3}
|
||||
st, err := state_native.InitializeFromProtoUnsafeEpbs(stpb)
|
||||
require.NoError(t, err)
|
||||
ctx := context.Background()
|
||||
require.ErrorContains(t, "invalid nil latest block header", UpdateHeaderAndVerify(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", UpdateHeaderAndVerify(ctx, prest, e))
|
||||
|
||||
header := prest.LatestBlockHeader()
|
||||
require.NoError(t, err)
|
||||
headerRoot, err := header.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
p.BeaconBlockRoot = headerRoot[:]
|
||||
require.NoError(t, UpdateHeaderAndVerify(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))
|
||||
}
|
||||
50
beacon-chain/core/epbs/execution_payload_header.go
Normal file
50
beacon-chain/core/epbs/execution_payload_header.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/crypto/bls"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/forks"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
// ValidatePayloadHeaderSignature validates the signature of the execution payload header.
|
||||
func ValidatePayloadHeaderSignature(st state.ReadOnlyBeaconState, sh interfaces.ROSignedExecutionPayloadHeader) error {
|
||||
h, err := sh.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pubkey := st.PubkeyAtIndex(h.BuilderIndex())
|
||||
pub, err := bls.PublicKeyFromBytes(pubkey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := sh.Signature()
|
||||
sig, err := bls.SignatureFromBytes(s[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentEpoch := slots.ToEpoch(h.Slot())
|
||||
f, err := forks.Fork(currentEpoch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
domain, err := signing.Domain(f, currentEpoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err := sh.SigningRoot(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !sig.Verify(pub, root[:]) {
|
||||
return signing.ErrSigFailedToVerify
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package electra
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,9 +6,11 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -62,11 +64,11 @@ func ProcessOperations(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process altair attester slashing")
|
||||
}
|
||||
st, err = ProcessAttestationsNoVerifySignature(ctx, st, block)
|
||||
st, err = electra.ProcessAttestationsNoVerifySignature(ctx, st, block)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process altair attestation")
|
||||
}
|
||||
if _, err := ProcessDeposits(ctx, st, bb.Deposits()); err != nil { // new in electra
|
||||
if _, err := electra.ProcessDeposits(ctx, st, bb.Deposits()); err != nil { // new in electra
|
||||
return nil, errors.Wrap(err, "could not process altair deposit")
|
||||
}
|
||||
st, err = ProcessVoluntaryExits(ctx, st, bb.VoluntaryExits())
|
||||
@@ -77,20 +79,27 @@ func ProcessOperations(
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process bls-to-execution changes")
|
||||
}
|
||||
// new in ePBS
|
||||
if block.Version() >= version.EPBS {
|
||||
if err := ProcessPayloadAttestations(st, bb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st, nil
|
||||
}
|
||||
// new in electra
|
||||
requests, err := bb.ExecutionRequests()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get execution requests")
|
||||
}
|
||||
st, err = ProcessDepositRequests(ctx, st, requests.Deposits)
|
||||
st, err = electra.ProcessDepositRequests(ctx, st, requests.Deposits)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process deposit receipts")
|
||||
}
|
||||
st, err = ProcessWithdrawalRequests(ctx, st, requests.Withdrawals)
|
||||
st, err = electra.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, requests.Consolidations); err != nil {
|
||||
if err := electra.ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil {
|
||||
return nil, fmt.Errorf("could not process consolidation requests: %w", err)
|
||||
}
|
||||
return st, nil
|
||||
125
beacon-chain/core/epbs/payload_attestation.go
Normal file
125
beacon-chain/core/epbs/payload_attestation.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package epbs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
)
|
||||
|
||||
func ProcessPayloadAttestations(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error {
|
||||
atts, err := body.PayloadAttestations()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(atts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
lbh := state.LatestBlockHeader()
|
||||
proposerIndex := lbh.ProposerIndex
|
||||
var participation []byte
|
||||
if state.Slot()%32 == 0 {
|
||||
participation, err = state.PreviousEpochParticipation()
|
||||
} else {
|
||||
participation, err = state.CurrentEpochParticipation()
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalBalance, err := helpers.TotalActiveBalance(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseReward, err := altair.BaseRewardWithTotalBalance(state, proposerIndex, totalBalance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lfs, err := state.LatestFullSlot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg := params.BeaconConfig()
|
||||
sourceFlagIndex := cfg.TimelySourceFlagIndex
|
||||
targetFlagIndex := cfg.TimelyTargetFlagIndex
|
||||
headFlagIndex := cfg.TimelyHeadFlagIndex
|
||||
penaltyNumerator := uint64(0)
|
||||
rewardNumerator := uint64(0)
|
||||
rewardDenominator := (cfg.WeightDenominator - cfg.ProposerWeight) * cfg.WeightDenominator / cfg.ProposerWeight
|
||||
|
||||
for _, att := range atts {
|
||||
data := att.Data
|
||||
if !bytes.Equal(data.BeaconBlockRoot, lbh.ParentRoot) {
|
||||
return errors.New("invalid beacon block root in payload attestation data")
|
||||
}
|
||||
if data.Slot+1 != state.Slot() {
|
||||
return errors.New("invalid data slot")
|
||||
}
|
||||
indexed, err := helpers.GetIndexedPayloadAttestation(ctx, state, data.Slot, att)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valid, err := helpers.IsValidIndexedPayloadAttestation(state, indexed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid {
|
||||
return errors.New("invalid payload attestation")
|
||||
}
|
||||
payloadWasPreset := data.Slot == lfs
|
||||
votedPresent := data.PayloadStatus == primitives.PAYLOAD_PRESENT
|
||||
if votedPresent != payloadWasPreset {
|
||||
for _, idx := range indexed.GetAttestingIndices() {
|
||||
flags := participation[idx]
|
||||
has, err := altair.HasValidatorFlag(flags, targetFlagIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
penaltyNumerator += baseReward * cfg.TimelyTargetWeight
|
||||
}
|
||||
has, err = altair.HasValidatorFlag(flags, sourceFlagIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
penaltyNumerator += baseReward * cfg.TimelySourceWeight
|
||||
}
|
||||
has, err = altair.HasValidatorFlag(flags, headFlagIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
penaltyNumerator += baseReward * cfg.TimelyHeadWeight
|
||||
}
|
||||
participation[idx] = 0
|
||||
}
|
||||
} else {
|
||||
for _, idx := range indexed.GetAttestingIndices() {
|
||||
participation[idx] = (1 << headFlagIndex) | (1 << sourceFlagIndex) | (1 << targetFlagIndex)
|
||||
rewardNumerator += baseReward * (cfg.TimelyHeadWeight + cfg.TimelySourceWeight + cfg.TimelyTargetWeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
if penaltyNumerator > 0 {
|
||||
if err := helpers.DecreaseBalance(state, proposerIndex, penaltyNumerator/rewardDenominator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rewardNumerator > 0 {
|
||||
if err := helpers.IncreaseBalance(state, proposerIndex, penaltyNumerator/rewardDenominator); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -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",
|
||||
@@ -69,21 +74,27 @@ go_test(
|
||||
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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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] = ðpb.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(ðpb.BeaconState{
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.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) {
|
||||
|
||||
12
beacon-chain/core/helpers/exports_test.go
Normal file
12
beacon-chain/core/helpers/exports_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package helpers
|
||||
|
||||
var (
|
||||
ErrNilMessage = errNilMessage
|
||||
ErrNilData = errNilData
|
||||
ErrNilBeaconBlockRoot = errNilBeaconBlockRoot
|
||||
ErrNilPayloadAttestation = errNilPayloadAttestation
|
||||
ErrNilSignature = errNilSignature
|
||||
ErrNilAggregationBits = errNilAggregationBits
|
||||
ErrPreEPBSState = errPreEPBSState
|
||||
ErrCommitteeOverflow = errCommitteeOverflow
|
||||
)
|
||||
296
beacon-chain/core/helpers/payload_attestation.go
Normal file
296
beacon-chain/core/helpers/payload_attestation.go
Normal 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
|
||||
}
|
||||
363
beacon-chain/core/helpers/payload_attestation_test.go
Normal file
363
beacon-chain/core/helpers/payload_attestation_test.go
Normal 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 := ð.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 := ð.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 := ð.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] = ðpb.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] = ðpb.Validator{
|
||||
PublicKey: pubkey,
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a beacon state.
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.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 := ð.PayloadAttestation{
|
||||
AggregationBits: aggregationBits,
|
||||
Data: ð.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] = ðpb.Validator{
|
||||
PublicKey: publicKey,
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a beacon state.
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.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 := ð.PayloadAttestation{
|
||||
AggregationBits: aggregationBits,
|
||||
Data: ð.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] = ðpb.Validator{
|
||||
PublicKey: secretKeys[i].PublicKey().Marshal(),
|
||||
WithdrawalCredentials: make([]byte, 32),
|
||||
ExitEpoch: params.BeaconConfig().FarFutureEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a beacon state.
|
||||
state, err := state_native.InitializeFromProtoEpbs(ðpb.BeaconStateEPBS{
|
||||
Validators: validators,
|
||||
Fork: ðpb.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: ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
},
|
||||
{
|
||||
attestation: &epbs.IndexedPayloadAttestation{
|
||||
AttestingIndices: []primitives.ValidatorIndex{13, 19},
|
||||
Data: ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
},
|
||||
{
|
||||
attestation: &epbs.IndexedPayloadAttestation{
|
||||
AttestingIndices: []primitives.ValidatorIndex{123, 234, 345},
|
||||
Data: ð.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: ð.PayloadAttestationData{
|
||||
BeaconBlockRoot: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
Signature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
},
|
||||
{
|
||||
attestation: &epbs.IndexedPayloadAttestation{
|
||||
AttestingIndices: []primitives.ValidatorIndex{5},
|
||||
Data: ð.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)
|
||||
}
|
||||
}
|
||||
@@ -108,3 +108,9 @@ func CanUpgradeToElectra(slot primitives.Slot) bool {
|
||||
func CanProcessEpoch(state state.ReadOnlyBeaconState) bool {
|
||||
return (state.Slot()+1)%params.BeaconConfig().SlotsPerEpoch == 0
|
||||
}
|
||||
|
||||
func CanUpgradeToEpbs(slot primitives.Slot) bool {
|
||||
epochStart := slots.IsEpochStart(slot)
|
||||
epbsEpoch := slots.ToEpoch(slot) == params.BeaconConfig().EPBSForkEpoch
|
||||
return epochStart && epbsEpoch
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ go_library(
|
||||
"state-bellatrix.go",
|
||||
"trailing_slot_state_cache.go",
|
||||
"transition.go",
|
||||
"transition_epbs.go",
|
||||
"transition_no_verify_sig.go",
|
||||
],
|
||||
importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition",
|
||||
@@ -20,6 +21,7 @@ go_library(
|
||||
"//beacon-chain/core/capella:go_default_library",
|
||||
"//beacon-chain/core/deneb:go_default_library",
|
||||
"//beacon-chain/core/electra:go_default_library",
|
||||
"//beacon-chain/core/epbs:go_default_library",
|
||||
"//beacon-chain/core/epoch:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/core/execution:go_default_library",
|
||||
@@ -45,6 +47,7 @@ go_library(
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus:go_default_library",
|
||||
"@com_github_prometheus_client_golang//prometheus/promauto:go_default_library",
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/execution"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
state_native "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"
|
||||
@@ -26,6 +27,8 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
|
||||
prysmTrace "github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
@@ -369,6 +372,15 @@ func UpgradeState(ctx context.Context, state state.BeaconState) (state.BeaconSta
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if time.CanUpgradeToEpbs(state.Slot()) {
|
||||
state, err = UpgradeToEpbs(state)
|
||||
if err != nil {
|
||||
tracing.AnnotateError(span, err)
|
||||
return nil, err
|
||||
}
|
||||
upgraded = true
|
||||
}
|
||||
|
||||
if upgraded {
|
||||
log.Debugf("upgraded state to %s", version.String(state.Version()))
|
||||
}
|
||||
@@ -487,3 +499,108 @@ func ProcessEpochPrecompute(ctx context.Context, state state.BeaconState) (state
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func UpgradeToEpbs(beaconState state.BeaconState) (state.BeaconState, error) {
|
||||
currentSyncCommittee, err := beaconState.CurrentSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextSyncCommittee, err := beaconState.NextSyncCommittee()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prevEpochParticipation, err := beaconState.PreviousEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
currentEpochParticipation, err := beaconState.CurrentEpochParticipation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inactivityScores, err := beaconState.InactivityScores()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wi, err := beaconState.NextWithdrawalIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vi, err := beaconState.NextWithdrawalValidatorIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
summaries, err := beaconState.HistoricalSummaries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
historicalRoots, err := beaconState.HistoricalRoots()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestHeader, err := beaconState.LatestExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := ðpb.BeaconStateEPBS{
|
||||
GenesisTime: beaconState.GenesisTime(),
|
||||
GenesisValidatorsRoot: beaconState.GenesisValidatorsRoot(),
|
||||
Slot: beaconState.Slot(),
|
||||
Fork: ðpb.Fork{
|
||||
PreviousVersion: beaconState.Fork().CurrentVersion,
|
||||
CurrentVersion: params.BeaconConfig().EPBSForkVersion,
|
||||
Epoch: time.CurrentEpoch(beaconState),
|
||||
},
|
||||
LatestBlockHeader: beaconState.LatestBlockHeader(),
|
||||
BlockRoots: beaconState.BlockRoots(),
|
||||
StateRoots: beaconState.StateRoots(),
|
||||
HistoricalRoots: historicalRoots,
|
||||
Eth1Data: beaconState.Eth1Data(),
|
||||
Eth1DataVotes: beaconState.Eth1DataVotes(),
|
||||
Eth1DepositIndex: beaconState.Eth1DepositIndex(),
|
||||
Validators: beaconState.Validators(),
|
||||
Balances: beaconState.Balances(),
|
||||
RandaoMixes: beaconState.RandaoMixes(),
|
||||
Slashings: beaconState.Slashings(),
|
||||
PreviousEpochParticipation: prevEpochParticipation,
|
||||
CurrentEpochParticipation: currentEpochParticipation,
|
||||
JustificationBits: beaconState.JustificationBits(),
|
||||
PreviousJustifiedCheckpoint: beaconState.PreviousJustifiedCheckpoint(),
|
||||
CurrentJustifiedCheckpoint: beaconState.CurrentJustifiedCheckpoint(),
|
||||
FinalizedCheckpoint: beaconState.FinalizedCheckpoint(),
|
||||
InactivityScores: inactivityScores,
|
||||
CurrentSyncCommittee: currentSyncCommittee,
|
||||
NextSyncCommittee: nextSyncCommittee,
|
||||
NextWithdrawalIndex: wi,
|
||||
NextWithdrawalValidatorIndex: vi,
|
||||
HistoricalSummaries: summaries,
|
||||
|
||||
// TODO: fix this
|
||||
DepositRequestsStartIndex: 0,
|
||||
DepositBalanceToConsume: 0,
|
||||
ExitBalanceToConsume: 0,
|
||||
EarliestExitEpoch: 0,
|
||||
ConsolidationBalanceToConsume: 0,
|
||||
EarliestConsolidationEpoch: 0,
|
||||
PendingDeposits: make([]*ethpb.PendingDeposit, 0),
|
||||
PendingPartialWithdrawals: make([]*ethpb.PendingPartialWithdrawal, 0),
|
||||
PendingConsolidations: make([]*ethpb.PendingConsolidation, 0),
|
||||
LatestExecutionPayloadHeader: &v1.ExecutionPayloadHeaderEPBS{
|
||||
ParentBlockHash: make([]byte, 32),
|
||||
ParentBlockRoot: make([]byte, 32),
|
||||
BlockHash: make([]byte, 32),
|
||||
BlobKzgCommitmentsRoot: make([]byte, 32),
|
||||
},
|
||||
LatestBlockHash: latestHeader.BlockHash(),
|
||||
LatestFullSlot: beaconState.Slot(),
|
||||
LastWithdrawalsRoot: make([]byte, 32),
|
||||
}
|
||||
|
||||
post, err := state_native.InitializeFromProtoUnsafeEpbs(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize post electra beaconState")
|
||||
}
|
||||
|
||||
return post, nil
|
||||
}
|
||||
|
||||
111
beacon-chain/core/transition/transition_epbs.go
Normal file
111
beacon-chain/core/transition/transition_epbs.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package transition
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func processExecution(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (err error) {
|
||||
if body.Version() >= version.EPBS {
|
||||
state, err = blocks.ProcessWithdrawals(state, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
return processExecutionPayloadHeader(state, body)
|
||||
}
|
||||
enabled, err := blocks.IsExecutionEnabled(state, body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not check if execution is enabled")
|
||||
}
|
||||
if !enabled {
|
||||
return nil
|
||||
}
|
||||
executionData, err := body.Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.Version() >= version.Capella {
|
||||
state, err = blocks.ProcessWithdrawals(state, executionData)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
}
|
||||
if err := blocks.ProcessPayload(state, body); err != nil {
|
||||
return errors.Wrap(err, "could not process execution data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function verifies the signature as it is not necessarily signed by the
|
||||
// proposer
|
||||
func processExecutionPayloadHeader(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (err error) {
|
||||
sh, err := body.SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header, err := sh.Header()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := epbs.ValidatePayloadHeaderSignature(state, sh); err != nil {
|
||||
return err
|
||||
}
|
||||
builderIndex := header.BuilderIndex()
|
||||
builder, err := state.ValidatorAtIndex(builderIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
epoch := slots.ToEpoch(state.Slot())
|
||||
if builder.ActivationEpoch > epoch || epoch >= builder.ExitEpoch {
|
||||
return errors.New("builder is not active")
|
||||
}
|
||||
if builder.Slashed {
|
||||
return errors.New("builder is slashed")
|
||||
}
|
||||
amount := header.Value()
|
||||
builderBalance, err := state.BalanceAtIndex(builderIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if amount > primitives.Gwei(builderBalance) {
|
||||
return errors.New("builder has insufficient balance")
|
||||
}
|
||||
// sate.Slot == block.Slot because of process_slot
|
||||
if header.Slot() != state.Slot() {
|
||||
return errors.New("incorrect header slot")
|
||||
}
|
||||
// the state latest block header has the parent root because of
|
||||
// process_block_header
|
||||
blockHeader := state.LatestBlockHeader()
|
||||
if header.ParentBlockRoot() != [32]byte(blockHeader.ParentRoot) {
|
||||
return errors.New(fmt.Sprintf("incorrect parent block root, wanted: %#x, got: %#x", blockHeader.ParentRoot, header.ParentBlockRoot()))
|
||||
}
|
||||
lbh, err := state.LatestBlockHash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if header.ParentBlockHash() != [32]byte(lbh) {
|
||||
return errors.New(fmt.Sprintf("incorrect parent block hash, wanted: %#x, got: %#x", lbh, header.ParentBlockHash()))
|
||||
}
|
||||
if err := state.UpdateBalancesAtIndex(builderIndex, builderBalance-uint64(amount)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := helpers.IncreaseBalance(state, blockHeader.ProposerIndex, uint64(amount)); err != nil {
|
||||
return err
|
||||
}
|
||||
headerEPBS, ok := header.Proto().(*enginev1.ExecutionPayloadHeaderEPBS)
|
||||
if !ok {
|
||||
return errors.New("not an ePBS execution payload header")
|
||||
}
|
||||
return state.SetLatestExecutionPayloadHeaderEPBS(headerEPBS)
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair"
|
||||
b "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition/interop"
|
||||
v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
@@ -132,7 +132,27 @@ func CalculateStateRoot(
|
||||
|
||||
// Execute per slots transition.
|
||||
var err error
|
||||
parentRoot := signed.Block().ParentRoot()
|
||||
var parentRoot [32]byte
|
||||
// check if based on empty or full
|
||||
if state.Version() >= version.EPBS {
|
||||
signedHeader, err := signed.Block().Body().SignedExecutionPayloadHeader()
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not retrieve signed execution payload header")
|
||||
}
|
||||
header, err := signedHeader.Header()
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not retrieve execution payload header")
|
||||
}
|
||||
lbh, err := state.LatestBlockHash()
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not get state latest block hash")
|
||||
}
|
||||
if header.ParentBlockHash() == [32]byte(lbh) {
|
||||
parentRoot = [32]byte(lbh)
|
||||
} else {
|
||||
parentRoot = signed.Block().ParentRoot()
|
||||
}
|
||||
}
|
||||
state, err = ProcessSlotsUsingNextSlotCache(ctx, state, parentRoot[:], signed.Block().Slot())
|
||||
if err != nil {
|
||||
return [32]byte{}, errors.Wrap(err, "could not process slots")
|
||||
@@ -272,7 +292,7 @@ func ProcessOperationsNoVerifyAttsSigs(
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
state, err = electra.ProcessOperations(ctx, state, beaconBlock)
|
||||
state, err = epbs.ProcessOperations(ctx, state, beaconBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -318,27 +338,9 @@ func ProcessBlockForStateRoot(
|
||||
return nil, errors.Wrap(err, "could not process block header")
|
||||
}
|
||||
|
||||
enabled, err := b.IsExecutionEnabled(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not check if execution is enabled")
|
||||
if err := processExecution(state, blk.Body()); err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution")
|
||||
}
|
||||
if enabled {
|
||||
executionData, err := blk.Body().Execution()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if state.Version() >= version.Capella {
|
||||
state, err = b.ProcessWithdrawals(state, executionData)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process withdrawals")
|
||||
}
|
||||
}
|
||||
state, err = b.ProcessPayload(state, blk.Body())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not process execution data")
|
||||
}
|
||||
}
|
||||
|
||||
randaoReveal := signed.Block().Body().RandaoReveal()
|
||||
state, err = b.ProcessRandaoNoVerify(state, randaoReveal[:])
|
||||
if err != nil {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -241,76 +241,35 @@ func SlashedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validat
|
||||
return slashed
|
||||
}
|
||||
|
||||
// ExitedValidatorIndices determines the indices exited during the current epoch.
|
||||
func ExitedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator, activeValidatorCount uint64) ([]primitives.ValidatorIndex, error) {
|
||||
// ExitedValidatorIndices returns the indices of validators who exited during the specified epoch.
|
||||
//
|
||||
// A validator is considered to have exited during an epoch if their ExitEpoch equals the epoch and
|
||||
// excludes validators that have been ejected.
|
||||
// This function simplifies the exit determination by directly checking the validator's ExitEpoch,
|
||||
// avoiding the complexities and potential inaccuracies of calculating withdrawable epochs.
|
||||
func ExitedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator) ([]primitives.ValidatorIndex, error) {
|
||||
exited := make([]primitives.ValidatorIndex, 0)
|
||||
exitEpochs := make([]primitives.Epoch, 0)
|
||||
for i := 0; i < len(validators); i++ {
|
||||
val := validators[i]
|
||||
if val.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
|
||||
exitEpochs = append(exitEpochs, val.ExitEpoch)
|
||||
}
|
||||
}
|
||||
exitQueueEpoch := primitives.Epoch(0)
|
||||
for _, i := range exitEpochs {
|
||||
if exitQueueEpoch < i {
|
||||
exitQueueEpoch = i
|
||||
}
|
||||
}
|
||||
|
||||
// We use the exit queue churn to determine if we have passed a churn limit.
|
||||
exitQueueChurn := uint64(0)
|
||||
for _, val := range validators {
|
||||
if val.ExitEpoch == exitQueueEpoch {
|
||||
exitQueueChurn++
|
||||
}
|
||||
}
|
||||
churn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
|
||||
if churn < exitQueueChurn {
|
||||
exitQueueEpoch++
|
||||
}
|
||||
withdrawableEpoch := exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
||||
for i, val := range validators {
|
||||
if val.ExitEpoch == epoch && val.WithdrawableEpoch == withdrawableEpoch &&
|
||||
val.EffectiveBalance > params.BeaconConfig().EjectionBalance {
|
||||
if val.ExitEpoch == epoch && val.EffectiveBalance > params.BeaconConfig().EjectionBalance {
|
||||
exited = append(exited, primitives.ValidatorIndex(i))
|
||||
}
|
||||
}
|
||||
return exited, nil
|
||||
}
|
||||
|
||||
// EjectedValidatorIndices determines the indices ejected during the given epoch.
|
||||
func EjectedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator, activeValidatorCount uint64) ([]primitives.ValidatorIndex, error) {
|
||||
// EjectedValidatorIndices returns the indices of validators who were ejected during the specified epoch.
|
||||
//
|
||||
// A validator is considered ejected during an epoch if:
|
||||
// - Their ExitEpoch equals the epoch.
|
||||
// - Their EffectiveBalance is less than or equal to the EjectionBalance threshold.
|
||||
//
|
||||
// This function simplifies the ejection determination by directly checking the validator's ExitEpoch
|
||||
// and EffectiveBalance, avoiding the complexities and potential inaccuracies of calculating
|
||||
// withdrawable epochs.
|
||||
func EjectedValidatorIndices(epoch primitives.Epoch, validators []*ethpb.Validator) ([]primitives.ValidatorIndex, error) {
|
||||
ejected := make([]primitives.ValidatorIndex, 0)
|
||||
exitEpochs := make([]primitives.Epoch, 0)
|
||||
for i := 0; i < len(validators); i++ {
|
||||
val := validators[i]
|
||||
if val.ExitEpoch != params.BeaconConfig().FarFutureEpoch {
|
||||
exitEpochs = append(exitEpochs, val.ExitEpoch)
|
||||
}
|
||||
}
|
||||
exitQueueEpoch := primitives.Epoch(0)
|
||||
for _, i := range exitEpochs {
|
||||
if exitQueueEpoch < i {
|
||||
exitQueueEpoch = i
|
||||
}
|
||||
}
|
||||
|
||||
// We use the exit queue churn to determine if we have passed a churn limit.
|
||||
exitQueueChurn := uint64(0)
|
||||
for _, val := range validators {
|
||||
if val.ExitEpoch == exitQueueEpoch {
|
||||
exitQueueChurn++
|
||||
}
|
||||
}
|
||||
churn := helpers.ValidatorExitChurnLimit(activeValidatorCount)
|
||||
if churn < exitQueueChurn {
|
||||
exitQueueEpoch++
|
||||
}
|
||||
withdrawableEpoch := exitQueueEpoch + params.BeaconConfig().MinValidatorWithdrawabilityDelay
|
||||
for i, val := range validators {
|
||||
if val.ExitEpoch == epoch && val.WithdrawableEpoch == withdrawableEpoch &&
|
||||
val.EffectiveBalance <= params.BeaconConfig().EjectionBalance {
|
||||
if val.ExitEpoch == epoch && val.EffectiveBalance <= params.BeaconConfig().EjectionBalance {
|
||||
ejected = append(ejected, primitives.ValidatorIndex(i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,19 +389,16 @@ func TestExitedValidatorIndices(t *testing.T) {
|
||||
state: ðpb.BeaconState{
|
||||
Validators: []*ethpb.Validator{
|
||||
{
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 0,
|
||||
WithdrawableEpoch: params.BeaconConfig().MinValidatorWithdrawabilityDelay,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 0,
|
||||
},
|
||||
{
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 0,
|
||||
WithdrawableEpoch: 10,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 10,
|
||||
},
|
||||
{
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 0,
|
||||
WithdrawableEpoch: params.BeaconConfig().MinValidatorWithdrawabilityDelay,
|
||||
EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance,
|
||||
ExitEpoch: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -433,11 +430,7 @@ func TestExitedValidatorIndices(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
s, err := state_native.InitializeFromProtoPhase0(tt.state)
|
||||
require.NoError(t, err)
|
||||
activeCount, err := helpers.ActiveValidatorCount(context.Background(), s, time.PrevEpoch(s))
|
||||
require.NoError(t, err)
|
||||
exitedIndices, err := validators.ExitedValidatorIndices(0, tt.state.Validators, activeCount)
|
||||
exitedIndices, err := validators.ExitedValidatorIndices(0, tt.state.Validators)
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, tt.wanted, exitedIndices)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ go_library(
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//monitoring/backup:go_default_library",
|
||||
"//proto/dbval:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"@com_github_ethereum_go_ethereum//common:go_default_library",
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/backup"
|
||||
"github.com/prysmaticlabs/prysm/v5/proto/dbval"
|
||||
engine "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
)
|
||||
|
||||
@@ -25,11 +26,13 @@ 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)
|
||||
BlockRootsBySlot(ctx context.Context, slot primitives.Slot) (bool, [][32]byte, error)
|
||||
HasBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
HasBlindPayloadEnvelope(ctx context.Context, hash [32]byte) bool
|
||||
GenesisBlock(ctx context.Context) (interfaces.ReadOnlySignedBeaconBlock, error)
|
||||
GenesisBlockRoot(ctx context.Context) ([32]byte, error)
|
||||
IsFinalizedBlock(ctx context.Context, blockRoot [32]byte) bool
|
||||
@@ -55,6 +58,7 @@ type ReadOnlyDatabase interface {
|
||||
DepositContractAddress(ctx context.Context) ([]byte, error)
|
||||
// ExecutionChainData operations.
|
||||
ExecutionChainData(ctx context.Context) (*ethpb.ETH1ChainData, error)
|
||||
SignedBlindPayloadEnvelope(ctx context.Context, blockHash []byte) (*engine.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 +96,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 interfaces.ROSignedExecutionPayloadEnvelope) error
|
||||
// Run any required database migrations.
|
||||
RunMigrations(ctx context.Context) error
|
||||
// Fee recipients operations.
|
||||
|
||||
@@ -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",
|
||||
@@ -53,6 +55,7 @@ go_library(
|
||||
"//monitoring/tracing:go_default_library",
|
||||
"//monitoring/tracing/trace:go_default_library",
|
||||
"//proto/dbval:go_default_library",
|
||||
"//proto/engine/v1:go_default_library",
|
||||
"//proto/eth/v2:go_default_library",
|
||||
"//proto/prysm/v1alpha1:go_default_library",
|
||||
"//runtime/version:go_default_library",
|
||||
@@ -70,6 +73,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 +84,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 +130,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",
|
||||
|
||||
76
beacon-chain/db/kv/blind_payload_envelope.go
Normal file
76
beacon-chain/db/kv/blind_payload_envelope.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package kv
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
engine "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
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, signed interfaces.ROSignedExecutionPayloadEnvelope) error {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.SaveBlindPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
pb := signed.Proto()
|
||||
if pb == nil {
|
||||
return errors.New("nil payload envelope")
|
||||
}
|
||||
env, ok := pb.(*engine.SignedExecutionPayloadEnvelope)
|
||||
if !ok {
|
||||
return errors.New("invalid payload envelope")
|
||||
}
|
||||
|
||||
r := env.Message.Payload.BlockHash
|
||||
blind := env.Blind()
|
||||
if blind == nil {
|
||||
return errors.New("nil blind payload envelope")
|
||||
}
|
||||
enc, err := encode(ctx, blind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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, blockHash []byte) (*engine.SignedBlindPayloadEnvelope, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "BeaconDB.SignedBlindPayloadEnvelope")
|
||||
defer span.End()
|
||||
|
||||
env := &engine.SignedBlindPayloadEnvelope{}
|
||||
err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(executionPayloadEnvelopeBucket)
|
||||
enc := bkt.Get(blockHash)
|
||||
if enc == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
return decode(ctx, enc, env)
|
||||
})
|
||||
return env, err
|
||||
}
|
||||
|
||||
func (s *Store) HasBlindPayloadEnvelope(ctx context.Context, hash [32]byte) bool {
|
||||
_, span := trace.StartSpan(ctx, "BeaconDB.HasBlock")
|
||||
defer span.End()
|
||||
if v, ok := s.blockCache.Get(string(hash[:])); v != nil && ok {
|
||||
return true
|
||||
}
|
||||
exists := false
|
||||
if err := s.db.View(func(tx *bolt.Tx) error {
|
||||
bkt := tx.Bucket(executionPayloadEnvelopeBucket)
|
||||
exists = bkt.Get(hash[:]) != nil
|
||||
return nil
|
||||
}); err != nil { // This view never returns an error, but we'll handle anyway for sanity.
|
||||
panic(err)
|
||||
}
|
||||
return exists
|
||||
}
|
||||
23
beacon-chain/db/kv/blind_payload_envelope_test.go
Normal file
23
beacon-chain/db/kv/blind_payload_envelope_test.go
Normal 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)
|
||||
}
|
||||
@@ -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 = ðpb.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 = ðpb.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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/golang/snappy"
|
||||
fastssz "github.com/prysmaticlabs/fastssz"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
engine "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -78,6 +79,8 @@ func isSSZStorageFormat(obj interface{}) bool {
|
||||
return true
|
||||
case *ethpb.VoluntaryExit:
|
||||
return true
|
||||
case *engine.SignedBlindPayloadEnvelope:
|
||||
return true
|
||||
case *ethpb.ValidatorRegistrationV1:
|
||||
return true
|
||||
default:
|
||||
|
||||
18
beacon-chain/db/kv/epbs.go
Normal file
18
beacon-chain/db/kv/epbs.go
Normal 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()
|
||||
}
|
||||
28
beacon-chain/db/kv/epbs_test.go
Normal file
28
beacon-chain/db/kv/epbs_test.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -119,6 +119,9 @@ var Buckets = [][]byte{
|
||||
|
||||
feeRecipientBucket,
|
||||
registrationBucket,
|
||||
|
||||
// ePBS
|
||||
executionPayloadEnvelopeBucket,
|
||||
}
|
||||
|
||||
// KVStoreOption is a functional option that modifies a kv.Store.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
@@ -252,6 +253,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 +372,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 +539,19 @@ func (s *Store) unmarshalState(_ context.Context, enc []byte, validatorEntries [
|
||||
}
|
||||
|
||||
switch {
|
||||
case hasEpbsKey(enc):
|
||||
protoState := ðpb.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 := ðpb.BeaconStateElectra{}
|
||||
if err := protoState.UnmarshalSSZ(enc[len(electraKey):]); err != nil {
|
||||
@@ -603,14 +639,14 @@ func (s *Store) unmarshalState(_ context.Context, enc []byte, validatorEntries [
|
||||
|
||||
// marshal versioned state from struct type down to bytes.
|
||||
func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, error) {
|
||||
switch st.ToProtoUnsafe().(type) {
|
||||
case *ethpb.BeaconState:
|
||||
switch st.Version() {
|
||||
case version.Phase0:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconState)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
}
|
||||
return encode(ctx, rState)
|
||||
case *ethpb.BeaconStateAltair:
|
||||
case version.Altair:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateAltair)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
@@ -623,7 +659,7 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
|
||||
return nil, err
|
||||
}
|
||||
return snappy.Encode(nil, append(altairKey, rawObj...)), nil
|
||||
case *ethpb.BeaconStateBellatrix:
|
||||
case version.Bellatrix:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateBellatrix)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
@@ -636,7 +672,7 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
|
||||
return nil, err
|
||||
}
|
||||
return snappy.Encode(nil, append(bellatrixKey, rawObj...)), nil
|
||||
case *ethpb.BeaconStateCapella:
|
||||
case version.Capella:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateCapella)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
@@ -649,7 +685,7 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
|
||||
return nil, err
|
||||
}
|
||||
return snappy.Encode(nil, append(capellaKey, rawObj...)), nil
|
||||
case *ethpb.BeaconStateDeneb:
|
||||
case version.Deneb:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateDeneb)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
@@ -662,7 +698,7 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
|
||||
return nil, err
|
||||
}
|
||||
return snappy.Encode(nil, append(denebKey, rawObj...)), nil
|
||||
case *ethpb.BeaconStateElectra:
|
||||
case version.Electra:
|
||||
rState, ok := st.ToProtoUnsafe().(*ethpb.BeaconStateElectra)
|
||||
if !ok {
|
||||
return nil, errors.New("non valid inner state")
|
||||
@@ -675,6 +711,19 @@ func marshalState(ctx context.Context, st state.ReadOnlyBeaconState) ([]byte, er
|
||||
return nil, err
|
||||
}
|
||||
return snappy.Encode(nil, append(electraKey, rawObj...)), nil
|
||||
case version.EPBS:
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -156,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)
|
||||
@@ -1110,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)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ go_library(
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//beacon-chain/state/state-native:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/verification:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
@@ -105,8 +106,11 @@ go_test(
|
||||
"//beacon-chain/db/testing:go_default_library",
|
||||
"//beacon-chain/execution/testing:go_default_library",
|
||||
"//beacon-chain/execution/types:go_default_library",
|
||||
"//beacon-chain/forkchoice:go_default_library",
|
||||
"//beacon-chain/forkchoice/doubly-linked-tree:go_default_library",
|
||||
"//beacon-chain/startup:go_default_library",
|
||||
"//beacon-chain/state/stategen:go_default_library",
|
||||
"//beacon-chain/verification:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//config/params:go_default_library",
|
||||
"//consensus-types/blocks:go_default_library",
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
"github.com/prysmaticlabs/prysm/v5/monitoring/tracing/trace"
|
||||
pb "github.com/prysmaticlabs/prysm/v5/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v5/runtime/version"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -79,6 +81,8 @@ const (
|
||||
GetPayloadBodiesByRangeV1 = "engine_getPayloadBodiesByRangeV1"
|
||||
// ExchangeCapabilities request string for JSON-RPC.
|
||||
ExchangeCapabilities = "engine_exchangeCapabilities"
|
||||
// GetBlobsV1 request string for JSON-RPC.
|
||||
GetBlobsV1 = "engine_getBlobsV1"
|
||||
// Defines the seconds before timing out engine endpoints with non-block execution semantics.
|
||||
defaultEngineTimeout = time.Second
|
||||
)
|
||||
@@ -93,16 +97,15 @@ type ForkchoiceUpdatedResponse struct {
|
||||
ValidationError string `json:"validationError"`
|
||||
}
|
||||
|
||||
// PayloadReconstructor defines a service that can reconstruct a full beacon
|
||||
// block with an execution payload from a signed beacon block and a connection
|
||||
// to an execution client's engine API.
|
||||
type PayloadReconstructor interface {
|
||||
// Reconstructor defines a service responsible for reconstructing full beacon chain objects by utilizing the execution API and making requests through the execution client.
|
||||
type Reconstructor interface {
|
||||
ReconstructFullBlock(
|
||||
ctx context.Context, blindedBlock interfaces.ReadOnlySignedBeaconBlock,
|
||||
) (interfaces.SignedBeaconBlock, error)
|
||||
ReconstructFullBellatrixBlockBatch(
|
||||
ctx context.Context, blindedBlocks []interfaces.ReadOnlySignedBeaconBlock,
|
||||
) ([]interfaces.SignedBeaconBlock, error)
|
||||
ReconstructBlobSidecars(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte, indices []bool) ([]blocks.VerifiedROBlob, error)
|
||||
}
|
||||
|
||||
// EngineCaller defines a client that can interact with an Ethereum
|
||||
@@ -230,7 +233,7 @@ func (s *Service) ForkchoiceUpdated(
|
||||
if err != nil {
|
||||
return nil, nil, handleRPCError(err)
|
||||
}
|
||||
case version.Deneb, version.Electra:
|
||||
case version.Deneb, version.Electra, version.EPBS:
|
||||
a, err := attrs.PbV3()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -494,6 +497,20 @@ func (s *Service) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H
|
||||
return hdr, err
|
||||
}
|
||||
|
||||
// GetBlobs returns the blob and proof from the execution engine for the given versioned hashes.
|
||||
func (s *Service) GetBlobs(ctx context.Context, versionedHashes []common.Hash) ([]*pb.BlobAndProof, error) {
|
||||
ctx, span := trace.StartSpan(ctx, "powchain.engine-api-client.GetBlobs")
|
||||
defer span.End()
|
||||
// If the execution engine does not support `GetBlobsV1`, return early to prevent encountering an error later.
|
||||
if !s.capabilityCache.has(GetBlobsV1) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := make([]*pb.BlobAndProof, len(versionedHashes))
|
||||
err := s.rpcClient.CallContext(ctx, &result, GetBlobsV1, versionedHashes)
|
||||
return result, handleRPCError(err)
|
||||
}
|
||||
|
||||
// ReconstructFullBlock takes in a blinded beacon block and reconstructs
|
||||
// a beacon block with a full execution payload via the engine API.
|
||||
func (s *Service) ReconstructFullBlock(
|
||||
@@ -522,6 +539,109 @@ func (s *Service) ReconstructFullBellatrixBlockBatch(
|
||||
return unb, nil
|
||||
}
|
||||
|
||||
// ReconstructBlobSidecars reconstructs the verified blob sidecars for a given beacon block.
|
||||
// It retrieves the KZG commitments from the block body, fetches the associated blobs and proofs,
|
||||
// and constructs the corresponding verified read-only blob sidecars.
|
||||
//
|
||||
// The 'exists' argument is a boolean list (must be the same length as body.BlobKzgCommitments), where each element corresponds to whether a
|
||||
// particular blob sidecar already exists. If exists[i] is true, the blob for the i-th KZG commitment
|
||||
// has already been retrieved and does not need to be fetched again from the execution layer (EL).
|
||||
//
|
||||
// For example:
|
||||
// - len(block.Body().BlobKzgCommitments()) == 6
|
||||
// - If exists = [true, false, true, false, true, false], the function will fetch the blobs
|
||||
// associated with indices 1, 3, and 5 (since those are marked as non-existent).
|
||||
// - If exists = [false ... x 6], the function will attempt to fetch all blobs.
|
||||
//
|
||||
// Only the blobs that do not already exist (where exists[i] is false) are fetched using the KZG commitments from block body.
|
||||
func (s *Service) ReconstructBlobSidecars(ctx context.Context, block interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte, exists []bool) ([]blocks.VerifiedROBlob, error) {
|
||||
blockBody := block.Block().Body()
|
||||
kzgCommitments, err := blockBody.BlobKzgCommitments()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get blob KZG commitments")
|
||||
}
|
||||
if len(kzgCommitments) > len(exists) {
|
||||
return nil, fmt.Errorf("length of KZG commitments (%d) is greater than length of exists (%d)", len(kzgCommitments), len(exists))
|
||||
}
|
||||
|
||||
// Collect KZG hashes for non-existing blobs
|
||||
var kzgHashes []common.Hash
|
||||
for i, commitment := range kzgCommitments {
|
||||
if !exists[i] {
|
||||
kzgHashes = append(kzgHashes, primitives.ConvertKzgCommitmentToVersionedHash(commitment))
|
||||
}
|
||||
}
|
||||
if len(kzgHashes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Fetch blobs from EL
|
||||
blobs, err := s.GetBlobs(ctx, kzgHashes)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get blobs")
|
||||
}
|
||||
if len(blobs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
header, err := block.Header()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not get header")
|
||||
}
|
||||
|
||||
// Reconstruct verified blob sidecars
|
||||
var verifiedBlobs []blocks.VerifiedROBlob
|
||||
for i, blobIndex := 0, 0; i < len(kzgCommitments); i++ {
|
||||
if exists[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
if blobIndex >= len(blobs) || blobs[blobIndex] == nil {
|
||||
blobIndex++
|
||||
continue
|
||||
}
|
||||
blob := blobs[blobIndex]
|
||||
blobIndex++
|
||||
|
||||
proof, err := blocks.MerkleProofKZGCommitment(blockBody, i)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("index", i).Error("failed to get Merkle proof for KZG commitment")
|
||||
continue
|
||||
}
|
||||
sidecar := ðpb.BlobSidecar{
|
||||
Index: uint64(i),
|
||||
Blob: blob.Blob,
|
||||
KzgCommitment: kzgCommitments[i],
|
||||
KzgProof: blob.KzgProof,
|
||||
SignedBlockHeader: header,
|
||||
CommitmentInclusionProof: proof,
|
||||
}
|
||||
|
||||
roBlob, err := blocks.NewROBlobWithRoot(sidecar, blockRoot)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("index", i).Error("failed to create RO blob with root")
|
||||
continue
|
||||
}
|
||||
|
||||
// Verify the sidecar KZG proof
|
||||
v := s.blobVerifier(roBlob, verification.ELMemPoolRequirements)
|
||||
if err := v.SidecarKzgProofVerified(); err != nil {
|
||||
log.WithError(err).WithField("index", i).Error("failed to verify KZG proof for sidecar")
|
||||
continue
|
||||
}
|
||||
|
||||
verifiedBlob, err := v.VerifiedROBlob()
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("index", i).Error("failed to verify RO blob")
|
||||
continue
|
||||
}
|
||||
|
||||
verifiedBlobs = append(verifiedBlobs, verifiedBlob)
|
||||
}
|
||||
|
||||
return verifiedBlobs, nil
|
||||
}
|
||||
|
||||
func fullPayloadFromPayloadBody(
|
||||
header interfaces.ExecutionData, body *pb.ExecutionPayloadBody, bVersion int,
|
||||
) (interfaces.ExecutionData, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package execution
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/pkg/errors"
|
||||
mocks "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
@@ -37,9 +39,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
_ = PayloadReconstructor(&Service{})
|
||||
_ = Reconstructor(&Service{})
|
||||
_ = EngineCaller(&Service{})
|
||||
_ = PayloadReconstructor(&Service{})
|
||||
_ = Reconstructor(&Service{})
|
||||
_ = EngineCaller(&mocks.EngineClient{})
|
||||
)
|
||||
|
||||
@@ -1767,7 +1769,9 @@ func fixturesStruct() *payloadFixtures {
|
||||
Proofs: []hexutil.Bytes{[]byte("proof1"), []byte("proof2")},
|
||||
Blobs: []hexutil.Bytes{{'a'}, {'b'}},
|
||||
},
|
||||
ExecutionRequests: []hexutil.Bytes{depositRequestBytes, withdrawalRequestBytes, consolidationRequestBytes},
|
||||
ExecutionRequests: []hexutil.Bytes{append([]byte{pb.DepositRequestType}, depositRequestBytes...),
|
||||
append([]byte{pb.WithdrawalRequestType}, withdrawalRequestBytes...),
|
||||
append([]byte{pb.ConsolidationRequestType}, consolidationRequestBytes...)},
|
||||
}
|
||||
parent := bytesutil.PadTo([]byte("parentHash"), fieldparams.RootLength)
|
||||
sha3Uncles := bytesutil.PadTo([]byte("sha3Uncles"), fieldparams.RootLength)
|
||||
@@ -2390,3 +2394,122 @@ func Test_ExchangeCapabilities(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestReconstructBlobSidecars(t *testing.T) {
|
||||
client := &Service{capabilityCache: &capabilityCache{}}
|
||||
b := util.NewBeaconBlockDeneb()
|
||||
kzgCommitments := createRandomKzgCommitments(t, 6)
|
||||
|
||||
b.Block.Body.BlobKzgCommitments = kzgCommitments
|
||||
r, err := b.Block.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
sb, err := blocks.NewSignedBeaconBlock(b)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
t.Run("all seen", func(t *testing.T) {
|
||||
exists := []bool{true, true, true, true, true, true}
|
||||
verifiedBlobs, err := client.ReconstructBlobSidecars(ctx, sb, r, exists)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(verifiedBlobs))
|
||||
})
|
||||
|
||||
t.Run("get-blobs end point is not supported", func(t *testing.T) {
|
||||
exists := []bool{true, true, true, true, true, false}
|
||||
verifiedBlobs, err := client.ReconstructBlobSidecars(ctx, sb, r, exists)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(verifiedBlobs))
|
||||
})
|
||||
|
||||
client.capabilityCache = &capabilityCache{capabilities: map[string]interface{}{GetBlobsV1: nil}}
|
||||
|
||||
t.Run("recovered 6 missing blobs", func(t *testing.T) {
|
||||
srv := createBlobServer(t, 6)
|
||||
defer srv.Close()
|
||||
|
||||
rpcClient, client := setupRpcClient(t, srv.URL, client)
|
||||
defer rpcClient.Close()
|
||||
|
||||
exists := [6]bool{}
|
||||
verifiedBlobs, err := client.ReconstructBlobSidecars(ctx, sb, r, exists[:])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 6, len(verifiedBlobs))
|
||||
})
|
||||
|
||||
t.Run("recovered 3 missing blobs", func(t *testing.T) {
|
||||
srv := createBlobServer(t, 3)
|
||||
defer srv.Close()
|
||||
|
||||
rpcClient, client := setupRpcClient(t, srv.URL, client)
|
||||
defer rpcClient.Close()
|
||||
|
||||
exists := []bool{true, false, true, false, true, false}
|
||||
verifiedBlobs, err := client.ReconstructBlobSidecars(ctx, sb, r, exists)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 3, len(verifiedBlobs))
|
||||
})
|
||||
|
||||
t.Run("kzg is longer than exist", func(t *testing.T) {
|
||||
srv := createBlobServer(t, 3)
|
||||
defer srv.Close()
|
||||
|
||||
rpcClient, client := setupRpcClient(t, srv.URL, client)
|
||||
defer rpcClient.Close()
|
||||
|
||||
exists := []bool{true, false, true, false, true}
|
||||
_, err := client.ReconstructBlobSidecars(ctx, sb, r, exists)
|
||||
require.ErrorContains(t, "length of KZG commitments (6) is greater than length of exists (5)", err)
|
||||
})
|
||||
}
|
||||
|
||||
func createRandomKzgCommitments(t *testing.T, num int) [][]byte {
|
||||
kzgCommitments := make([][]byte, num)
|
||||
for i := range kzgCommitments {
|
||||
kzgCommitments[i] = make([]byte, 48)
|
||||
_, err := rand.Read(kzgCommitments[i])
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return kzgCommitments
|
||||
}
|
||||
|
||||
func createBlobServer(t *testing.T, numBlobs int) *httptest.Server {
|
||||
return 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())
|
||||
}()
|
||||
|
||||
blobs := make([]pb.BlobAndProofJson, numBlobs)
|
||||
for i := range blobs {
|
||||
blobs[i] = pb.BlobAndProofJson{Blob: []byte(fmt.Sprintf("blob%d", i+1)), KzgProof: []byte(fmt.Sprintf("proof%d", i+1))}
|
||||
}
|
||||
|
||||
respJSON := map[string]interface{}{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": blobs,
|
||||
}
|
||||
require.NoError(t, json.NewEncoder(w).Encode(respJSON))
|
||||
}))
|
||||
}
|
||||
|
||||
func setupRpcClient(t *testing.T, url string, client *Service) (*rpc.Client, *Service) {
|
||||
rpcClient, err := rpc.DialHTTP(url)
|
||||
require.NoError(t, err)
|
||||
|
||||
client.rpcClient = rpcClient
|
||||
client.capabilityCache = &capabilityCache{capabilities: map[string]interface{}{GetBlobsV1: nil}}
|
||||
client.blobVerifier = testNewBlobVerifier()
|
||||
|
||||
return rpcClient, client
|
||||
}
|
||||
|
||||
func testNewBlobVerifier() verification.NewBlobVerifier {
|
||||
return func(b blocks.ROBlob, reqs []verification.Requirement) verification.BlobVerifier {
|
||||
return &verification.MockBlobVerifier{
|
||||
CbVerifiedROBlob: func() (blocks.VerifiedROBlob, error) {
|
||||
return blocks.VerifiedROBlob{}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/db"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
||||
"github.com/prysmaticlabs/prysm/v5/network"
|
||||
"github.com/prysmaticlabs/prysm/v5/network/authorization"
|
||||
)
|
||||
@@ -115,3 +116,11 @@ func WithJwtId(jwtId string) Option {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithVerifierWaiter gives the sync package direct access to the verifier waiter.
|
||||
func WithVerifierWaiter(v *verification.InitializerWaiter) Option {
|
||||
return func(s *Service) error {
|
||||
s.verifierWaiter = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,13 @@ func (s *Service) pollConnectionStatus(ctx context.Context) {
|
||||
currClient.Close()
|
||||
}
|
||||
log.WithField("endpoint", logs.MaskCredentialsLogging(s.cfg.currHttpEndpoint.Url)).Info("Connected to new endpoint")
|
||||
|
||||
c, err := s.ExchangeCapabilities(ctx)
|
||||
if err != nil {
|
||||
errorLogger(err, "Could not exchange capabilities with execution client")
|
||||
}
|
||||
s.capabilityCache.save(c)
|
||||
|
||||
return
|
||||
case <-s.ctx.Done():
|
||||
log.Debug("Received cancelled context,closing existing powchain service")
|
||||
|
||||
@@ -29,7 +29,9 @@ import (
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state"
|
||||
native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/blocks"
|
||||
"github.com/prysmaticlabs/prysm/v5/container/trie"
|
||||
contracts "github.com/prysmaticlabs/prysm/v5/contracts/deposit"
|
||||
"github.com/prysmaticlabs/prysm/v5/encoding/bytesutil"
|
||||
@@ -155,6 +157,9 @@ type Service struct {
|
||||
lastReceivedMerkleIndex int64 // Keeps track of the last received index to prevent log spam.
|
||||
runError error
|
||||
preGenesisState state.BeaconState
|
||||
verifierWaiter *verification.InitializerWaiter
|
||||
blobVerifier verification.NewBlobVerifier
|
||||
capabilityCache *capabilityCache
|
||||
}
|
||||
|
||||
// NewService sets up a new instance with an ethclient when given a web3 endpoint as a string in the config.
|
||||
@@ -192,6 +197,7 @@ func NewService(ctx context.Context, opts ...Option) (*Service, error) {
|
||||
lastReceivedMerkleIndex: -1,
|
||||
preGenesisState: genState,
|
||||
eth1HeadTicker: time.NewTicker(time.Duration(params.BeaconConfig().SecondsPerETH1Block) * time.Second),
|
||||
capabilityCache: &capabilityCache{},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -229,6 +235,13 @@ func (s *Service) Start() {
|
||||
}
|
||||
}
|
||||
|
||||
v, err := s.verifierWaiter.WaitForInitializer(s.ctx)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Could not get verification initializer")
|
||||
return
|
||||
}
|
||||
s.blobVerifier = newBlobVerifierFromInitializer(v)
|
||||
|
||||
s.isRunning = true
|
||||
|
||||
// Poll the execution client connection and fallback if errors occur.
|
||||
@@ -886,3 +899,39 @@ func (s *Service) migrateOldDepositTree(eth1DataInDB *ethpb.ETH1ChainData) error
|
||||
func (s *Service) removeStartupState() {
|
||||
s.cfg.finalizedStateAtStartup = nil
|
||||
}
|
||||
|
||||
func newBlobVerifierFromInitializer(ini *verification.Initializer) verification.NewBlobVerifier {
|
||||
return func(b blocks.ROBlob, reqs []verification.Requirement) verification.BlobVerifier {
|
||||
return ini.NewBlobVerifier(b, reqs)
|
||||
}
|
||||
}
|
||||
|
||||
type capabilityCache struct {
|
||||
capabilities map[string]interface{}
|
||||
capabilitiesLock sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *capabilityCache) save(cs []string) {
|
||||
c.capabilitiesLock.Lock()
|
||||
defer c.capabilitiesLock.Unlock()
|
||||
|
||||
if c.capabilities == nil {
|
||||
c.capabilities = make(map[string]interface{})
|
||||
}
|
||||
|
||||
for _, capability := range cs {
|
||||
c.capabilities[capability] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *capabilityCache) has(capability string) bool {
|
||||
c.capabilitiesLock.RLock()
|
||||
defer c.capabilitiesLock.RUnlock()
|
||||
|
||||
if c.capabilities == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := c.capabilities[capability]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -19,8 +19,11 @@ import (
|
||||
dbutil "github.com/prysmaticlabs/prysm/v5/beacon-chain/db/testing"
|
||||
mockExecution "github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/testing"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/execution/types"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice"
|
||||
doublylinkedtree "github.com/prysmaticlabs/prysm/v5/beacon-chain/forkchoice/doubly-linked-tree"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/startup"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stategen"
|
||||
"github.com/prysmaticlabs/prysm/v5/beacon-chain/verification"
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/container/trie"
|
||||
contracts "github.com/prysmaticlabs/prysm/v5/contracts/deposit"
|
||||
@@ -92,10 +95,16 @@ func TestStart_OK(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
server.Stop()
|
||||
})
|
||||
c := startup.NewClockSynchronizer()
|
||||
require.NoError(t, c.SetClock(startup.NewClock(time.Unix(0, 0), [32]byte{})))
|
||||
waiter := verification.NewInitializerWaiter(
|
||||
c, forkchoice.NewROForkChoice(nil), nil)
|
||||
|
||||
web3Service, err := NewService(context.Background(),
|
||||
WithHttpEndpoint(endpoint),
|
||||
WithDepositContractAddress(testAcc.ContractAddr),
|
||||
WithDatabase(beaconDB),
|
||||
WithVerifierWaiter(waiter),
|
||||
)
|
||||
require.NoError(t, err, "unable to setup execution service")
|
||||
web3Service = setDefaultMocks(web3Service)
|
||||
|
||||
@@ -36,6 +36,8 @@ type EngineClient struct {
|
||||
OverrideValidHash [32]byte
|
||||
GetPayloadResponse *blocks.GetPayloadResponse
|
||||
ErrGetPayload error
|
||||
BlobSidecars []blocks.VerifiedROBlob
|
||||
ErrorBlobSidecars error
|
||||
}
|
||||
|
||||
// NewPayload --
|
||||
@@ -106,6 +108,11 @@ func (e *EngineClient) ReconstructFullBellatrixBlockBatch(
|
||||
return fullBlocks, nil
|
||||
}
|
||||
|
||||
// ReconstructBlobSidecars is a mock implementation of the ReconstructBlobSidecars method.
|
||||
func (e *EngineClient) ReconstructBlobSidecars(context.Context, interfaces.ReadOnlySignedBeaconBlock, [32]byte, []bool) ([]blocks.VerifiedROBlob, error) {
|
||||
return e.BlobSidecars, e.ErrorBlobSidecars
|
||||
}
|
||||
|
||||
// GetTerminalBlockHash --
|
||||
func (e *EngineClient) GetTerminalBlockHash(ctx context.Context, transitionTime uint64) ([]byte, bool, error) {
|
||||
ttd := new(big.Int)
|
||||
|
||||
@@ -18,7 +18,9 @@ go_library(
|
||||
"//beacon-chain/forkchoice/types:go_default_library",
|
||||
"//beacon-chain/state:go_default_library",
|
||||
"//config/fieldparams:go_default_library",
|
||||
"//consensus-types/blocks: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",
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"epbs.go",
|
||||
"errors.go",
|
||||
"forkchoice.go",
|
||||
"last_root.go",
|
||||
@@ -23,14 +24,15 @@ go_library(
|
||||
"//testing/spectest:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//beacon-chain/core/blocks:go_default_library",
|
||||
"//beacon-chain/core/epoch/precompute:go_default_library",
|
||||
"//beacon-chain/forkchoice: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/blocks: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",
|
||||
@@ -71,12 +74,12 @@ go_test(
|
||||
"//consensus-types/forkchoice:go_default_library",
|
||||
"//consensus-types/primitives:go_default_library",
|
||||
"//crypto/hash: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",
|
||||
"//testing/util:go_default_library",
|
||||
"//time/slots:go_default_library",
|
||||
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
91
beacon-chain/forkchoice/doubly-linked-tree/epbs.go
Normal file
91
beacon-chain/forkchoice/doubly-linked-tree/epbs.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package doublylinkedtree
|
||||
|
||||
import (
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces"
|
||||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives"
|
||||
"github.com/prysmaticlabs/prysm/v5/time/slots"
|
||||
)
|
||||
|
||||
func (f *ForkChoice) GetPTCVote() primitives.PTCStatus {
|
||||
highestNode := f.store.highestReceivedNode
|
||||
if highestNode == nil {
|
||||
return primitives.PAYLOAD_ABSENT
|
||||
}
|
||||
if slots.CurrentSlot(f.store.genesisTime) > highestNode.block.slot {
|
||||
return primitives.PAYLOAD_ABSENT
|
||||
}
|
||||
if highestNode.full {
|
||||
return primitives.PAYLOAD_PRESENT
|
||||
}
|
||||
return primitives.PAYLOAD_ABSENT
|
||||
}
|
||||
|
||||
func (f *ForkChoice) insertExecutionPayload(b *BlockNode, e interfaces.ExecutionData) error {
|
||||
s := f.store
|
||||
hash := [32]byte(e.BlockHash())
|
||||
if _, ok := s.fullNodeByPayload[hash]; ok {
|
||||
// We ignore nodes with the give payload hash already included
|
||||
return nil
|
||||
}
|
||||
n := &Node{
|
||||
block: b,
|
||||
children: make([]*Node, 0),
|
||||
full: true,
|
||||
optimistic: true,
|
||||
}
|
||||
if n.block.parent != nil {
|
||||
n.block.parent.children = append(n.block.parent.children, n)
|
||||
} else {
|
||||
// make this the tree node
|
||||
f.store.treeRootNode = n
|
||||
}
|
||||
s.fullNodeByPayload[hash] = n
|
||||
s.updateWithPayload(n)
|
||||
processedPayloadCount.Inc()
|
||||
payloadCount.Set(float64(len(s.fullNodeByPayload)))
|
||||
|
||||
// make this node head if the empty node was
|
||||
if s.headNode.block == n.block {
|
||||
s.headNode = n
|
||||
}
|
||||
if b.slot == s.highestReceivedNode.block.slot {
|
||||
s.highestReceivedNode = n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.emptyNodeByRoot[envelope.BeaconBlockRoot()]
|
||||
if !ok {
|
||||
return ErrNilNode
|
||||
}
|
||||
e, err := envelope.Execution()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.insertExecutionPayload(b.block, e)
|
||||
}
|
||||
|
||||
func (s *Store) updateWithPayload(n *Node) {
|
||||
for _, node := range s.emptyNodeByRoot {
|
||||
if node.bestDescendant != nil && node.bestDescendant.block == n.block {
|
||||
node.bestDescendant = n
|
||||
}
|
||||
}
|
||||
for _, node := range s.fullNodeByPayload {
|
||||
if node.bestDescendant != nil && node.bestDescendant.block == n.block {
|
||||
node.bestDescendant = n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ForkChoice) HashForBlockRoot(root [32]byte) [32]byte {
|
||||
node, ok := f.store.emptyNodeByRoot[root]
|
||||
if !ok {
|
||||
return [32]byte{}
|
||||
}
|
||||
return node.block.payloadHash
|
||||
}
|
||||
27
beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go
Normal file
27
beacon-chain/forkchoice/doubly-linked-tree/epbs_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package doublylinkedtree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/prysm/v5/config/params"
|
||||
"github.com/prysmaticlabs/prysm/v5/testing/require"
|
||||
)
|
||||
|
||||
func TestFullInsertionPreEPBS(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
f := setup(1, 1)
|
||||
require.Equal(t, 1, f.NodeCount())
|
||||
|
||||
payloadHash := [32]byte{'A'}
|
||||
state, blk, err := prepareForkchoiceState(ctx, f, 1, [32]byte{'a'}, params.BeaconConfig().ZeroHash, payloadHash, 1, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blk))
|
||||
node, ok := f.store.emptyNodeByRoot[blk.Root()]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, blk.Root(), node.block.root)
|
||||
|
||||
fullNode, ok := f.store.fullNodeByPayload[payloadHash]
|
||||
require.Equal(t, true, ok)
|
||||
require.Equal(t, payloadHash, fullNode.block.payloadHash)
|
||||
}
|
||||
@@ -11,4 +11,3 @@ var errInvalidOptimisticStatus = errors.New("invalid optimistic status")
|
||||
var errInvalidNilCheckpoint = errors.New("invalid nil checkpoint")
|
||||
var errInvalidUnrealizedJustifiedEpoch = errors.New("invalid unrealized justified epoch")
|
||||
var errInvalidUnrealizedFinalizedEpoch = errors.New("invalid unrealized finalized epoch")
|
||||
var errNilBlockHeader = errors.New("invalid nil block header")
|
||||
|
||||
@@ -20,6 +20,8 @@ func TestFFGUpdates_OneBranch(t *testing.T) {
|
||||
r, err := f.Head(context.Background())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, params.BeaconConfig().ZeroHash, r, "Incorrect head with genesis")
|
||||
// check that the head is the full block
|
||||
require.Equal(t, true, f.store.headNode.full)
|
||||
|
||||
// Define the following tree:
|
||||
// 0 <- justified: 0, finalized: 0
|
||||
@@ -29,13 +31,13 @@ func TestFFGUpdates_OneBranch(t *testing.T) {
|
||||
// 2 <- justified: 1, finalized: 0
|
||||
// |
|
||||
// 3 <- justified: 2, finalized: 1
|
||||
state, blkRoot, err := prepareForkchoiceState(context.Background(), 1, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
state, blkRoot, err := prepareForkchoiceState(context.Background(), f, 1, indexToHash(1), params.BeaconConfig().ZeroHash, indexToHash(101), 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(2), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 2, indexToHash(2), indexToHash(1), indexToHash(102), 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(3), indexToHash(2), params.BeaconConfig().ZeroHash, 2, 1)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 3, indexToHash(3), indexToHash(2), indexToHash(103), 2, 1)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -101,35 +103,35 @@ func TestFFGUpdates_TwoBranches(t *testing.T) {
|
||||
// | |
|
||||
// justified: 2, finalized: 0 -> 9 10 <- justified: 2, finalized: 0
|
||||
// Left branch.
|
||||
state, blkRoot, err := prepareForkchoiceState(context.Background(), 1, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
state, blkRoot, err := prepareForkchoiceState(context.Background(), f, 1, indexToHash(1), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(3), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 2, indexToHash(3), indexToHash(1), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(5), indexToHash(3), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 3, indexToHash(5), indexToHash(3), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(7), indexToHash(5), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 4, indexToHash(7), indexToHash(5), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(9), indexToHash(7), params.BeaconConfig().ZeroHash, 2, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 4, indexToHash(9), indexToHash(7), params.BeaconConfig().ZeroHash, 2, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
// Right branch.
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 1, indexToHash(2), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 1, indexToHash(2), params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 2, indexToHash(4), indexToHash(2), params.BeaconConfig().ZeroHash, 0, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 2, indexToHash(4), indexToHash(2), params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 3, indexToHash(6), indexToHash(4), params.BeaconConfig().ZeroHash, 0, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 3, indexToHash(6), indexToHash(4), params.BeaconConfig().ZeroHash, 0, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(8), indexToHash(6), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 4, indexToHash(8), indexToHash(6), params.BeaconConfig().ZeroHash, 1, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), 4, indexToHash(10), indexToHash(8), params.BeaconConfig().ZeroHash, 2, 0)
|
||||
state, blkRoot, err = prepareForkchoiceState(context.Background(), f, 4, indexToHash(10), indexToHash(8), params.BeaconConfig().ZeroHash, 2, 0)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
||||
|
||||
@@ -220,12 +222,12 @@ func setup(justifiedEpoch, finalizedEpoch primitives.Epoch) *ForkChoice {
|
||||
f := New()
|
||||
f.store.justifiedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: justifiedEpoch, Root: params.BeaconConfig().ZeroHash}
|
||||
f.store.finalizedCheckpoint = &forkchoicetypes.Checkpoint{Epoch: finalizedEpoch, Root: params.BeaconConfig().ZeroHash}
|
||||
state, blkRoot, err := prepareForkchoiceState(ctx, 0, params.BeaconConfig().ZeroHash, [32]byte{}, params.BeaconConfig().ZeroHash, justifiedEpoch, finalizedEpoch)
|
||||
state, blk, err := prepareForkchoiceState(ctx, f, 0, params.BeaconConfig().ZeroHash, [32]byte{}, params.BeaconConfig().ZeroHash, justifiedEpoch, finalizedEpoch)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
f.SetBalancesByRooter(func(_ context.Context, _ [32]byte) ([]uint64, error) { return f.justifiedBalances, nil })
|
||||
err = f.InsertNode(ctx, state, blkRoot)
|
||||
err = f.InsertNode(ctx, state, blk)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user