github.com/ethereum/go-ethereum@v1.14.3/beacon/blsync/block_sync.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package blsync
    18  
    19  import (
    20  	"github.com/ethereum/go-ethereum/beacon/light/request"
    21  	"github.com/ethereum/go-ethereum/beacon/light/sync"
    22  	"github.com/ethereum/go-ethereum/beacon/params"
    23  	"github.com/ethereum/go-ethereum/beacon/types"
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"github.com/ethereum/go-ethereum/common/lru"
    26  	"github.com/ethereum/go-ethereum/event"
    27  	"github.com/ethereum/go-ethereum/log"
    28  )
    29  
    30  // beaconBlockSync implements request.Module; it fetches the beacon blocks belonging
    31  // to the validated and prefetch heads.
    32  type beaconBlockSync struct {
    33  	recentBlocks *lru.Cache[common.Hash, *types.BeaconBlock]
    34  	locked       map[common.Hash]request.ServerAndID
    35  	serverHeads  map[request.Server]common.Hash
    36  	headTracker  headTracker
    37  
    38  	lastHeadInfo  types.HeadInfo
    39  	chainHeadFeed event.FeedOf[types.ChainHeadEvent]
    40  }
    41  
    42  type headTracker interface {
    43  	PrefetchHead() types.HeadInfo
    44  	ValidatedOptimistic() (types.OptimisticUpdate, bool)
    45  	ValidatedFinality() (types.FinalityUpdate, bool)
    46  }
    47  
    48  // newBeaconBlockSync returns a new beaconBlockSync.
    49  func newBeaconBlockSync(headTracker headTracker) *beaconBlockSync {
    50  	return &beaconBlockSync{
    51  		headTracker:  headTracker,
    52  		recentBlocks: lru.NewCache[common.Hash, *types.BeaconBlock](10),
    53  		locked:       make(map[common.Hash]request.ServerAndID),
    54  		serverHeads:  make(map[request.Server]common.Hash),
    55  	}
    56  }
    57  
    58  func (s *beaconBlockSync) SubscribeChainHead(ch chan<- types.ChainHeadEvent) event.Subscription {
    59  	return s.chainHeadFeed.Subscribe(ch)
    60  }
    61  
    62  // Process implements request.Module.
    63  func (s *beaconBlockSync) Process(requester request.Requester, events []request.Event) {
    64  	for _, event := range events {
    65  		switch event.Type {
    66  		case request.EvResponse, request.EvFail, request.EvTimeout:
    67  			sid, req, resp := event.RequestInfo()
    68  			blockRoot := common.Hash(req.(sync.ReqBeaconBlock))
    69  			log.Debug("Beacon block event", "type", event.Type.Name, "hash", blockRoot)
    70  			if resp != nil {
    71  				s.recentBlocks.Add(blockRoot, resp.(*types.BeaconBlock))
    72  			}
    73  			if s.locked[blockRoot] == sid {
    74  				delete(s.locked, blockRoot)
    75  			}
    76  		case sync.EvNewHead:
    77  			s.serverHeads[event.Server] = event.Data.(types.HeadInfo).BlockRoot
    78  		case request.EvUnregistered:
    79  			delete(s.serverHeads, event.Server)
    80  		}
    81  	}
    82  	s.updateEventFeed()
    83  	// request validated head block if unavailable and not yet requested
    84  	if vh, ok := s.headTracker.ValidatedOptimistic(); ok {
    85  		s.tryRequestBlock(requester, vh.Attested.Hash(), false)
    86  	}
    87  	// request prefetch head if the given server has announced it
    88  	if prefetchHead := s.headTracker.PrefetchHead().BlockRoot; prefetchHead != (common.Hash{}) {
    89  		s.tryRequestBlock(requester, prefetchHead, true)
    90  	}
    91  }
    92  
    93  func (s *beaconBlockSync) tryRequestBlock(requester request.Requester, blockRoot common.Hash, needSameHead bool) {
    94  	if _, ok := s.recentBlocks.Get(blockRoot); ok {
    95  		return
    96  	}
    97  	if _, ok := s.locked[blockRoot]; ok {
    98  		return
    99  	}
   100  	for _, server := range requester.CanSendTo() {
   101  		if needSameHead && (s.serverHeads[server] != blockRoot) {
   102  			continue
   103  		}
   104  		id := requester.Send(server, sync.ReqBeaconBlock(blockRoot))
   105  		s.locked[blockRoot] = request.ServerAndID{Server: server, ID: id}
   106  		return
   107  	}
   108  }
   109  
   110  func blockHeadInfo(block *types.BeaconBlock) types.HeadInfo {
   111  	if block == nil {
   112  		return types.HeadInfo{}
   113  	}
   114  	return types.HeadInfo{Slot: block.Slot(), BlockRoot: block.Root()}
   115  }
   116  
   117  func (s *beaconBlockSync) updateEventFeed() {
   118  	optimistic, ok := s.headTracker.ValidatedOptimistic()
   119  	if !ok {
   120  		return
   121  	}
   122  
   123  	validatedHead := optimistic.Attested.Hash()
   124  	headBlock, ok := s.recentBlocks.Get(validatedHead)
   125  	if !ok {
   126  		return
   127  	}
   128  
   129  	var finalizedHash common.Hash
   130  	if finality, ok := s.headTracker.ValidatedFinality(); ok {
   131  		he := optimistic.Attested.Epoch()
   132  		fe := finality.Attested.Header.Epoch()
   133  		switch {
   134  		case he == fe:
   135  			finalizedHash = finality.Finalized.PayloadHeader.BlockHash()
   136  		case he < fe:
   137  			return
   138  		case he == fe+1:
   139  			parent, ok := s.recentBlocks.Get(optimistic.Attested.ParentRoot)
   140  			if !ok || parent.Slot()/params.EpochLength == fe {
   141  				return // head is at first slot of next epoch, wait for finality update
   142  			}
   143  		}
   144  	}
   145  
   146  	headInfo := blockHeadInfo(headBlock)
   147  	if headInfo == s.lastHeadInfo {
   148  		return
   149  	}
   150  	s.lastHeadInfo = headInfo
   151  
   152  	// new head block and finality info available; extract executable data and send event to feed
   153  	execBlock, err := headBlock.ExecutionPayload()
   154  	if err != nil {
   155  		log.Error("Error extracting execution block from validated beacon block", "error", err)
   156  		return
   157  	}
   158  	s.chainHeadFeed.Send(types.ChainHeadEvent{
   159  		BeaconHead: optimistic.Attested.Header,
   160  		Block:      execBlock,
   161  		Finalized:  finalizedHash,
   162  	})
   163  }