Compare commits

...

3 Commits

Author SHA1 Message Date
Potuz
0df4d2cf82 Add tests for getVerifyingState edge cases
Add tests for two previously uncovered code paths in getVerifyingState:

1. Different dependent roots (different forks): Tests the StateByRoot
   path when parent and head have different dependent roots due to
   forking before the epoch boundary.

2. Same dependent root with head far ahead: Tests the ProcessSlots
   path when head is multiple epochs ahead of the data column
   (headEpoch+1 < dataColumnEpoch).

Also fixes existing TestValidProposerSignature to properly configure
mocks for the new dependent root checks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 13:15:09 -03:00
Potuz
c81283ece0 Use dependent root when validating data column
This PR uses the head state to validate data column in more places than
we currently do. When the parent state is from the previous epoch and is
the head (for example at slot 0) instead of replaying slot and doing an
epoch transition, we use the head state directly.

Another change is that instead of replaying until the parent state in
the case of a head miss, we only replay until the target checkpoint
state, which is more likely to be a checkpoint state in the epoch
boundary cache.
2026-01-12 12:33:37 -03:00
Potuz
76420d9428 Update Spectests to v1.7.0.alpha-1 (#16246) 2026-01-11 12:54:01 +00:00
15 changed files with 327 additions and 166 deletions

View File

@@ -273,16 +273,16 @@ filegroup(
url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz",
)
consensus_spec_version = "v1.7.0-alpha.0"
consensus_spec_version = "v1.7.0-alpha.1"
load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests")
consensus_spec_tests(
name = "consensus_spec_tests",
flavors = {
"general": "sha256-b+rJOuVqq+Dy53quPcNYcQwPFoMU7Wp7tdUVe7n0g8w=",
"minimal": "sha256-qxRIxtjPxVsVCY90WsBJKhk0027XDSmhjnRvRN14V1c=",
"mainnet": "sha256-NsuOQG3LzeiEE1TrWuvQ6vu6BboHv7h7f/RTS0pWkCs=",
"general": "sha256-j5R3jA7Oo4OSDMTvpMuD+8RomaCByeFSwtfkq6fL0Zg=",
"minimal": "sha256-tdTqByoyswOS4r6OxFmo70y2BP7w1TgEok+gf4cbxB0=",
"mainnet": "sha256-5gB4dt6SnSDKzdBc06VedId3NkgvSYyv9n9FRxWKwYI=",
},
version = consensus_spec_version,
)
@@ -298,7 +298,7 @@ filegroup(
visibility = ["//visibility:public"],
)
""",
integrity = "sha256-hwNdUBgdBrkk6pWIpNYbzbwswUuOu6AMD2exN8uv+QQ=",
integrity = "sha256-J+43DrK1pF658kTXTwMS6zGf4KDjvas++m8w2a8swpg=",
strip_prefix = "consensus-specs-" + consensus_spec_version[1:],
url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version,
)

View File

@@ -26,7 +26,6 @@ go_library(
"//beacon-chain/core/time:go_default_library",
"//beacon-chain/core/validators:go_default_library",
"//beacon-chain/state:go_default_library",
"//config/features:go_default_library",
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",

View File

@@ -7,7 +7,6 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/signing"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
"github.com/OffchainLabs/prysm/v7/config/features"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/interfaces"
@@ -213,12 +212,7 @@ func ProcessWithdrawals(st state.BeaconState, executionData interfaces.Execution
if err != nil {
return nil, errors.Wrap(err, "could not get next withdrawal validator index")
}
if features.Get().LowValcountSweep {
bound := min(uint64(st.NumValidators()), params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
nextValidatorIndex += primitives.ValidatorIndex(bound)
} else {
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
}
nextValidatorIndex += primitives.ValidatorIndex(params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep)
nextValidatorIndex = nextValidatorIndex % primitives.ValidatorIndex(st.NumValidators())
} else {
nextValidatorIndex = expectedWithdrawals[len(expectedWithdrawals)-1].ValidatorIndex + 1

View File

@@ -588,6 +588,12 @@ func fcReturnsTargetRoot(root [32]byte) func([32]byte, primitives.Epoch) ([32]by
}
}
func fcReturnsDependentRoot() func([32]byte, primitives.Epoch) ([32]byte, error) {
return func(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
return root, nil
}
}
type mockSignatureCache struct {
svCalledForSig map[signatureData]bool
svcb func(sig signatureData) (bool, error)

View File

@@ -1,7 +1,6 @@
package verification
import (
"bytes"
"context"
"crypto/sha256"
"fmt"
@@ -19,6 +18,7 @@ import (
"github.com/OffchainLabs/prysm/v7/runtime/logging"
"github.com/OffchainLabs/prysm/v7/time/slots"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
@@ -293,55 +293,57 @@ func (dv *RODataColumnsVerifier) ValidProposerSignature(ctx context.Context) (er
// The returned state is guaranteed to be at the same epoch as the data column's epoch, and have the same randao mix and active
// validator indices as the data column's parent state advanced to the data column's slot.
func (dv *RODataColumnsVerifier) getVerifyingState(ctx context.Context, dataColumn blocks.RODataColumn) (state.ReadOnlyBeaconState, error) {
dataColumnSlot := dataColumn.Slot()
dataColumnEpoch := slots.ToEpoch(dataColumnSlot)
if dataColumnEpoch == 0 {
return dv.hsp.HeadStateReadOnly(ctx)
}
parentRoot := dataColumn.ParentRoot()
dcDependentRoot, err := dv.fc.DependentRootForEpoch(parentRoot, dataColumnEpoch-1)
if err != nil {
return nil, err
}
headRoot, err := dv.hsp.HeadRoot(ctx)
if err != nil {
return nil, err
}
parentRoot := dataColumn.ParentRoot()
dataColumnSlot := dataColumn.Slot()
dataColumnEpoch := slots.ToEpoch(dataColumnSlot)
headSlot := dv.hsp.HeadSlot()
headEpoch := slots.ToEpoch(headSlot)
// Use head if it's the parent
if bytes.Equal(parentRoot[:], headRoot) {
// If they are in the same epoch, then we can return the head state directly
if dataColumnEpoch == headEpoch {
return dv.hsp.HeadStateReadOnly(ctx)
}
// Otherwise, we need to process the head state to the data column's slot
headState, err := dv.hsp.HeadState(ctx)
if err != nil {
return nil, err
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, dataColumnSlot)
}
// If head and data column are in the same epoch and head is compatible with the parent's depdendent root, then use head
if dataColumnEpoch == headEpoch {
headDependent, err := dv.fc.DependentRootForEpoch(bytesutil.ToBytes32(headRoot), dataColumnEpoch)
if err != nil {
return nil, err
}
parentDependent, err := dv.fc.DependentRootForEpoch(parentRoot, dataColumnEpoch)
if err != nil {
return nil, err
}
if bytes.Equal(headDependent[:], parentDependent[:]) {
return dv.hsp.HeadStateReadOnly(ctx)
}
}
// Otherwise retrieve the parent state and advance it to the data column's slot
parentState, err := dv.sr.StateByRoot(ctx, parentRoot)
headDependentRoot, err := dv.fc.DependentRootForEpoch(bytesutil.ToBytes32(headRoot), dataColumnEpoch-1)
if err != nil {
return nil, err
}
parentEpoch := slots.ToEpoch(parentState.Slot())
if dataColumnEpoch == parentEpoch {
return parentState, nil
if dcDependentRoot == headDependentRoot {
headSlot := dv.hsp.HeadSlot()
headEpoch := slots.ToEpoch(headSlot)
if headEpoch == dataColumnEpoch || headEpoch == dataColumnEpoch-1 {
return dv.hsp.HeadStateReadOnly(ctx)
}
if headEpoch+1 < dataColumnEpoch {
headState, err := dv.hsp.HeadState(ctx)
if err != nil {
return nil, err
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, headState, headRoot, dataColumnSlot)
}
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, parentState, parentRoot[:], dataColumnSlot)
logrus.WithFields(logrus.Fields{
"slot": dataColumnSlot,
"parentRoot": fmt.Sprintf("%#x", parentRoot),
"headRoot": fmt.Sprintf("%#x", headRoot),
}).Debug("Replying state for data column verification")
targetRoot, err := dv.fc.TargetRootForEpoch(parentRoot, dataColumnEpoch)
if err != nil {
return nil, err
}
targetState, err := dv.sr.StateByRoot(ctx, targetRoot)
if err != nil {
return nil, err
}
targetEpoch := slots.ToEpoch(targetState.Slot())
if targetEpoch == dataColumnEpoch || targetEpoch == dataColumnEpoch-1 {
return targetState, nil
}
return transition.ProcessSlotsUsingNextSlotCache(ctx, targetState, parentRoot[:], dataColumnSlot)
}
func (dv *RODataColumnsVerifier) SidecarParentSeen(parentSeen func([fieldparams.RootLength]byte) bool) (err error) {

View File

@@ -1,6 +1,7 @@
package verification
import (
"context"
"reflect"
"testing"
"time"
@@ -9,6 +10,7 @@ import (
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
@@ -281,7 +283,7 @@ func TestColumnSlotAboveFinalized(t *testing.T) {
func TestValidProposerSignature(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -294,59 +296,83 @@ func TestValidProposerSignature(t *testing.T) {
// The signature data does not depend on the data column itself, so we can use the first one.
expectedSignatureData := columnToSignatureData(firstColumn)
// Create a proper Fulu state for verification.
// We need enough validators to cover the proposer index.
numValidators := max(uint64(firstColumn.ProposerIndex()+1), 64)
fuluState, _ := util.DeterministicGenesisStateFulu(t, numValidators)
// Head state provider that returns the fuluState via HeadStateReadOnly path.
headStateWithState := &mockHeadStateProvider{
headRoot: parentRoot[:],
headSlot: columnSlot,
headStateReadOnly: fuluState,
}
// Head state provider that will fail (headStateReadOnly is nil).
headStateNotFound := &mockHeadStateProvider{
headRoot: parentRoot[:],
headSlot: columnSlot,
}
testCases := []struct {
isError bool
vscbShouldError bool
svcbReturn bool
stateByRooter StateByRooter
vscbError error
svcbError error
name string
isError bool
vscbShouldError bool
svcbReturn bool
stateByRooter StateByRooter
headStateProvider *mockHeadStateProvider
vscbError error
svcbError error
name string
}{
{
name: "cache hit - success",
svcbReturn: true,
svcbError: nil,
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
isError: false,
name: "cache hit - success",
svcbReturn: true,
svcbError: nil,
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
headStateProvider: headStateWithState,
isError: false,
},
{
name: "cache hit - error",
svcbReturn: true,
svcbError: errors.New("derp"),
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
isError: true,
name: "cache hit - error",
svcbReturn: true,
svcbError: errors.New("derp"),
vscbShouldError: true,
vscbError: nil,
stateByRooter: &mockStateByRooter{sbr: sbrErrorIfCalled(t)},
headStateProvider: headStateWithState,
isError: true,
},
{
name: "cache miss - success",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: false,
name: "cache miss - success",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
headStateProvider: headStateWithState,
isError: false,
},
{
name: "cache miss - state not found",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrNotFound(t, expectedSignatureData.Parent),
isError: true,
name: "cache miss - state not found",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: nil,
stateByRooter: sbrNotFound(t, expectedSignatureData.Parent),
headStateProvider: headStateNotFound,
isError: true,
},
{
name: "cache miss - signature failure",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: errors.New("signature, not so good!"),
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
isError: true,
name: "cache miss - signature failure",
svcbReturn: false,
svcbError: nil,
vscbShouldError: false,
vscbError: errors.New("signature, not so good!"),
stateByRooter: sbrForValOverrideWithT(t, firstColumn.ProposerIndex(), validator),
headStateProvider: headStateWithState,
isError: true,
},
}
@@ -377,9 +403,10 @@ func TestValidProposerSignature(t *testing.T) {
shared: &sharedResources{
sc: signatureCache,
sr: tc.stateByRooter,
hsp: &mockHeadStateProvider{},
hsp: tc.headStateProvider,
fc: &mockForkchoicer{
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
DependentRootForEpochCB: fcReturnsDependentRoot(),
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
@@ -405,7 +432,7 @@ func TestValidProposerSignature(t *testing.T) {
func TestDataColumnsSidecarParentSeen(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -509,7 +536,7 @@ func TestDataColumnsSidecarParentValid(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -630,7 +657,7 @@ func TestDataColumnsSidecarDescendsFromFinalized(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -693,7 +720,7 @@ func TestDataColumnsSidecarInclusionProven(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -748,7 +775,7 @@ func TestDataColumnsSidecarKzgProofVerified(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
const (
columnSlot = 0
columnSlot = 97
blobCount = 1
)
@@ -924,3 +951,135 @@ func TestColumnRequirementSatisfaction(t *testing.T) {
require.NoError(t, err)
}
func TestGetVerifyingStateEdgeCases(t *testing.T) {
const (
columnSlot = 97 // epoch 3
blobCount = 1
)
parentRoot := [fieldparams.RootLength]byte{}
columns := GenerateTestDataColumns(t, parentRoot, columnSlot, blobCount)
// Create a proper Fulu state for verification.
numValidators := max(uint64(columns[0].ProposerIndex()+1), 64)
fuluState, _ := util.DeterministicGenesisStateFulu(t, numValidators)
t.Run("different dependent roots - uses StateByRoot path", func(t *testing.T) {
// Parent and head are on different forks with different dependent roots.
// This forces the code to use TargetRootForEpoch -> StateByRoot path.
signatureCache := &mockSignatureCache{
svcb: func(signatureData signatureData) (bool, error) {
return false, nil // Cache miss
},
vscb: func(signatureData signatureData, _ validatorAtIndexer) (err error) {
return nil // Signature valid
},
}
// StateByRoot will be called because dependent roots differ
stateByRootCalled := false
stateByRooter := &mockStateByRooter{
sbr: func(_ context.Context, root [32]byte) (state.BeaconState, error) {
stateByRootCalled = true
return fuluState, nil
},
}
initializer := Initializer{
shared: &sharedResources{
sc: signatureCache,
sr: stateByRooter,
hsp: &mockHeadStateProvider{
headRoot: []byte{0xff}, // Different from parentRoot
headSlot: columnSlot,
},
fc: &mockForkchoicer{
// Return different roots for parent vs head to simulate different forks
DependentRootForEpochCB: func(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
return root, nil // Returns input, so parent [0...] != head [0xff...]
},
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidProposerSignature(t.Context())
require.NoError(t, err)
require.Equal(t, true, stateByRootCalled, "StateByRoot should be called when dependent roots differ")
})
t.Run("same dependent root head far ahead - uses head state with ProcessSlots", func(t *testing.T) {
// Parent is ancestor of head on same chain, but head is in epoch 1 while column is in epoch 3.
// headEpoch (1) + 1 < dataColumnEpoch (3), so ProcessSlots is called on head state.
signatureCache := &mockSignatureCache{
svcb: func(signatureData signatureData) (bool, error) {
return false, nil // Cache miss
},
vscb: func(signatureData signatureData, _ validatorAtIndexer) (err error) {
return nil // Signature valid
},
}
headStateCalled := false
initializer := Initializer{
shared: &sharedResources{
sc: signatureCache,
sr: &mockStateByRooter{sbr: sbrErrorIfCalled(t)}, // Should not be called
hsp: &mockHeadStateProvider{
headRoot: parentRoot[:], // Same as parent
headSlot: 32, // Epoch 1
headState: fuluState.Copy(), // HeadState (not ReadOnly) for ProcessSlots
headStateReadOnly: nil, // Should not use ReadOnly path
},
fc: &mockForkchoicer{
// Return same root for both to simulate same chain
DependentRootForEpochCB: func(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
return [32]byte{0xaa}, nil // Same for all inputs
},
TargetRootForEpochCB: fcReturnsTargetRoot([fieldparams.RootLength]byte{}),
},
},
}
// Wrap to detect HeadState call
originalHsp := initializer.shared.hsp.(*mockHeadStateProvider)
wrappedHsp := &mockHeadStateProvider{
headRoot: originalHsp.headRoot,
headSlot: originalHsp.headSlot,
headState: originalHsp.headState,
}
initializer.shared.hsp = &headStateCallTracker{
mockHeadStateProvider: wrappedHsp,
headStateCalled: &headStateCalled,
}
verifier := initializer.NewDataColumnsVerifier(columns, GossipDataColumnSidecarRequirements)
err := verifier.ValidProposerSignature(t.Context())
require.NoError(t, err)
require.Equal(t, true, headStateCalled, "HeadState should be called when head is far ahead")
})
}
// headStateCallTracker wraps mockHeadStateProvider to track HeadState calls.
type headStateCallTracker struct {
*mockHeadStateProvider
headStateCalled *bool
}
func (h *headStateCallTracker) HeadState(ctx context.Context) (state.BeaconState, error) {
*h.headStateCalled = true
return h.mockHeadStateProvider.HeadState(ctx)
}
func (h *headStateCallTracker) HeadRoot(ctx context.Context) ([]byte, error) {
return h.mockHeadStateProvider.HeadRoot(ctx)
}
func (h *headStateCallTracker) HeadSlot() primitives.Slot {
return h.mockHeadStateProvider.HeadSlot()
}
func (h *headStateCallTracker) HeadStateReadOnly(ctx context.Context) (state.ReadOnlyBeaconState, error) {
return h.mockHeadStateProvider.HeadStateReadOnly(ctx)
}

View File

@@ -0,0 +1,2 @@
### Changed
- Use dependent root and target root to verify data column proposer index.

View File

@@ -0,0 +1,2 @@
### Added
- Update spectests to v1.7.0-alpha-1.

View File

@@ -81,7 +81,6 @@ type Flags struct {
SaveInvalidBlob bool // SaveInvalidBlob saves invalid blob to temp.
EnableDiscoveryReboot bool // EnableDiscoveryReboot allows the node to have its local listener to be rebooted in the event of discovery issues.
LowValcountSweep bool // LowValcountSweep bounds withdrawal sweep by validator count.
// KeystoreImportDebounceInterval specifies the time duration the validator waits to reload new keys if they have
// changed on disk. This feature is for advanced use cases only.
@@ -290,11 +289,6 @@ func ConfigureBeaconChain(ctx *cli.Context) error {
logEnabled(ignoreUnviableAttestations)
cfg.IgnoreUnviableAttestations = true
}
if ctx.Bool(lowValcountSweep.Name) {
logEnabled(lowValcountSweep)
cfg.LowValcountSweep = true
}
if ctx.IsSet(EnableStateDiff.Name) {
logEnabled(EnableStateDiff)
cfg.EnableStateDiff = true

View File

@@ -216,13 +216,6 @@ var (
Name: "ignore-unviable-attestations",
Usage: "Ignores attestations whose target state is not viable with respect to the current head (avoid expensive state replay from lagging attesters).",
}
// lowValcountSweep bounds withdrawal sweep by validator count.
lowValcountSweep = &cli.BoolFlag{
Name: "low-valcount-sweep",
Usage: "Uses validator count bound for withdrawal sweep when validator count is less than MaxValidatorsPerWithdrawalsSweep.",
Value: false,
Hidden: true,
}
)
// devModeFlags holds list of flags that are set when development mode is on.
@@ -285,7 +278,6 @@ var BeaconChainFlags = combinedFlags([]cli.Flag{
enableExperimentalAttestationPool,
forceHeadFlag,
blacklistRoots,
lowValcountSweep,
}, deprecatedBeaconFlags, deprecatedFlags, upcomingDeprecation)
func combinedFlags(flags ...[]cli.Flag) []cli.Flag {

View File

@@ -1,4 +1,4 @@
version: v1.7.0-alpha.0
version: v1.7.0-alpha.1
style: full
specrefs:
@@ -117,6 +117,8 @@ exceptions:
dataclasses:
# Not implemented
- BlobParameters#fulu
- ExpectedWithdrawals#capella
- ExpectedWithdrawals#electra
- LatestMessage#phase0
- LightClientStore#altair
- OptimisticStore#bellatrix
@@ -126,6 +128,7 @@ exceptions:
- LightClientStore#capella
# Not implemented: gloas (future fork)
- ExpectedWithdrawals#gloas
- LatestMessage#gloas
- Store#gloas

View File

@@ -34,6 +34,26 @@
blobs: List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK]
</spec>
- name: ExpectedWithdrawals#capella
sources: []
spec: |
<spec dataclass="ExpectedWithdrawals" fork="capella" hash="73becb4c">
class ExpectedWithdrawals(object):
withdrawals: Sequence[Withdrawal]
processed_sweep_withdrawals_count: uint64
</spec>
- name: ExpectedWithdrawals#electra
sources: []
spec: |
<spec dataclass="ExpectedWithdrawals" fork="electra" hash="a3827f01">
class ExpectedWithdrawals(object):
withdrawals: Sequence[Withdrawal]
# [New in Electra:EIP7251]
processed_partial_withdrawals_count: uint64
processed_sweep_withdrawals_count: uint64
</spec>
- name: GetPayloadResponse#bellatrix
sources:
- file: consensus-types/blocks/get_payload.go

View File

@@ -2366,8 +2366,8 @@
- file: beacon-chain/state/state-native/getters_withdrawal.go
search: func (b *BeaconState) ExpectedWithdrawals(
spec: |
<spec fn="get_expected_withdrawals" fork="capella" hash="d6a98c14">
def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]:
<spec fn="get_expected_withdrawals" fork="capella" hash="12088347">
def get_expected_withdrawals(state: BeaconState) -> ExpectedWithdrawals:
withdrawal_index = state.next_withdrawal_index
withdrawals: List[Withdrawal] = []
@@ -2377,7 +2377,10 @@
)
withdrawals.extend(validators_sweep_withdrawals)
return withdrawals, processed_validators_sweep_count
return ExpectedWithdrawals(
withdrawals,
processed_validators_sweep_count,
)
</spec>
- name: get_expected_withdrawals#electra
@@ -2385,8 +2388,8 @@
- file: beacon-chain/state/state-native/getters_withdrawal.go
search: func (b *BeaconState) ExpectedWithdrawals(
spec: |
<spec fn="get_expected_withdrawals" fork="electra" hash="cfce862b">
def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64, uint64]:
<spec fn="get_expected_withdrawals" fork="electra" hash="254541e3">
def get_expected_withdrawals(state: BeaconState) -> ExpectedWithdrawals:
withdrawal_index = state.next_withdrawal_index
withdrawals: List[Withdrawal] = []
@@ -2403,8 +2406,12 @@
)
withdrawals.extend(validators_sweep_withdrawals)
# [Modified in Electra:EIP7251]
return withdrawals, processed_partial_withdrawals_count, processed_validators_sweep_count
return ExpectedWithdrawals(
withdrawals,
# [New in Electra:EIP7251]
processed_partial_withdrawals_count,
processed_validators_sweep_count,
)
</spec>
- name: get_filtered_block_tree
@@ -4932,7 +4939,7 @@
- name: prepare_execution_payload#capella
sources: []
spec: |
<spec fn="prepare_execution_payload" fork="capella" hash="c258893e">
<spec fn="prepare_execution_payload" fork="capella" hash="998e8b92">
def prepare_execution_payload(
state: BeaconState,
safe_block_hash: Hash32,
@@ -4945,15 +4952,12 @@
parent_hash = state.latest_execution_payload_header.block_hash
# Set the forkchoice head and initiate the payload build process
# [New in Capella]
withdrawals, _ = get_expected_withdrawals(state)
payload_attributes = PayloadAttributes(
timestamp=compute_time_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
# [New in Capella]
withdrawals=withdrawals,
withdrawals=get_expected_withdrawals(state).withdrawals,
)
return execution_engine.notify_forkchoice_updated(
head_block_hash=parent_hash,
@@ -4966,7 +4970,7 @@
- name: prepare_execution_payload#deneb
sources: []
spec: |
<spec fn="prepare_execution_payload" fork="deneb" hash="59f61f3a">
<spec fn="prepare_execution_payload" fork="deneb" hash="c617aa1b">
def prepare_execution_payload(
state: BeaconState,
safe_block_hash: Hash32,
@@ -4978,13 +4982,11 @@
parent_hash = state.latest_execution_payload_header.block_hash
# Set the forkchoice head and initiate the payload build process
withdrawals, _ = get_expected_withdrawals(state)
payload_attributes = PayloadAttributes(
timestamp=compute_time_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
withdrawals=withdrawals,
withdrawals=get_expected_withdrawals(state).withdrawals,
# [New in Deneb:EIP4788]
parent_beacon_block_root=hash_tree_root(state.latest_block_header),
)
@@ -4999,7 +5001,7 @@
- name: prepare_execution_payload#electra
sources: []
spec: |
<spec fn="prepare_execution_payload" fork="electra" hash="5414b883">
<spec fn="prepare_execution_payload" fork="electra" hash="c617aa1b">
def prepare_execution_payload(
state: BeaconState,
safe_block_hash: Hash32,
@@ -5010,15 +5012,13 @@
# Verify consistency of the parent hash with respect to the previous execution payload header
parent_hash = state.latest_execution_payload_header.block_hash
# [Modified in EIP7251]
# Set the forkchoice head and initiate the payload build process
withdrawals, _, _ = get_expected_withdrawals(state)
payload_attributes = PayloadAttributes(
timestamp=compute_time_at_slot(state, state.slot),
prev_randao=get_randao_mix(state, get_current_epoch(state)),
suggested_fee_recipient=suggested_fee_recipient,
withdrawals=withdrawals,
withdrawals=get_expected_withdrawals(state).withdrawals,
# [New in Deneb:EIP4788]
parent_beacon_block_root=hash_tree_root(state.latest_block_header),
)
return execution_engine.notify_forkchoice_updated(
@@ -7071,18 +7071,18 @@
- file: beacon-chain/core/blocks/withdrawals.go
search: func ProcessWithdrawals(
spec: |
<spec fn="process_withdrawals" fork="capella" hash="901f9fc4">
<spec fn="process_withdrawals" fork="capella" hash="47327ef6">
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
# Get expected withdrawals
withdrawals, processed_validators_sweep_count = get_expected_withdrawals(state)
assert payload.withdrawals == withdrawals
expected = get_expected_withdrawals(state)
assert payload.withdrawals == expected.withdrawals
# Apply expected withdrawals
apply_withdrawals(state, withdrawals)
apply_withdrawals(state, expected.withdrawals)
# Update withdrawals fields in the state
update_next_withdrawal_index(state, withdrawals)
update_next_withdrawal_validator_index(state, processed_validators_sweep_count)
update_next_withdrawal_index(state, expected.withdrawals)
update_next_withdrawal_validator_index(state, expected.withdrawals)
</spec>
- name: process_withdrawals#electra
@@ -7090,23 +7090,20 @@
- file: beacon-chain/core/blocks/withdrawals.go
search: func ProcessWithdrawals(
spec: |
<spec fn="process_withdrawals" fork="electra" hash="67870972">
<spec fn="process_withdrawals" fork="electra" hash="24a50daa">
def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None:
# [Modified in Electra:EIP7251]
# Get expected withdrawals
withdrawals, processed_partial_withdrawals_count, processed_validators_sweep_count = (
get_expected_withdrawals(state)
)
assert payload.withdrawals == withdrawals
expected = get_expected_withdrawals(state)
assert payload.withdrawals == expected.withdrawals
# Apply expected withdrawals
apply_withdrawals(state, withdrawals)
apply_withdrawals(state, expected.withdrawals)
# Update withdrawals fields in the state
update_next_withdrawal_index(state, withdrawals)
update_next_withdrawal_index(state, expected.withdrawals)
# [New in Electra:EIP7251]
update_pending_partial_withdrawals(state, processed_partial_withdrawals_count)
update_next_withdrawal_validator_index(state, processed_validators_sweep_count)
update_pending_partial_withdrawals(state, expected.processed_partial_withdrawals_count)
update_next_withdrawal_validator_index(state, expected.withdrawals)
</spec>
- name: queue_excess_active_balance

View File

@@ -10,7 +10,6 @@ go_library(
importpath = "github.com/OffchainLabs/prysm/v7/testing/spectest/utils",
visibility = ["//testing/spectest:__subpackages__"],
deps = [
"//config/features:go_default_library",
"//config/params:go_default_library",
"//io/file:go_default_library",
"//testing/require:go_default_library",

View File

@@ -6,20 +6,12 @@ import (
"fmt"
"testing"
"github.com/OffchainLabs/prysm/v7/config/features"
"github.com/OffchainLabs/prysm/v7/config/params"
)
// SetConfig sets the global params for spec tests depending on the option chosen.
// Provides reset function allowing to get back to the previous configuration at the end of a test.
func SetConfig(t testing.TB, config string) error {
params.SetupTestConfigCleanup(t)
resetFeatures := features.InitWithReset(&features.Flags{
LowValcountSweep: true,
})
t.Cleanup(resetFeatures)
switch config {
case "minimal":
params.OverrideBeaconConfig(params.MinimalSpecConfig().Copy())