Skip to content

Commit ec9aa2c

Browse files
committed
changes on forkchoice, persistent execution_payload_states not include
1 parent df372e6 commit ec9aa2c

24 files changed

+1266
-59
lines changed

cl/clparams/config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ type BeaconChainConfig struct {
543543
ElectraForkEpoch uint64 `yaml:"ELECTRA_FORK_EPOCH" spec:"true" json:"ELECTRA_FORK_EPOCH,string"` // ElectraForkEpoch is used to represent the assigned fork epoch for Electra.
544544
FuluForkVersion ConfigForkVersion `yaml:"FULU_FORK_VERSION" spec:"true" json:"FULU_FORK_VERSION"` // FuluForkVersion is used to represent the fork version for Fulu.
545545
FuluForkEpoch uint64 `yaml:"FULU_FORK_EPOCH" spec:"true" json:"FULU_FORK_EPOCH,string"` // FuluForkEpoch is used to represent the assigned fork epoch for Fulu.
546+
GloasForkVersion ConfigForkVersion `yaml:"GLOAS_FORK_VERSION" spec:"true" json:"GLOAS_FORK_VERSION"` // GloasForkVersion is used to represent the fork version for Gloas.
547+
GloasForkEpoch uint64 `yaml:"GLOAS_FORK_EPOCH" spec:"true" json:"GLOAS_FORK_EPOCH,string"` // GloasForkEpoch is used to represent the assigned fork epoch for Gloas.
546548

547549
ForkVersionSchedule map[common.Bytes4]VersionScheduleEntry `json:"-"` // Schedule of fork epochs by version.
548550

@@ -694,6 +696,7 @@ func (b *BeaconChainConfig) GetCurrentStateVersion(epoch uint64) StateVersion {
694696
b.DenebForkEpoch,
695697
b.ElectraForkEpoch,
696698
b.FuluForkEpoch,
699+
b.GloasForkEpoch,
697700
}
698701
stateVersion := Phase0Version
699702
for _, forkEpoch := range forkEpochList {

cl/clparams/constants.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package clparams
2+
3+
import "math"
4+
5+
const (
6+
// non-configurable constants
7+
// For Gloas
8+
BuilderIndexFlag = uint64(1 << 40) // 2^40
9+
BuilderIndexSelfBuild = math.MaxUint64
10+
BuilderPaymentThresholdNumerator = uint64(6)
11+
BuilderPaymentThresholdDenominator = uint64(10)
12+
PtcSize = uint64(512)
13+
PayloadTimelyThreshold = PtcSize / 2 // 256
14+
15+
AttestationTimelinessIndex = 0
16+
PtcTimelinessIndex = 1
17+
NumBlockTimelinessDeadlines = 2
18+
)

cl/clparams/global.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,5 @@
11
package clparams
22

3-
import "math"
4-
5-
const (
6-
// non-configurable constants
7-
// For Gloas
8-
BuilderIndexFlag = uint64(1 << 40) // 2^40
9-
BuilderIndexSelfBuild = math.MaxUint64
10-
BuilderPaymentThresholdNumerator = uint64(6)
11-
BuilderPaymentThresholdDenominator = uint64(10)
12-
PtcSize = uint64(512)
13-
)
14-
153
var (
164
globalBeaconConfig *BeaconChainConfig
175
globalCaplinConfig *CaplinConfig

cl/cltypes/epbs_payload.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ var (
2727
_ ssz2.SizedObjectSSZ = (*SignedExecutionPayloadEnvelope)(nil)
2828
)
2929

30+
type PayloadStatus uint64
31+
32+
const (
33+
PayloadStatusPending PayloadStatus = 0
34+
PayloadStatusEmpty PayloadStatus = 1
35+
PayloadStatusFull PayloadStatus = 2
36+
)
37+
3038
// PayloadAttestationData represents attestation data for a payload.
3139
type PayloadAttestationData struct {
3240
BeaconBlockRoot common.Hash `json:"beacon_block_root"`

cl/phase1/forkchoice/fork_graph/fork_graph_disk.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,9 @@ func (f *forkGraphDisk) isBlockRootTheCurrentState(blockRoot common.Hash) bool {
181181
}
182182

183183
// Add a new node and edge to the graph
184-
func (f *forkGraphDisk) AddChainSegment(signedBlock *cltypes.SignedBeaconBlock, fullValidation bool) (*state.CachingBeaconState, ChainSegmentInsertionResult, error) {
184+
// parentFullState: if non-nil, use this as the starting state instead of looking up from block_states.
185+
// [Modified in Gloas:EIP7732] Allows passing execution_payload_states when parent is FULL.
186+
func (f *forkGraphDisk) AddChainSegment(signedBlock *cltypes.SignedBeaconBlock, fullValidation bool, parentFullState *state.CachingBeaconState) (*state.CachingBeaconState, ChainSegmentInsertionResult, error) {
185187
block := signedBlock.Block
186188
blockRoot, err := block.HashSSZ()
187189
if err != nil {
@@ -200,7 +202,10 @@ func (f *forkGraphDisk) AddChainSegment(signedBlock *cltypes.SignedBeaconBlock,
200202

201203
isBlockRootTheCurrentState := f.isBlockRootTheCurrentState(blockRoot)
202204
var newState *state.CachingBeaconState
203-
if isBlockRootTheCurrentState {
205+
if parentFullState != nil {
206+
// [New in Gloas:EIP7732] Use provided parent state (from execution_payload_states)
207+
newState = parentFullState
208+
} else if isBlockRootTheCurrentState {
204209
newState = f.currentState
205210
} else {
206211
newState, err = f.getState(block.ParentRoot, false, true)
@@ -213,8 +218,8 @@ func (f *forkGraphDisk) AddChainSegment(signedBlock *cltypes.SignedBeaconBlock,
213218
log.Debug("AddChainSegment: missing segment", "block", common.Hash(blockRoot), "slot", block.Slot, "parentRoot", block.ParentRoot)
214219
return nil, MissingSegment, nil
215220
}
216-
finalizedBlock, hasFinalized := f.getBlock(newState.FinalizedCheckpoint().Root)
217-
parentBlock, hasParentBlock := f.getBlock(block.ParentRoot)
221+
finalizedBlock, hasFinalized := f.GetBlock(newState.FinalizedCheckpoint().Root)
222+
parentBlock, hasParentBlock := f.GetBlock(block.ParentRoot)
218223

219224
// Before processing the state: update the newest lightclient update.
220225
if block.Version() >= clparams.AltairVersion && hasParentBlock && fullValidation && hasFinalized && f.rcfg.Beacon && !isBlockRootTheCurrentState {
@@ -328,7 +333,7 @@ func (f *forkGraphDisk) GetHeader(blockRoot common.Hash) (*cltypes.BeaconBlockHe
328333
return obj.(*cltypes.BeaconBlockHeader), true
329334
}
330335

331-
func (f *forkGraphDisk) getBlock(blockRoot common.Hash) (*cltypes.SignedBeaconBlock, bool) {
336+
func (f *forkGraphDisk) GetBlock(blockRoot common.Hash) (*cltypes.SignedBeaconBlock, bool) {
332337
obj, has := f.blocks.Load(blockRoot)
333338
if !has {
334339
return nil, false
@@ -425,7 +430,7 @@ func (f *forkGraphDisk) getState(blockRoot common.Hash, alwaysCopy bool, addChai
425430

426431
// try and find the point of recconnection
427432
for copyReferencedState == nil {
428-
block, isSegmentPresent := f.getBlock(currentIteratorRoot)
433+
block, isSegmentPresent := f.GetBlock(currentIteratorRoot)
429434
if !isSegmentPresent {
430435
// check if it is in the header
431436
bHeader, ok := f.GetHeader(currentIteratorRoot)

cl/phase1/forkchoice/fork_graph/fork_graph_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,20 +51,20 @@ func TestForkGraphInDisk(t *testing.T) {
5151
require.NoError(t, utils.DecodeSSZSnappy(anchorState, anchor, int(clparams.Phase0Version)))
5252
emitter := beaconevents.NewEventEmitter()
5353
graph := NewForkGraphDisk(anchorState, nil, afero.NewMemMapFs(), beacon_router_configuration.RouterConfiguration{}, emitter)
54-
_, status, err := graph.AddChainSegment(blockA, true)
54+
_, status, err := graph.AddChainSegment(blockA, true, nil)
5555
require.NoError(t, err)
5656
require.Equal(t, Success, status)
5757
// Now make blockC a bad block
5858
blockC.Block.ProposerIndex = 81214459 // some invalid thing
59-
_, status, err = graph.AddChainSegment(blockC, true)
59+
_, status, err = graph.AddChainSegment(blockC, true, nil)
6060
require.Error(t, err)
6161
require.Equal(t, InvalidBlock, status)
6262
// Save current state hash
63-
_, status, err = graph.AddChainSegment(blockB, true)
63+
_, status, err = graph.AddChainSegment(blockB, true, nil)
6464
require.NoError(t, err)
6565
require.Equal(t, Success, status)
6666
// Try again with same should yield success
67-
_, status, err = graph.AddChainSegment(blockB, true)
67+
_, status, err = graph.AddChainSegment(blockB, true, nil)
6868
require.NoError(t, err)
6969
require.Equal(t, PreValidated, status)
7070
}

cl/phase1/forkchoice/fork_graph/interface.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,11 @@ import (
3535
* to analyze and manipulate the state of the blockchain.
3636
*/
3737
type ForkGraph interface {
38-
AddChainSegment(signedBlock *cltypes.SignedBeaconBlock, fullValidation bool) (*state.CachingBeaconState, ChainSegmentInsertionResult, error)
38+
// AddChainSegment processes a new block and returns the post-state.
39+
// parentFullState: if non-nil, use this as the starting state (for GLOAS when parent is FULL).
40+
AddChainSegment(signedBlock *cltypes.SignedBeaconBlock, fullValidation bool, parentFullState *state.CachingBeaconState) (*state.CachingBeaconState, ChainSegmentInsertionResult, error)
3941
GetHeader(blockRoot common.Hash) (*cltypes.BeaconBlockHeader, bool)
42+
GetBlock(blockRoot common.Hash) (*cltypes.SignedBeaconBlock, bool)
4043
GetState(blockRoot common.Hash, alwaysCopy bool) (*state.CachingBeaconState, error)
4144
GetCurrentJustifiedCheckpoint(blockRoot common.Hash) (solid.Checkpoint, bool)
4245
GetFinalizedCheckpoint(blockRoot common.Hash) (solid.Checkpoint, bool)

cl/phase1/forkchoice/forkchoice.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package forkchoice
1818

1919
import (
20+
"fmt"
2021
"slices"
2122
"sort"
2223
"sync"
@@ -146,11 +147,15 @@ type ForkChoiceStore struct {
146147
ethClock eth_clock.EthereumClock
147148
optimisticStore optimistic.OptimisticStore
148149
probabilisticHeadGetter bool
149-
}
150150

151-
type LatestMessage struct {
152-
Epoch uint64
153-
Root common.Hash
151+
// [New in Gloas:EIP7732] Stores post-execution-payload states keyed by beacon block root.
152+
// In GLOAS, beacon block and execution payload have separate state transitions.
153+
// This stores the state after ProcessExecutionPayloadEnvelope is applied.
154+
executionPayloadStates sync.Map // map[common.Hash]*state.CachingBeaconState
155+
// [New in Gloas:EIP7732]
156+
ptcVote sync.Map // map[common.Hash][clparams.PtcSize]bool
157+
// [New in Gloas:EIP7732] Indexed weight store for optimized weight calculation
158+
indexedWeightStore *indexedWeightStore
154159
}
155160

156161
type childrens struct {
@@ -296,6 +301,18 @@ func NewForkChoiceStore(
296301

297302
f.highestSeen.Store(anchorState.Slot())
298303
f.time.Store(anchorState.GenesisTime() + anchorState.BeaconConfig().SecondsPerSlot*anchorState.Slot())
304+
305+
// [New in Gloas:EIP7732] Initialize anchor root entries with anchor state copy
306+
anchorStateCopy, err := anchorState.Copy()
307+
if err != nil {
308+
return nil, fmt.Errorf("failed to copy anchor state for execution payload states: %w", err)
309+
}
310+
f.executionPayloadStates.Store(anchorRoot, anchorStateCopy)
311+
f.ptcVote.Store(anchorRoot, [clparams.PtcSize]bool{})
312+
313+
// [New in Gloas:EIP7732] Initialize indexed weight store
314+
f.indexedWeightStore = NewIndexedWeightStore(f)
315+
299316
return f, nil
300317
}
301318

cl/phase1/forkchoice/get_head.go

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sort"
2424
"time"
2525

26+
"github.com/erigontech/erigon/cl/clparams"
2627
"github.com/erigontech/erigon/cl/cltypes"
2728
"github.com/erigontech/erigon/cl/cltypes/solid"
2829
"github.com/erigontech/erigon/cl/phase1/core/state"
@@ -105,14 +106,94 @@ func (f *ForkChoiceStore) computeVotes(justifiedCheckpoint solid.Checkpoint, che
105106
}
106107

107108
// GetHead returns the head of the fork choice store.
108-
// it can take an optional auxilliary state to determine the current weights instead of computing the justified state.
109+
// Dispatches to GLOAS or pre-GLOAS implementation based on current epoch.
109110
func (f *ForkChoiceStore) GetHead(auxilliaryState *state.CachingBeaconState) (common.Hash, uint64, error) {
110111
f.mu.RLock()
111112
if f.headHash != (common.Hash{}) {
112113
f.mu.RUnlock()
113114
return f.headHash, f.headSlot, nil
114115
}
115116
f.mu.RUnlock()
117+
118+
currentEpoch := f.computeEpochAtSlot(f.Slot())
119+
if f.beaconCfg.GetCurrentStateVersion(currentEpoch) >= clparams.GloasVersion {
120+
return f.getHeadGloas()
121+
}
122+
return f.getHeadPreGloas(auxilliaryState)
123+
}
124+
125+
// getHeadGloas returns the head using GLOAS fork choice rules.
126+
// [New in Gloas:EIP7732]
127+
func (f *ForkChoiceStore) getHeadGloas() (common.Hash, uint64, error) {
128+
f.mu.Lock()
129+
defer f.mu.Unlock()
130+
131+
justifiedCheckpoint := f.justifiedCheckpoint.Load().(solid.Checkpoint)
132+
133+
// Get filtered block tree
134+
blocks := f.getFilteredBlockTree(justifiedCheckpoint.Root)
135+
136+
// Start from justified checkpoint with PENDING status
137+
head := ForkChoiceNode{
138+
Root: justifiedCheckpoint.Root,
139+
PayloadStatus: cltypes.PayloadStatusPending,
140+
}
141+
142+
// Get weight store for weight calculation
143+
ws := f.GetWeightStore()
144+
145+
for {
146+
children := f.getNodeChildren(head, blocks)
147+
if len(children) == 0 {
148+
// No children, head is the result
149+
header, hasHeader := f.forkGraph.GetHeader(head.Root)
150+
if !hasHeader {
151+
return common.Hash{}, 0, errors.New("no slot for head is stored")
152+
}
153+
f.headHash = head.Root
154+
f.headSlot = header.Slot
155+
return f.headHash, f.headSlot, nil
156+
}
157+
158+
// Find best child: max(children, key=(weight, root, tiebreaker))
159+
bestChild := children[0]
160+
bestWeight := ws.GetWeight(bestChild)
161+
bestTiebreaker := f.getPayloadStatusTiebreaker(bestChild)
162+
163+
for i := 1; i < len(children); i++ {
164+
child := children[i]
165+
weight := ws.GetWeight(child)
166+
tiebreaker := f.getPayloadStatusTiebreaker(child)
167+
168+
// Compare: weight first, then root, then tiebreaker
169+
if weight > bestWeight {
170+
bestChild = child
171+
bestWeight = weight
172+
bestTiebreaker = tiebreaker
173+
} else if weight == bestWeight {
174+
// Compare by root (lexicographically greater wins)
175+
rootCmp := bytes.Compare(child.Root[:], bestChild.Root[:])
176+
if rootCmp > 0 {
177+
bestChild = child
178+
bestWeight = weight
179+
bestTiebreaker = tiebreaker
180+
} else if rootCmp == 0 {
181+
// Same root, compare by tiebreaker
182+
if tiebreaker > bestTiebreaker {
183+
bestChild = child
184+
bestWeight = weight
185+
bestTiebreaker = tiebreaker
186+
}
187+
}
188+
}
189+
}
190+
191+
head = bestChild
192+
}
193+
}
194+
195+
// getHeadPreGloas returns the head using pre-GLOAS fork choice rules.
196+
func (f *ForkChoiceStore) getHeadPreGloas(auxilliaryState *state.CachingBeaconState) (common.Hash, uint64, error) {
116197
justifiedCheckpoint := f.justifiedCheckpoint.Load().(solid.Checkpoint)
117198
var justificationState *checkpointState
118199
var err error

cl/phase1/forkchoice/interface.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ type ForkChoiceStorage interface {
3434
}
3535

3636
type ForkChoiceStorageReader interface {
37-
Ancestor(root common.Hash, slot uint64) common.Hash
37+
// [Modified in Gloas:EIP7732] Returns ForkChoiceNode with payload status.
38+
Ancestor(root common.Hash, slot uint64) ForkChoiceNode
3839
AnchorSlot() uint64
3940
Engine() execution_client.ExecutionEngine
4041
FinalizedCheckpoint() solid.Checkpoint
@@ -96,6 +97,8 @@ type ForkChoiceStorageWriter interface {
9697
fullValidation bool,
9798
checkDataAvaibility bool,
9899
) error
100+
// [New in Gloas:EIP7732] OnExecutionPayload processes an execution payload envelope from the builder.
101+
OnExecutionPayload(ctx context.Context, signedEnvelope *cltypes.SignedExecutionPayloadEnvelope) error
99102
AddPreverifiedBlobSidecar(blobSidecar *cltypes.BlobSidecar) error
100103
OnTick(time uint64)
101104
SetSynced(synced bool)

0 commit comments

Comments
 (0)