@@ -495,6 +495,109 @@ func (f *forkGraphDisk) hasBeaconState(blockRoot common.Hash) bool {
495495 return err == nil && exists
496496}
497497
498+ // GetExecutionPayloadState reconstructs the post-execution-payload state by replaying.
499+ // Similar to getState() but handles GLOAS FULL/EMPTY paths.
500+ // [New in Gloas:EIP7732]
501+ func (f * forkGraphDisk ) GetExecutionPayloadState (blockRoot common.Hash ) (* state.CachingBeaconState , error ) {
502+ // 1. Read target envelope
503+ targetEnvelope , err := f .ReadEnvelopeFromDisk (blockRoot )
504+ if err != nil {
505+ return nil , fmt .Errorf ("GetExecutionPayloadState: failed to read envelope: %w" , err )
506+ }
507+
508+ // 2. Get block_state for the target block (handles FULL/EMPTY paths)
509+ blockState , err := f .getStateWithEnvelopes (blockRoot )
510+ if err != nil {
511+ return nil , fmt .Errorf ("GetExecutionPayloadState: failed to get block state: %w" , err )
512+ }
513+
514+ // 3. Apply target envelope to get exec_payload_state
515+ if err := transition .DefaultMachine .ProcessExecutionPayloadEnvelope (blockState , targetEnvelope ); err != nil {
516+ return nil , fmt .Errorf ("GetExecutionPayloadState: failed to process envelope: %w" , err )
517+ }
518+
519+ return blockState , nil
520+ }
521+
522+ // getStateWithEnvelopes is like getState but handles GLOAS FULL/EMPTY parent paths.
523+ // When replaying blocks, if a block was built on parent's FULL path, applies parent's envelope first.
524+ // [New in Gloas:EIP7732]
525+ func (f * forkGraphDisk ) getStateWithEnvelopes (blockRoot common.Hash ) (* state.CachingBeaconState , error ) {
526+ // Collect blocks with their parent path info
527+ type blockInfo struct {
528+ block * cltypes.SignedBeaconBlock
529+ parentWasFull bool
530+ parentEnvelope * cltypes.SignedExecutionPayloadEnvelope
531+ }
532+ blocksInTheWay := []blockInfo {}
533+ currentRoot := blockRoot
534+ var checkpointState * state.CachingBeaconState
535+
536+ // Walk back to find checkpoint
537+ for checkpointState == nil {
538+ block , ok := f .GetBlock (currentRoot )
539+ if ! ok {
540+ // Check if it's a header-only checkpoint
541+ bHeader , headerOk := f .GetHeader (currentRoot )
542+ if headerOk && bHeader .Slot % dumpSlotFrequency == 0 {
543+ checkpointState , _ = f .readBeaconStateFromDisk (currentRoot )
544+ }
545+ if checkpointState != nil {
546+ break
547+ }
548+ return nil , fmt .Errorf ("getStateWithEnvelopes: block not found: %x" , currentRoot )
549+ }
550+
551+ // Try to read checkpoint state
552+ if block .Block .Slot % dumpSlotFrequency == 0 {
553+ checkpointState , _ = f .readBeaconStateFromDisk (currentRoot )
554+ if checkpointState != nil {
555+ break
556+ }
557+ }
558+
559+ // Check if this block was built on parent's FULL path
560+ parentRoot := block .Block .ParentRoot
561+ parentEnvelope , _ := f .ReadEnvelopeFromDisk (parentRoot )
562+ parentWasFull := false
563+ if parentEnvelope != nil && block .Block .Body .SignedExecutionPayloadBid != nil &&
564+ block .Block .Body .SignedExecutionPayloadBid .Message != nil {
565+ // Compare block's bid.ParentBlockHash with parent envelope's payload.BlockHash
566+ bidHash := block .Block .Body .SignedExecutionPayloadBid .Message .ParentBlockHash
567+ envHash := parentEnvelope .Message .Payload .BlockHash
568+ parentWasFull = (bidHash == envHash )
569+ }
570+
571+ blocksInTheWay = append (blocksInTheWay , blockInfo {
572+ block : block ,
573+ parentWasFull : parentWasFull ,
574+ parentEnvelope : parentEnvelope ,
575+ })
576+ currentRoot = parentRoot
577+ }
578+
579+ // Replay forward from checkpoint
580+ // Note: checkpointState is always a block_state (from DumpBeaconStateOnDisk)
581+ replayState := checkpointState
582+ for i := len (blocksInTheWay ) - 1 ; i >= 0 ; i -- {
583+ bi := blocksInTheWay [i ]
584+
585+ // If this block was built on parent's FULL path, apply parent envelope first
586+ if bi .parentWasFull && bi .parentEnvelope != nil {
587+ if err := transition .DefaultMachine .ProcessExecutionPayloadEnvelope (replayState , bi .parentEnvelope ); err != nil {
588+ return nil , fmt .Errorf ("getStateWithEnvelopes: failed to process parent envelope: %w" , err )
589+ }
590+ }
591+
592+ // Apply process_block to get this block's block_state
593+ if err := transition .TransitionState (replayState , bi .block , nil , false ); err != nil {
594+ return nil , fmt .Errorf ("getStateWithEnvelopes: failed to transition state: %w" , err )
595+ }
596+ }
597+
598+ return replayState , nil
599+ }
600+
498601func (f * forkGraphDisk ) Prune (pruneSlot uint64 ) (err error ) {
499602 oldRoots := make ([]common.Hash , 0 , f .beaconCfg .SlotsPerEpoch )
500603 highestStoredBeaconStateSlot := uint64 (0 )
@@ -529,6 +632,8 @@ func (f *forkGraphDisk) Prune(pruneSlot uint64) (err error) {
529632 f .headers .Delete (root )
530633 f .blockRewards .Delete (root )
531634 f .fs .Remove (getBeaconStateFilename (root ))
635+ // [New in Gloas:EIP7732] Also remove envelope files
636+ f .fs .Remove (getEnvelopeFilename (root ))
532637 }
533638 log .Debug ("Pruned old blocks" , "pruneSlot" , pruneSlot )
534639 return
0 commit comments