@@ -107,6 +107,7 @@ type ForkChoiceStore struct {
107107 // [New in Gloas:EIP7732] Track execution payload validation status by execution block hash.
108108 // Used to check if parent execution payload has been validated/invalidated for gossip validation.
109109 executionPayloadStatus * lru.Cache [common.Hash , execution_client.PayloadStatus ]
110+ payloadStatusByRoot * lru.Cache [common.Hash , execution_client.PayloadStatus ]
110111 // [New in Gloas:EIP7732] Track execution payload gas_limit by execution block hash.
111112 // Used for the is_gas_limit_target_compatible IGNORE check in bid gossip validation.
112113 executionPayloadGasLimit * lru.Cache [common.Hash , uint64 ]
@@ -316,6 +317,10 @@ func NewForkChoiceStore(
316317 if err != nil {
317318 return nil , err
318319 }
320+ payloadStatusByRoot , err := lru.New [common.Hash , execution_client.PayloadStatus ](checkpointsPerCache )
321+ if err != nil {
322+ return nil , err
323+ }
319324
320325 // [New in Gloas:EIP7732] Track execution payload gas_limit by execution block hash
321326 executionPayloadGasLimit , err := lru.New [common.Hash , uint64 ](checkpointsPerCache )
@@ -377,6 +382,7 @@ func NewForkChoiceStore(
377382 pendingEnvelopes : pendingEnvelopes ,
378383 pendingLocalSelfBuildEnvelopes : pendingLocalSelfBuildEnvelopes ,
379384 executionPayloadStatus : executionPayloadStatus ,
385+ payloadStatusByRoot : payloadStatusByRoot ,
380386 executionPayloadGasLimit : executionPayloadGasLimit ,
381387 db : db ,
382388 }
@@ -427,6 +433,13 @@ func (f *ForkChoiceStore) GetRecentExecutionPayloadStatus(executionBlockHash com
427433 return f .executionPayloadStatus .Get (executionBlockHash )
428434}
429435
436+ func (f * ForkChoiceStore ) GetRecentExecutionPayloadStatusByRoot (blockRoot common.Hash ) (execution_client.PayloadStatus , bool ) {
437+ if f .payloadStatusByRoot == nil {
438+ return execution_client .PayloadStatusNone , false
439+ }
440+ return f .payloadStatusByRoot .Get (blockRoot )
441+ }
442+
430443// GetExecutionPayloadGasLimit returns the gas_limit of a recently validated execution payload.
431444func (f * ForkChoiceStore ) GetExecutionPayloadGasLimit (executionBlockHash common.Hash ) (uint64 , bool ) {
432445 return f .executionPayloadGasLimit .Get (executionBlockHash )
@@ -761,6 +774,58 @@ func (f *ForkChoiceStore) HasEnvelope(blockRoot common.Hash) bool {
761774 return f .forkGraph .HasEnvelope (blockRoot )
762775}
763776
777+ // IsPayloadVerified returns whether the execution payload for the beacon block root
778+ // has been accepted by the execution layer.
779+ // [New in Gloas:EIP7732]
780+ func (f * ForkChoiceStore ) IsPayloadVerified (blockRoot common.Hash ) bool {
781+ if f .verifiedExecutionPayload == nil {
782+ return false
783+ }
784+ return f .verifiedExecutionPayload .Contains (blockRoot )
785+ }
786+
787+ func (f * ForkChoiceStore ) MarkPayloadVerified (blockRoot common.Hash , executionBlockHash common.Hash ) {
788+ f .mu .Lock ()
789+ defer f .mu .Unlock ()
790+ f .markPayloadVerifiedLocked (blockRoot , executionBlockHash )
791+ }
792+
793+ func (f * ForkChoiceStore ) markPayloadVerifiedLocked (blockRoot common.Hash , executionBlockHash common.Hash ) {
794+ if f .verifiedExecutionPayload == nil {
795+ return
796+ }
797+ f .verifiedExecutionPayload .Add (blockRoot , struct {}{})
798+ if f .executionPayloadStatus != nil {
799+ f .executionPayloadStatus .Add (executionBlockHash , execution_client .PayloadStatusValidated )
800+ }
801+ if f .payloadStatusByRoot != nil {
802+ f .payloadStatusByRoot .Add (blockRoot , execution_client .PayloadStatusValidated )
803+ }
804+ f .headHash = common.Hash {}
805+ f .headPayloadStatus = cltypes .PayloadStatusPending
806+ }
807+
808+ func (f * ForkChoiceStore ) MarkPayloadInvalid (blockRoot common.Hash , executionBlockHash common.Hash ) {
809+ f .mu .Lock ()
810+ defer f .mu .Unlock ()
811+ f .markPayloadInvalidLocked (blockRoot , executionBlockHash )
812+ }
813+
814+ func (f * ForkChoiceStore ) markPayloadInvalidLocked (blockRoot common.Hash , executionBlockHash common.Hash ) {
815+ if f .verifiedExecutionPayload != nil {
816+ f .verifiedExecutionPayload .Remove (blockRoot )
817+ }
818+ if f .executionPayloadStatus != nil {
819+ f .executionPayloadStatus .Add (executionBlockHash , execution_client .PayloadStatusInvalidated )
820+ }
821+ if f .payloadStatusByRoot != nil {
822+ f .payloadStatusByRoot .Add (blockRoot , execution_client .PayloadStatusInvalidated )
823+ }
824+ f .forkGraph .MarkHeaderAsInvalid (blockRoot )
825+ f .headHash = common.Hash {}
826+ f .headPayloadStatus = cltypes .PayloadStatusPending
827+ }
828+
764829// ReadEnvelopeFromDisk delegates to forkGraph.ReadEnvelopeFromDisk.
765830// [New in Gloas:EIP7732]
766831func (f * ForkChoiceStore ) ReadEnvelopeFromDisk (blockRoot common.Hash ) (* cltypes.SignedExecutionPayloadEnvelope , error ) {
@@ -985,6 +1050,14 @@ func (f *ForkChoiceStore) GetProposerLookahead(slot uint64) (solid.Uint64VectorS
9851050func (f * ForkChoiceStore ) addPendingELPayload (block * cltypes.SignedBeaconBlock , envelope * cltypes.SignedExecutionPayloadEnvelope ) {
9861051 f .pendingELPayloadsMu .Lock ()
9871052 defer f .pendingELPayloadsMu .Unlock ()
1053+ root , ok := pendingELPayloadRoot (PendingELPayload {Block : block , Envelope : envelope })
1054+ if ok {
1055+ for _ , p := range f .pendingELPayloads {
1056+ if existingRoot , existingOk := pendingELPayloadRoot (p ); existingOk && existingRoot == root {
1057+ return
1058+ }
1059+ }
1060+ }
9881061 if len (f .pendingELPayloads ) >= maxPendingELPayloads {
9891062 log .Warn ("addPendingELPayload: dropping oldest pending EL payload" , "queueLen" , len (f .pendingELPayloads ))
9901063 copy (f .pendingELPayloads , f .pendingELPayloads [1 :])
@@ -997,8 +1070,21 @@ func (f *ForkChoiceStore) addPendingELPayload(block *cltypes.SignedBeaconBlock,
9971070 })
9981071}
9991072
1073+ func pendingELPayloadRoot (p PendingELPayload ) (common.Hash , bool ) {
1074+ if p .Envelope != nil && p .Envelope .Message != nil && p .Envelope .Message .BeaconBlockRoot != (common.Hash {}) {
1075+ return p .Envelope .Message .BeaconBlockRoot , true
1076+ }
1077+ return common.Hash {}, false
1078+ }
1079+
1080+ // RequeuePendingELPayload queues a drained execution payload for another EL validation attempt.
1081+ // [New in Gloas:EIP7732]
1082+ func (f * ForkChoiceStore ) RequeuePendingELPayload (p PendingELPayload ) {
1083+ f .addPendingELPayload (p .Block , p .Envelope )
1084+ }
1085+
10001086// DrainPendingELPayloads returns and clears all queued EL payloads.
1001- // The stages layer calls this before Flush() to add them to blockCollector .
1087+ // The stages layer calls this before Flush() to retry them with engine.NewPayload .
10021088func (f * ForkChoiceStore ) DrainPendingELPayloads () []PendingELPayload {
10031089 f .pendingELPayloadsMu .Lock ()
10041090 defer f .pendingELPayloadsMu .Unlock ()
0 commit comments