polygon/sync: span updates (#9229)

It is possible that a span update happens during a milestone.
A headers slice might cross to the new span.
Also if 2 forks evolve simulaneously, a shorter fork can still be in the
previous span.
In these cases we need access to the previous span to calculate
difficulty and validate header times.

SpansCache will keep recent spans.
The cache will be updated on new span events from the heimdall.
The cache is pruned on new milestone events and in practice no more than
2 spans are kept.

The header difficulty calculation and time validation depends on having
a span for that header in the cache.
This commit is contained in:
battlmonstr 2024-01-15 12:36:25 +01:00 committed by GitHub
parent 8d4d4d802c
commit c5b75d00ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 96 additions and 41 deletions

View File

@ -38,19 +38,20 @@ type canonicalChainBuilderImpl struct {
tip *forkTreeNode tip *forkTreeNode
difficultyCalc DifficultyCalculator difficultyCalc DifficultyCalculator
headerValidator HeaderValidator headerValidator HeaderValidator
spansCache *SpansCache
} }
func NewCanonicalChainBuilder( func NewCanonicalChainBuilder(
root *types.Header, root *types.Header,
difficultyCalc DifficultyCalculator, difficultyCalc DifficultyCalculator,
headerValidator HeaderValidator, headerValidator HeaderValidator,
spansCache *SpansCache,
) CanonicalChainBuilder { ) CanonicalChainBuilder {
impl := &canonicalChainBuilderImpl{ impl := &canonicalChainBuilderImpl{
difficultyCalc: difficultyCalc, difficultyCalc: difficultyCalc,
headerValidator: headerValidator, headerValidator: headerValidator,
spansCache: spansCache,
} }
impl.Reset(root) impl.Reset(root)
return impl return impl
@ -63,6 +64,9 @@ func (impl *canonicalChainBuilderImpl) Reset(root *types.Header) {
headerHash: root.Hash(), headerHash: root.Hash(),
} }
impl.tip = impl.root impl.tip = impl.root
if impl.spansCache != nil {
impl.spansCache.Prune(root.Number.Uint64())
}
} }
// depth-first search // depth-first search
@ -138,8 +142,11 @@ func (impl *canonicalChainBuilderImpl) Prune(newRootNum uint64) error {
for newRoot.header.Number.Uint64() > newRootNum { for newRoot.header.Number.Uint64() > newRootNum {
newRoot = newRoot.parent newRoot = newRoot.parent
} }
impl.root = newRoot impl.root = newRoot
if impl.spansCache != nil {
impl.spansCache.Prune(newRootNum)
}
return nil return nil
} }

View File

