go-ethereum/beacon/light/sync/header_sync.go
2023-05-10 14:20:48 +02:00

187 lines
5.3 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 (
"math"
"sync"
"github.com/ethereum/go-ethereum/beacon/light"
"github.com/ethereum/go-ethereum/beacon/light/request"
"github.com/ethereum/go-ethereum/beacon/light/types"
"github.com/ethereum/go-ethereum/common"
)
type beaconHeaderServer interface {
request.RequestServer
RequestBeaconHeader(blockRoot common.Hash, response func(*types.Header))
}
type HeaderSync struct {
lock sync.Mutex
reqLock request.MultiLock
chain *light.LightChain
prefetch bool
targetHead, syncPtr types.Header
targetTailSlot uint64
selfTrigger, chainTrigger *request.ModuleTrigger
}
func NewHeaderSync(chain *light.LightChain, prefetch bool) *HeaderSync {
return &HeaderSync{
chain: chain,
prefetch: prefetch,
targetTailSlot: math.MaxUint64,
}
}
// SetupModuleTriggers implements request.Module
func (s *HeaderSync) SetupModuleTriggers(trigger func(id string, subscribe bool) *request.ModuleTrigger) {
s.selfTrigger = trigger("headerSync", true)
s.reqLock.Trigger = s.selfTrigger
trigger("validatedHead", true)
s.chainTrigger = trigger("headerChain", false)
}
func (s *HeaderSync) SetTailTarget(targetTailSlot uint64) {
s.lock.Lock()
defer s.lock.Unlock()
if targetTailSlot < s.targetTailSlot {
s.selfTrigger.Trigger()
}
s.targetTailSlot = targetTailSlot
}
// Process implements request.Module
func (s *HeaderSync) Process(env *request.Environment) {
s.lock.Lock()
defer s.lock.Unlock()
validatedHead := env.ValidatedHead()
if validatedHead != s.targetHead {
s.targetHead = validatedHead
s.syncPtr = validatedHead
s.chain.AddHeader(validatedHead)
}
if s.targetHead == (types.Header{}) {
return
}
chainHead, chainTail, chainInit := s.chain.HeaderRange()
if !chainInit {
s.chain.AddHeader(s.targetHead)
s.chain.SetHead(s.targetHead)
s.selfTrigger.Trigger()
s.chainTrigger.Trigger()
}
if !env.CanRequestNow() {
return
}
if s.prefetch {
if prefetchHead := env.PrefetchHead(); !s.chain.HasHeader(prefetchHead) {
s.tryPrefetchHead(env, prefetchHead)
}
}
if chainHead != s.targetHead && !s.trySyncHead(env, chainTail.Slot) {
// always prioritize syncing to the latest head, do not start tail sync until done
return
}
if s.targetTailSlot < chainTail.Slot {
s.trySyncTail(env, chainTail)
}
}
// returns true if targetHead has been reached
func (s *HeaderSync) trySyncHead(env *request.Environment, chainTailSlot uint64) bool {
for {
if s.syncPtr.Slot <= chainTailSlot || s.chain.IsCanonical(s.syncPtr) {
s.chain.SetHead(s.targetHead)
s.chainTrigger.Trigger()
return true
}
if parent, err := s.chain.GetHeaderByHash(s.syncPtr.ParentRoot); err == nil {
s.syncPtr = parent
} else {
s.tryRequestHeader(env, s.syncPtr.ParentRoot, false)
return false
}
}
}
func (s *HeaderSync) trySyncTail(env *request.Environment, syncTail types.Header) {
for {
if parent, err := s.chain.GetHeaderByHash(syncTail.ParentRoot); err == nil {
syncTail = parent
} else {
s.tryRequestHeader(env, syncTail.ParentRoot, false)
return
}
}
}
func (s *HeaderSync) tryPrefetchHead(env *request.Environment, head common.Hash) {
if head != (common.Hash{}) && !s.chain.HasHeader(head) {
s.tryRequestHeader(env, head, true)
}
}
func (s *HeaderSync) tryRequestHeader(env *request.Environment, blockRoot common.Hash, prefetch bool) {
if s.reqLock.CanRequest(blockRoot) {
env.TryRequest(headerRequest{
HeaderSync: s,
blockRoot: blockRoot,
prefetch: prefetch,
})
}
}
type headerRequest struct {
*HeaderSync
blockRoot common.Hash
prefetch bool
}
func (r headerRequest) CanSendTo(server *request.Server, moduleData *interface{}) (canSend bool, priority uint64) {
if _, ok := server.RequestServer.(beaconHeaderServer); !ok {
return false, 0
}
if !r.prefetch {
return true, 0
}
_, headRoot := server.LatestHead()
return r.blockRoot == headRoot, 0
}
func (r headerRequest) SendTo(server *request.Server, moduleData *interface{}) {
reqId := r.reqLock.Send(server, r.blockRoot)
server.RequestServer.(beaconHeaderServer).RequestBeaconHeader(r.blockRoot, func(header *types.Header) {
r.lock.Lock()
defer r.lock.Unlock()
r.reqLock.Returned(server, reqId, r.blockRoot)
if header == nil {
server.Fail("error retrieving beacon header")
return
}
_, oldChainTail, _ := r.chain.HeaderRange()
r.chain.AddHeader(*header)
_, chainTail, _ := r.chain.HeaderRange()
if chainTail != oldChainTail {
r.chainTrigger.Trigger() //TODO do this in a nicer way?
}
})
}