erigon-pulse/polygon/sync/heimdall.go
battlmonstr 2793ef6ec1
polygon: flatten redundant packages (#9241)
* move mocks to the owner packages
* squash single file packages
* move types to more appropriate files
* remove unused mocks
2024-01-16 09:23:02 +01:00

175 lines
4.2 KiB
Go

package sync
import (
"context"
"errors"
"math/big"
"time"
"github.com/ledgerwatch/log/v3"
"github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/polygon/bor"
"github.com/ledgerwatch/erigon/polygon/heimdall"
)
// Heimdall is a wrapper of Heimdall HTTP API
//
//go:generate mockgen -destination=./heimdall_mock.go -package=sync . Heimdall
type Heimdall interface {
FetchCheckpoints(ctx context.Context, start uint64) ([]*heimdall.Checkpoint, error)
FetchMilestones(ctx context.Context, start uint64) ([]*heimdall.Milestone, error)
FetchSpan(ctx context.Context, start uint64) (*heimdall.HeimdallSpan, error)
OnMilestoneEvent(ctx context.Context, callback func(*heimdall.Milestone)) error
}
// ErrIncompleteMilestoneRange happens when FetchMilestones is called with an old start block because old milestones are evicted
var ErrIncompleteMilestoneRange = errors.New("milestone range doesn't contain the start block")
type HeimdallImpl struct {
client heimdall.HeimdallClient
pollDelay time.Duration
logger log.Logger
}
func NewHeimdall(client heimdall.HeimdallClient, logger log.Logger) Heimdall {
impl := HeimdallImpl{
client: client,
pollDelay: time.Second,
logger: logger,
}
return &impl
}
func cmpNumToRange(n uint64, min *big.Int, max *big.Int) int {
num := new(big.Int).SetUint64(n)
if num.Cmp(min) < 0 {
return -1
}
if num.Cmp(max) > 0 {
return 1
}
return 0
}
func cmpBlockNumToCheckpointRange(n uint64, c *heimdall.Checkpoint) int {
return cmpNumToRange(n, c.StartBlock, c.EndBlock)
}
func cmpBlockNumToMilestoneRange(n uint64, m *heimdall.Milestone) int {
return cmpNumToRange(n, m.StartBlock, m.EndBlock)
}
func (impl *HeimdallImpl) FetchCheckpoints(ctx context.Context, start uint64) ([]*heimdall.Checkpoint, error) {
count, err := impl.client.FetchCheckpointCount(ctx)
if err != nil {
return nil, err
}
var checkpoints []*heimdall.Checkpoint
for i := count; i >= 1; i-- {
c, err := impl.client.FetchCheckpoint(ctx, i)
if err != nil {
return nil, err
}
cmpResult := cmpBlockNumToCheckpointRange(start, c)
// the start block is past the last checkpoint
if cmpResult > 0 {
return nil, nil
}
checkpoints = append(checkpoints, c)
// the checkpoint contains the start block
if cmpResult == 0 {
break
}
}
common.SliceReverse(checkpoints)
return checkpoints, nil
}
func (impl *HeimdallImpl) FetchMilestones(ctx context.Context, start uint64) ([]*heimdall.Milestone, error) {
count, err := impl.client.FetchMilestoneCount(ctx)
if err != nil {
return nil, err
}
var milestones []*heimdall.Milestone
for i := count; i >= 1; i-- {
m, err := impl.client.FetchMilestone(ctx, i)
if err != nil {
if errors.Is(err, heimdall.ErrNotInMilestoneList) {
common.SliceReverse(milestones)
return milestones, ErrIncompleteMilestoneRange
}
return nil, err
}
cmpResult := cmpBlockNumToMilestoneRange(start, m)
// the start block is past the last milestone
if cmpResult > 0 {
return nil, nil
}
milestones = append(milestones, m)
// the checkpoint contains the start block
if cmpResult == 0 {
break
}
}
common.SliceReverse(milestones)
return milestones, nil
}
func (impl *HeimdallImpl) FetchSpan(ctx context.Context, start uint64) (*heimdall.HeimdallSpan, error) {
return impl.client.Span(ctx, bor.SpanIDAt(start))
}
func (impl *HeimdallImpl) OnMilestoneEvent(ctx context.Context, callback func(*heimdall.Milestone)) error {
currentCount, err := impl.client.FetchMilestoneCount(ctx)
if err != nil {
return err
}
go func() {
for {
count, err := impl.client.FetchMilestoneCount(ctx)
if err != nil {
if !errors.Is(err, context.Canceled) {
impl.logger.Error("HeimdallImpl.OnMilestoneEvent FetchMilestoneCount error", "err", err)
}
break
}
if count <= currentCount {
pollDelayTimer := time.NewTimer(impl.pollDelay)
select {
case <-ctx.Done():
return
case <-pollDelayTimer.C:
}
} else {
currentCount = count
m, err := impl.client.FetchMilestone(ctx, count)
if err != nil {
if !errors.Is(err, context.Canceled) {
impl.logger.Error("HeimdallImpl.OnMilestoneEvent FetchMilestone error", "err", err)
}
break
}
go callback(m)
}
}
}()
return nil
}