@ -34,7 +34,7 @@ func makeRoot() *types.Header {
func makeCCB(root *types.Header) CanonicalChainBuilder { func makeCCB(root *types.Header) CanonicalChainBuilder {
difficultyCalc := testDifficultyCalculator{} difficultyCalc := testDifficultyCalculator{}
builder := NewCanonicalChainBuilder(root, &difficultyCalc, nil) builder := NewCanonicalChainBuilder(root, &difficultyCalc, nil, nil)
return builder return builder
} }

View File

@ -1,12 +1,12 @@
package sync package sync
import ( import (
"fmt"
lru "github.com/hashicorp/golang-lru/arc/v2" lru "github.com/hashicorp/golang-lru/arc/v2"
"github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/eth/stagedsync"
heimdallspan "github.com/ledgerwatch/erigon/polygon/heimdall/span"
libcommon "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/polygon/bor" "github.com/ledgerwatch/erigon/polygon/bor"
@ -16,20 +16,19 @@ import (
type DifficultyCalculator interface { type DifficultyCalculator interface {
HeaderDifficulty(header *types.Header) (uint64, error) HeaderDifficulty(header *types.Header) (uint64, error)
SetSpan(span *heimdallspan.HeimdallSpan)
} }
type difficultyCalculatorImpl struct { type difficultyCalculatorImpl struct {
borConfig *borcfg.BorConfig borConfig *borcfg.BorConfig
span *heimdallspan.HeimdallSpan spans *SpansCache
validatorSetFactory func() validatorSetInterface validatorSetFactory func(headerNum uint64) validatorSetInterface
signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address] signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address]
} }
func NewDifficultyCalculator( func NewDifficultyCalculator(
borConfig *borcfg.BorConfig, borConfig *borcfg.BorConfig,
span *heimdallspan.HeimdallSpan, spans *SpansCache,
validatorSetFactory func() validatorSetInterface, validatorSetFactory func(headerNum uint64) validatorSetInterface,
signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address], signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address],
) DifficultyCalculator { ) DifficultyCalculator {
if signaturesCache == nil { if signaturesCache == nil {
@ -42,7 +41,7 @@ func NewDifficultyCalculator(
impl := difficultyCalculatorImpl{ impl := difficultyCalculatorImpl{
borConfig: borConfig, borConfig: borConfig,
span: span, spans: spans,
validatorSetFactory: validatorSetFactory, validatorSetFactory: validatorSetFactory,
signaturesCache: signaturesCache, signaturesCache: signaturesCache,
} }
@ -54,12 +53,12 @@ func NewDifficultyCalculator(
return &impl return &impl
} }
func (impl *difficultyCalculatorImpl) makeValidatorSet() validatorSetInterface { func (impl *difficultyCalculatorImpl) makeValidatorSet(headerNum uint64) validatorSetInterface {
return valset.NewValidatorSet(impl.span.ValidatorSet.Validators) span := impl.spans.SpanAt(headerNum)
} if span == nil {
return nil
func (impl *difficultyCalculatorImpl) SetSpan(span *heimdallspan.HeimdallSpan) { }
impl.span = span return valset.NewValidatorSet(span.ValidatorSet.Validators)
} }
func (impl *difficultyCalculatorImpl) HeaderDifficulty(header *types.Header) (uint64, error) { func (impl *difficultyCalculatorImpl) HeaderDifficulty(header *types.Header) (uint64, error) {
@ -71,7 +70,10 @@ func (impl *difficultyCalculatorImpl) HeaderDifficulty(header *types.Header) (ui
} }
func (impl *difficultyCalculatorImpl) signerDifficulty(signer libcommon.Address, headerNum uint64) (uint64, error) { func (impl *difficultyCalculatorImpl) signerDifficulty(signer libcommon.Address, headerNum uint64) (uint64, error) {
validatorSet := impl.validatorSetFactory() validatorSet := impl.validatorSetFactory(headerNum)
if validatorSet == nil {
return 0, fmt.Errorf("difficultyCalculatorImpl.signerDifficulty: no span at %d", headerNum)
}
sprintNum := impl.borConfig.CalculateSprintNumber(headerNum) sprintNum := impl.borConfig.CalculateSprintNumber(headerNum)
if sprintNum > 0 { if sprintNum > 0 {

View File

@ -7,8 +7,6 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
heimdallspan "github.com/ledgerwatch/erigon/polygon/heimdall/span"
libcommon "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/core/types" "github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/polygon/bor/borcfg" "github.com/ledgerwatch/erigon/polygon/bor/borcfg"
@ -57,7 +55,7 @@ func TestSignerDifficulty(t *testing.T) {
libcommon.HexToAddress("01"), libcommon.HexToAddress("01"),
libcommon.HexToAddress("02"), libcommon.HexToAddress("02"),
} }
validatorSetFactory := func() validatorSetInterface { return &testValidatorSetInterface{signers: signers} } validatorSetFactory := func(uint64) validatorSetInterface { return &testValidatorSetInterface{signers: signers} }
calc := NewDifficultyCalculator(&borConfig, nil, validatorSetFactory, nil).(*difficultyCalculatorImpl) calc := NewDifficultyCalculator(&borConfig, nil, validatorSetFactory, nil).(*difficultyCalculatorImpl)
var d uint64 var d uint64
@ -123,9 +121,18 @@ func TestSignerDifficulty(t *testing.T) {
func TestHeaderDifficultyNoSignature(t *testing.T) { func TestHeaderDifficultyNoSignature(t *testing.T) {
borConfig := borcfg.BorConfig{} borConfig := borcfg.BorConfig{}
span := heimdallspan.HeimdallSpan{} spans := NewSpansCache()
calc := NewDifficultyCalculator(&borConfig, &span, nil, nil) calc := NewDifficultyCalculator(&borConfig, spans, nil, nil)
_, err := calc.HeaderDifficulty(new(types.Header)) _, err := calc.HeaderDifficulty(new(types.Header))
require.ErrorContains(t, err, "signature suffix missing") require.ErrorContains(t, err, "signature suffix missing")
} }
func TestSignerDifficultyNoSpan(t *testing.T) {
borConfig := borcfg.BorConfig{}
spans := NewSpansCache()
calc := NewDifficultyCalculator(&borConfig, spans, nil, nil).(*difficultyCalculatorImpl)
_, err := calc.signerDifficulty(libcommon.HexToAddress("00"), 0)
require.ErrorContains(t, err, "no span")
}

View File

@ -1,6 +1,7 @@
package sync package sync
import ( import (
"fmt"
"time" "time"
lru "github.com/hashicorp/golang-lru/arc/v2" lru "github.com/hashicorp/golang-lru/arc/v2"
@ -11,25 +12,23 @@ import (
"github.com/ledgerwatch/erigon/polygon/bor" "github.com/ledgerwatch/erigon/polygon/bor"
"github.com/ledgerwatch/erigon/polygon/bor/borcfg" "github.com/ledgerwatch/erigon/polygon/bor/borcfg"
"github.com/ledgerwatch/erigon/polygon/bor/valset" "github.com/ledgerwatch/erigon/polygon/bor/valset"
heimdallspan "github.com/ledgerwatch/erigon/polygon/heimdall/span"
) )
type HeaderTimeValidator interface { type HeaderTimeValidator interface {
ValidateHeaderTime(header *types.Header, now time.Time, parent *types.Header) error ValidateHeaderTime(header *types.Header, now time.Time, parent *types.Header) error
SetSpan(span *heimdallspan.HeimdallSpan)
} }
type headerTimeValidatorImpl struct { type headerTimeValidatorImpl struct {
borConfig *borcfg.BorConfig borConfig *borcfg.BorConfig
span *heimdallspan.HeimdallSpan spans *SpansCache
validatorSetFactory func() validatorSetInterface validatorSetFactory func(headerNum uint64) validatorSetInterface
signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address] signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address]
} }
func NewHeaderTimeValidator( func NewHeaderTimeValidator(
borConfig *borcfg.BorConfig, borConfig *borcfg.BorConfig,
span *heimdallspan.HeimdallSpan, spans *SpansCache,
validatorSetFactory func() validatorSetInterface, validatorSetFactory func(headerNum uint64) validatorSetInterface,
signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address], signaturesCache *lru.ARCCache[libcommon.Hash, libcommon.Address],
) HeaderTimeValidator { ) HeaderTimeValidator {
if signaturesCache == nil { if signaturesCache == nil {
@ -42,7 +41,7 @@ func NewHeaderTimeValidator(
impl := headerTimeValidatorImpl{ impl := headerTimeValidatorImpl{
borConfig: borConfig, borConfig: borConfig,
span: span, spans: spans,
validatorSetFactory: validatorSetFactory, validatorSetFactory: validatorSetFactory,
signaturesCache: signaturesCache, signaturesCache: signaturesCache,
} }
@ -54,18 +53,22 @@ func NewHeaderTimeValidator(
return &impl return &impl
} }
func (impl *headerTimeValidatorImpl) makeValidatorSet() validatorSetInterface { func (impl *headerTimeValidatorImpl) makeValidatorSet(headerNum uint64) validatorSetInterface {
return valset.NewValidatorSet(impl.span.ValidatorSet.Validators) span := impl.spans.SpanAt(headerNum)
} if span == nil {
return nil
func (impl *headerTimeValidatorImpl) SetSpan(span *heimdallspan.HeimdallSpan) { }
impl.span = span return valset.NewValidatorSet(span.ValidatorSet.Validators)
} }
func (impl *headerTimeValidatorImpl) ValidateHeaderTime(header *types.Header, now time.Time, parent *types.Header) error { func (impl *headerTimeValidatorImpl) ValidateHeaderTime(header *types.Header, now time.Time, parent *types.Header) error {
validatorSet := impl.validatorSetFactory() headerNum := header.Number.Uint64()
validatorSet := impl.validatorSetFactory(headerNum)
if validatorSet == nil {
return fmt.Errorf("headerTimeValidatorImpl.ValidateHeaderTime: no span at %d", headerNum)
}
sprintNum := impl.borConfig.CalculateSprintNumber(header.Number.Uint64()) sprintNum := impl.borConfig.CalculateSprintNumber(headerNum)
if sprintNum > 0 { if sprintNum > 0 {
validatorSet.IncrementProposerPriority(int(sprintNum)) validatorSet.IncrementProposerPriority(int(sprintNum))
} }

View File

@ -0,0 +1,36 @@
package sync
import heimdallspan "github.com/ledgerwatch/erigon/polygon/heimdall/span"
type SpansCache struct {
spans map[uint64]*heimdallspan.HeimdallSpan
}
func NewSpansCache() *SpansCache {
return &SpansCache{
spans: make(map[uint64]*heimdallspan.HeimdallSpan),
}
}
func (cache *SpansCache) Add(span *heimdallspan.HeimdallSpan) {
cache.spans[span.StartBlock] = span
}
// SpanAt finds a span that contains blockNum.
func (cache *SpansCache) SpanAt(blockNum uint64) *heimdallspan.HeimdallSpan {
for _, span := range cache.spans {
if (span.StartBlock <= blockNum) && (blockNum <= span.EndBlock) {
return span
}
}
return nil
}
// Prune removes spans that ended before blockNum.
func (cache *SpansCache) Prune(blockNum uint64) {
for key, span := range cache.spans {
if span.EndBlock < blockNum {
delete(cache.spans, key)
}
}
}