aadcb88675
Here we add a beacon chain light client for use by geth. Geth can now be configured to run against a beacon chain API endpoint, without pointing a CL to it. To set this up, use the `--beacon.api` flag. Information provided by the beacon chain is verified, i.e. geth does not blindly trust the beacon API endpoint in this mode. The root of trust are the beacon chain 'sync committees'. The configured beacon API endpoint must provide light client data. At this time, only Lodestar and Nimbus provide the necessary APIs. There is also a standalone tool, cmd/blsync, which uses the beacon chain light client to drive any EL implementation via its engine API. --------- Co-authored-by: Felix Lange <fjl@twurst.com>
177 lines
6.1 KiB
Go
177 lines
6.1 KiB
Go
// Copyright 2023 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package sync
|
|
|
|
import (
|
|
"github.com/ethereum/go-ethereum/beacon/light/request"
|
|
"github.com/ethereum/go-ethereum/beacon/types"
|
|
)
|
|
|
|
type headTracker interface {
|
|
ValidateHead(head types.SignedHeader) (bool, error)
|
|
ValidateFinality(head types.FinalityUpdate) (bool, error)
|
|
SetPrefetchHead(head types.HeadInfo)
|
|
}
|
|
|
|
// HeadSync implements request.Module; it updates the validated and prefetch
|
|
// heads of HeadTracker based on the EvHead and EvSignedHead events coming from
|
|
// registered servers.
|
|
// It can also postpone the validation of the latest announced signed head
|
|
// until the committee chain is synced up to at least the required period.
|
|
type HeadSync struct {
|
|
headTracker headTracker
|
|
chain committeeChain
|
|
nextSyncPeriod uint64
|
|
chainInit bool
|
|
unvalidatedHeads map[request.Server]types.SignedHeader
|
|
unvalidatedFinality map[request.Server]types.FinalityUpdate
|
|
serverHeads map[request.Server]types.HeadInfo
|
|
headServerCount map[types.HeadInfo]headServerCount
|
|
headCounter uint64
|
|
prefetchHead types.HeadInfo
|
|
}
|
|
|
|
// headServerCount is associated with most recently seen head infos; it counts
|
|
// the number of servers currently having the given head info as their announced
|
|
// head and a counter signaling how recent that head is.
|
|
// This data is used for selecting the prefetch head.
|
|
type headServerCount struct {
|
|
serverCount int
|
|
headCounter uint64
|
|
}
|
|
|
|
// NewHeadSync creates a new HeadSync.
|
|
func NewHeadSync(headTracker headTracker, chain committeeChain) *HeadSync {
|
|
s := &HeadSync{
|
|
headTracker: headTracker,
|
|
chain: chain,
|
|
unvalidatedHeads: make(map[request.Server]types.SignedHeader),
|
|
unvalidatedFinality: make(map[request.Server]types.FinalityUpdate),
|
|
serverHeads: make(map[request.Server]types.HeadInfo),
|
|
headServerCount: make(map[types.HeadInfo]headServerCount),
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Process implements request.Module.
|
|
func (s *HeadSync) Process(requester request.Requester, events []request.Event) {
|
|
for _, event := range events {
|
|
switch event.Type {
|
|
case EvNewHead:
|
|
s.setServerHead(event.Server, event.Data.(types.HeadInfo))
|
|
case EvNewSignedHead:
|
|
s.newSignedHead(event.Server, event.Data.(types.SignedHeader))
|
|
case EvNewFinalityUpdate:
|
|
s.newFinalityUpdate(event.Server, event.Data.(types.FinalityUpdate))
|
|
case request.EvUnregistered:
|
|
s.setServerHead(event.Server, types.HeadInfo{})
|
|
delete(s.serverHeads, event.Server)
|
|
delete(s.unvalidatedHeads, event.Server)
|
|
}
|
|
}
|
|
|
|
nextPeriod, chainInit := s.chain.NextSyncPeriod()
|
|
if nextPeriod != s.nextSyncPeriod || chainInit != s.chainInit {
|
|
s.nextSyncPeriod, s.chainInit = nextPeriod, chainInit
|
|
s.processUnvalidated()
|
|
}
|
|
}
|
|
|
|
// newSignedHead handles received signed head; either validates it if the chain
|
|
// is properly synced or stores it for further validation.
|
|
func (s *HeadSync) newSignedHead(server request.Server, signedHead types.SignedHeader) {
|
|
if !s.chainInit || types.SyncPeriod(signedHead.SignatureSlot) > s.nextSyncPeriod {
|
|
s.unvalidatedHeads[server] = signedHead
|
|
return
|
|
}
|
|
s.headTracker.ValidateHead(signedHead)
|
|
}
|
|
|
|
// newSignedHead handles received signed head; either validates it if the chain
|
|
// is properly synced or stores it for further validation.
|
|
func (s *HeadSync) newFinalityUpdate(server request.Server, finalityUpdate types.FinalityUpdate) {
|
|
if !s.chainInit || types.SyncPeriod(finalityUpdate.SignatureSlot) > s.nextSyncPeriod {
|
|
s.unvalidatedFinality[server] = finalityUpdate
|
|
return
|
|
}
|
|
s.headTracker.ValidateFinality(finalityUpdate)
|
|
}
|
|
|
|
// processUnvalidatedHeads iterates the list of unvalidated heads and validates
|
|
// those which can be validated.
|
|
func (s *HeadSync) processUnvalidated() {
|
|
if !s.chainInit {
|
|
return
|
|
}
|
|
for server, signedHead := range s.unvalidatedHeads {
|
|
if types.SyncPeriod(signedHead.SignatureSlot) <= s.nextSyncPeriod {
|
|
s.headTracker.ValidateHead(signedHead)
|
|
delete(s.unvalidatedHeads, server)
|
|
}
|
|
}
|
|
for server, finalityUpdate := range s.unvalidatedFinality {
|
|
if types.SyncPeriod(finalityUpdate.SignatureSlot) <= s.nextSyncPeriod {
|
|
s.headTracker.ValidateFinality(finalityUpdate)
|
|
delete(s.unvalidatedFinality, server)
|
|
}
|
|
}
|
|
}
|
|
|
|
// setServerHead processes non-validated server head announcements and updates
|
|
// the prefetch head if necessary.
|
|
func (s *HeadSync) setServerHead(server request.Server, head types.HeadInfo) bool {
|
|
if oldHead, ok := s.serverHeads[server]; ok {
|
|
if head == oldHead {
|
|
return false
|
|
}
|
|
h := s.headServerCount[oldHead]
|
|
if h.serverCount--; h.serverCount > 0 {
|
|
s.headServerCount[oldHead] = h
|
|
} else {
|
|
delete(s.headServerCount, oldHead)
|
|
}
|
|
}
|
|
if head != (types.HeadInfo{}) {
|
|
h, ok := s.headServerCount[head]
|
|
if !ok {
|
|
s.headCounter++
|
|
h.headCounter = s.headCounter
|
|
}
|
|
h.serverCount++
|
|
s.headServerCount[head] = h
|
|
s.serverHeads[server] = head
|
|
} else {
|
|
delete(s.serverHeads, server)
|
|
}
|
|
var (
|
|
bestHead types.HeadInfo
|
|
bestHeadInfo headServerCount
|
|
)
|
|
for head, headServerCount := range s.headServerCount {
|
|
if headServerCount.serverCount > bestHeadInfo.serverCount ||
|
|
(headServerCount.serverCount == bestHeadInfo.serverCount && headServerCount.headCounter > bestHeadInfo.headCounter) {
|
|
bestHead, bestHeadInfo = head, headServerCount
|
|
}
|
|
}
|
|
if bestHead == s.prefetchHead {
|
|
return false
|
|
}
|
|
s.prefetchHead = bestHead
|
|
s.headTracker.SetPrefetchHead(bestHead)
|
|
return true
|
|
}
|