mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
beacon/light: comments and various minor changes
This commit is contained in:
parent
8ff38966c4
commit
6eb5767b70
4 changed files with 161 additions and 124 deletions
|
|
@ -49,7 +49,7 @@ func (c *CheckpointData) Validate() error {
|
||||||
func (c *CheckpointData) InitChain(chain *CommitteeChain) {
|
func (c *CheckpointData) InitChain(chain *CommitteeChain) {
|
||||||
must := func(err error) {
|
must := func(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Error initializing committee chain with checkpoint", "error", err)
|
log.Error("Error initializing committee chain with checkpoint", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
period := c.Header.SyncPeriod()
|
period := c.Header.SyncPeriod()
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package light
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -27,13 +28,13 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotInitialized = errors.New("sync committee chain not initialized")
|
|
||||||
ErrNeedCommittee = errors.New("sync committee required")
|
ErrNeedCommittee = errors.New("sync committee required")
|
||||||
ErrInvalidUpdate = errors.New("invalid committee update")
|
ErrInvalidUpdate = errors.New("invalid committee update")
|
||||||
ErrInvalidPeriod = errors.New("invalid update period")
|
ErrInvalidPeriod = errors.New("invalid update period")
|
||||||
|
|
@ -41,12 +42,6 @@ var (
|
||||||
ErrCannotReorg = errors.New("can not reorg committee chain")
|
ErrCannotReorg = errors.New("can not reorg committee chain")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
bestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
|
|
||||||
fixedRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
|
||||||
syncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
|
||||||
)
|
|
||||||
|
|
||||||
// CommitteeChain is a passive data structure that can validate, hold and update
|
// CommitteeChain is a passive data structure that can validate, hold and update
|
||||||
// a chain of beacon light sync committees and updates. It requires at least one
|
// a chain of beacon light sync committees and updates. It requires at least one
|
||||||
// externally set fixed committee root at the beginning of the chain which can
|
// externally set fixed committee root at the beginning of the chain which can
|
||||||
|
|
@ -67,15 +62,16 @@ var (
|
||||||
// Once synced to the current sync period, CommitteeChain can also validate
|
// Once synced to the current sync period, CommitteeChain can also validate
|
||||||
// signed beacon headers.
|
// signed beacon headers.
|
||||||
type CommitteeChain struct {
|
type CommitteeChain struct {
|
||||||
lock sync.RWMutex
|
chainmu sync.RWMutex // locks database, cache and canonicalStore access
|
||||||
db ethdb.KeyValueStore
|
db ethdb.KeyValueStore
|
||||||
sigVerifier committeeSigVerifier
|
|
||||||
clock mclock.Clock
|
|
||||||
updates *canonicalStore[*types.LightClientUpdate]
|
updates *canonicalStore[*types.LightClientUpdate]
|
||||||
committees *canonicalStore[*types.SerializedSyncCommittee]
|
committees *canonicalStore[*types.SerializedSyncCommittee]
|
||||||
fixedRoots *canonicalStore[common.Hash]
|
fixedRoots *canonicalStore[common.Hash]
|
||||||
syncCommitteeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
syncCommitteeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees
|
||||||
unixNano func() int64
|
|
||||||
|
clock mclock.Clock // monotonic clock (simulated clock in tests)
|
||||||
|
unixNano func() int64 // system clock (simulated clock in tests)
|
||||||
|
sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests)
|
||||||
|
|
||||||
config *types.ChainConfig
|
config *types.ChainConfig
|
||||||
signerThreshold int
|
signerThreshold int
|
||||||
|
|
@ -86,17 +82,15 @@ type CommitteeChain struct {
|
||||||
// NewCommitteeChain creates a new CommitteeChain.
|
// NewCommitteeChain creates a new CommitteeChain.
|
||||||
func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain {
|
||||||
s := &CommitteeChain{
|
s := &CommitteeChain{
|
||||||
fixedRoots: newCanonicalStore[common.Hash](db, fixedRootKey, func(root common.Hash) ([]byte, error) {
|
fixedRoots: newCanonicalStore[common.Hash](db, rawdb.FixedRootKey, func(root common.Hash) ([]byte, error) {
|
||||||
return root[:], nil
|
return root[:], nil
|
||||||
}, func(enc []byte) (root common.Hash, err error) {
|
}, func(enc []byte) (root common.Hash, err error) {
|
||||||
if len(enc) == len(root) {
|
if len(enc) != common.HashLength {
|
||||||
copy(root[:], enc)
|
return common.Hash{}, errors.New("incorrect length for committee root entry in the database")
|
||||||
} else {
|
|
||||||
err = errors.New("Incorrect length for committee root entry in the database")
|
|
||||||
}
|
}
|
||||||
return
|
return common.BytesToHash(enc), nil
|
||||||
}),
|
}),
|
||||||
committees: newCanonicalStore[*types.SerializedSyncCommittee](db, syncCommitteeKey, func(committee *types.SerializedSyncCommittee) ([]byte, error) {
|
committees: newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey, func(committee *types.SerializedSyncCommittee) ([]byte, error) {
|
||||||
return committee[:], nil
|
return committee[:], nil
|
||||||
}, func(enc []byte) (*types.SerializedSyncCommittee, error) {
|
}, func(enc []byte) (*types.SerializedSyncCommittee, error) {
|
||||||
if len(enc) == types.SerializedSyncCommitteeSize {
|
if len(enc) == types.SerializedSyncCommitteeSize {
|
||||||
|
|
@ -104,9 +98,9 @@ func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signer
|
||||||
copy(committee[:], enc)
|
copy(committee[:], enc)
|
||||||
return committee, nil
|
return committee, nil
|
||||||
}
|
}
|
||||||
return nil, errors.New("Incorrect length for serialized committee entry in the database")
|
return nil, errors.New("incorrect length for serialized committee entry in the database")
|
||||||
}),
|
}),
|
||||||
updates: newCanonicalStore[*types.LightClientUpdate](db, bestUpdateKey, func(update *types.LightClientUpdate) ([]byte, error) {
|
updates: newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey, func(update *types.LightClientUpdate) ([]byte, error) {
|
||||||
return rlp.EncodeToBytes(update)
|
return rlp.EncodeToBytes(update)
|
||||||
}, func(enc []byte) (*types.LightClientUpdate, error) {
|
}, func(enc []byte) (*types.LightClientUpdate, error) {
|
||||||
update := new(types.LightClientUpdate)
|
update := new(types.LightClientUpdate)
|
||||||
|
|
@ -130,38 +124,38 @@ func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signer
|
||||||
}
|
}
|
||||||
|
|
||||||
// check validity constraints
|
// check validity constraints
|
||||||
if !s.updates.IsEmpty() {
|
if !s.updates.periods.IsEmpty() {
|
||||||
if s.fixedRoots.IsEmpty() || s.updates.First < s.fixedRoots.First ||
|
if s.fixedRoots.periods.IsEmpty() || s.updates.periods.First < s.fixedRoots.periods.First ||
|
||||||
s.updates.First >= s.fixedRoots.AfterLast {
|
s.updates.periods.First >= s.fixedRoots.periods.AfterLast {
|
||||||
log.Crit("Inconsistent database error: first update is not in the fixed roots range")
|
log.Error("Inconsistent database error: first update is not in the fixed roots range")
|
||||||
}
|
}
|
||||||
if s.committees.First > s.updates.First || s.committees.AfterLast <= s.updates.AfterLast {
|
if s.committees.periods.First > s.updates.periods.First || s.committees.periods.AfterLast <= s.updates.periods.AfterLast {
|
||||||
log.Crit("Inconsistent database error: missing committees in update range")
|
log.Error("Inconsistent database error: missing committees in update range")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !s.committees.IsEmpty() {
|
if !s.committees.periods.IsEmpty() {
|
||||||
if s.fixedRoots.IsEmpty() || s.committees.First < s.fixedRoots.First ||
|
if s.fixedRoots.periods.IsEmpty() || s.committees.periods.First < s.fixedRoots.periods.First ||
|
||||||
s.committees.First >= s.fixedRoots.AfterLast {
|
s.committees.periods.First >= s.fixedRoots.periods.AfterLast {
|
||||||
log.Crit("Inconsistent database error: first committee is not in the fixed roots range")
|
log.Error("Inconsistent database error: first committee is not in the fixed roots range")
|
||||||
}
|
}
|
||||||
if s.committees.AfterLast > s.fixedRoots.AfterLast && s.committees.AfterLast > s.updates.AfterLast+1 {
|
if s.committees.periods.AfterLast > s.fixedRoots.periods.AfterLast && s.committees.periods.AfterLast > s.updates.periods.AfterLast+1 {
|
||||||
log.Crit("Inconsistent database error: last committee is neither in the fixed roots range nor proven by updates")
|
log.Error("Inconsistent database error: last committee is neither in the fixed roots range nor proven by updates")
|
||||||
}
|
}
|
||||||
log.Trace("Sync committee chain loaded", "first period", s.committees.First, "last period", s.committees.AfterLast-1)
|
log.Trace("Sync committee chain loaded", "first period", s.committees.periods.First, "last period", s.committees.periods.AfterLast-1)
|
||||||
}
|
}
|
||||||
// roll back invalid updates (might be necessary if forks have been changed since last time)
|
// roll back invalid updates (might be necessary if forks have been changed since last time)
|
||||||
var batch ethdb.Batch
|
var batch ethdb.Batch
|
||||||
for !s.updates.IsEmpty() {
|
for !s.updates.periods.IsEmpty() {
|
||||||
if update := s.updates.get(s.updates.AfterLast - 1); update == nil || s.verifyUpdate(update) {
|
if update, ok := s.updates.get(s.updates.periods.AfterLast - 1); !ok || s.verifyUpdate(update) {
|
||||||
if update == nil {
|
if update == nil {
|
||||||
log.Crit("Sync committee update missing", "period", s.updates.AfterLast-1)
|
log.Error("Sync committee update missing", "period", s.updates.periods.AfterLast-1)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if batch == nil {
|
if batch == nil {
|
||||||
batch = s.db.NewBatch()
|
batch = s.db.NewBatch()
|
||||||
}
|
}
|
||||||
s.rollback(batch, s.updates.AfterLast)
|
s.rollback(batch, s.updates.periods.AfterLast)
|
||||||
}
|
}
|
||||||
if batch != nil {
|
if batch != nil {
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
|
|
@ -173,8 +167,8 @@ func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signer
|
||||||
|
|
||||||
// Reset resets the committee chain.
|
// Reset resets the committee chain.
|
||||||
func (s *CommitteeChain) Reset() {
|
func (s *CommitteeChain) Reset() {
|
||||||
s.lock.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
batch := s.db.NewBatch()
|
batch := s.db.NewBatch()
|
||||||
s.rollback(batch, 0)
|
s.rollback(batch, 0)
|
||||||
|
|
@ -187,24 +181,40 @@ func (s *CommitteeChain) Reset() {
|
||||||
// Note that the period where the first committee is added has to have a fixed
|
// Note that the period where the first committee is added has to have a fixed
|
||||||
// root which can either come from a CheckpointData or a trusted source.
|
// root which can either come from a CheckpointData or a trusted source.
|
||||||
func (s *CommitteeChain) AddFixedRoot(period uint64, root common.Hash) error {
|
func (s *CommitteeChain) AddFixedRoot(period uint64, root common.Hash) error {
|
||||||
s.lock.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
batch := s.db.NewBatch()
|
batch := s.db.NewBatch()
|
||||||
oldRoot := s.getCommitteeRoot(period)
|
oldRoot := s.getCommitteeRoot(period)
|
||||||
if !s.fixedRoots.CanExpand(period) {
|
if !s.fixedRoots.periods.CanExpand(period) {
|
||||||
|
// Note: the fixed committee root range should always be continuous and
|
||||||
|
// therefore the expected syncing method is to forward sync and optionally
|
||||||
|
// backward sync periods one by one, starting from a checkpoint. The only
|
||||||
|
// case when a root that is not adjacent to the already fixed ones can be
|
||||||
|
// fixed is when the same root has already been proven by an update chain.
|
||||||
|
// In this case the all roots in between can and should be fixed.
|
||||||
|
// This scenario makes sense when a new trusted checkpoint is added to an
|
||||||
|
// existing chain, ensuring that it will not be rolled back (might be
|
||||||
|
// important in case of low signer participation rate).
|
||||||
if root != oldRoot {
|
if root != oldRoot {
|
||||||
return ErrInvalidPeriod
|
return ErrInvalidPeriod
|
||||||
}
|
}
|
||||||
for p := s.fixedRoots.AfterLast; p <= period; p++ {
|
// if the old root exists and matches the new one then it is guaranteed
|
||||||
s.fixedRoots.add(batch, p, s.getCommitteeRoot(p))
|
// that the given period is after the existing fixed range and the roots
|
||||||
|
// in between can also be fixed.
|
||||||
|
for p := s.fixedRoots.periods.AfterLast; p < period; p++ {
|
||||||
|
if err := s.fixedRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if oldRoot != (common.Hash{}) && (oldRoot != root) {
|
if oldRoot != (common.Hash{}) && (oldRoot != root) {
|
||||||
// existing old root was different, we have to reorg the chain
|
// existing old root was different, we have to reorg the chain
|
||||||
s.rollback(batch, period)
|
s.rollback(batch, period)
|
||||||
}
|
}
|
||||||
s.fixedRoots.add(batch, period, root)
|
if err := s.fixedRoots.add(batch, period, root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Error("Error writing batch into chain database", "error", err)
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -216,21 +226,29 @@ func (s *CommitteeChain) AddFixedRoot(period uint64, root common.Hash) error {
|
||||||
// It also maintains chain consistency, meaning that it also deletes updates and
|
// It also maintains chain consistency, meaning that it also deletes updates and
|
||||||
// committees if they are no longer supported by a valid update chain.
|
// committees if they are no longer supported by a valid update chain.
|
||||||
func (s *CommitteeChain) DeleteFixedRootsFrom(period uint64) error {
|
func (s *CommitteeChain) DeleteFixedRootsFrom(period uint64) error {
|
||||||
s.lock.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
if period >= s.fixedRoots.AfterLast {
|
if period >= s.fixedRoots.periods.AfterLast {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
batch := s.db.NewBatch()
|
batch := s.db.NewBatch()
|
||||||
s.fixedRoots.deleteFrom(batch, period)
|
s.fixedRoots.deleteFrom(batch, period)
|
||||||
if s.updates.IsEmpty() || period <= s.updates.First {
|
if s.updates.periods.IsEmpty() || period <= s.updates.periods.First {
|
||||||
|
// Note: the first period of the update chain should always be fixed so if
|
||||||
|
// the fixed root at the first update is removed then the entire update chain
|
||||||
|
// and the proven committees have to be removed. Earlier committees in the
|
||||||
|
// remaining fixed root range can stay.
|
||||||
s.updates.deleteFrom(batch, period)
|
s.updates.deleteFrom(batch, period)
|
||||||
s.deleteCommitteesFrom(batch, period)
|
s.deleteCommitteesFrom(batch, period)
|
||||||
} else {
|
} else {
|
||||||
fromPeriod := s.updates.AfterLast + 1
|
// The update chain stays intact, some previously fixed committee roots might
|
||||||
|
// get unfixed but are still proven by the update chain. If there were
|
||||||
|
// committees present after the range proven by updates, those should be
|
||||||
|
// removed if the belonging fixed roots are also removed.
|
||||||
|
fromPeriod := s.updates.periods.AfterLast + 1 // not proven by updates
|
||||||
if period > fromPeriod {
|
if period > fromPeriod {
|
||||||
fromPeriod = period
|
fromPeriod = period // also not justified by fixed roots
|
||||||
}
|
}
|
||||||
s.deleteCommitteesFrom(batch, fromPeriod)
|
s.deleteCommitteesFrom(batch, fromPeriod)
|
||||||
}
|
}
|
||||||
|
|
@ -250,16 +268,18 @@ func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCommittee returns the committee at the given period.
|
// GetCommittee returns the committee at the given period.
|
||||||
|
// Note: GetCommittee can be called either with locked or unlocked chain mutex.
|
||||||
func (s *CommitteeChain) GetCommittee(period uint64) *types.SerializedSyncCommittee {
|
func (s *CommitteeChain) GetCommittee(period uint64) *types.SerializedSyncCommittee {
|
||||||
return s.committees.get(period)
|
committee, _ := s.committees.get(period)
|
||||||
|
return committee
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddCommittee adds a committee at the given period if possible.
|
// AddCommittee adds a committee at the given period if possible.
|
||||||
func (s *CommitteeChain) AddCommittee(period uint64, committee *types.SerializedSyncCommittee) error {
|
func (s *CommitteeChain) AddCommittee(period uint64, committee *types.SerializedSyncCommittee) error {
|
||||||
s.lock.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
if !s.committees.CanExpand(period) {
|
if !s.committees.periods.CanExpand(period) {
|
||||||
return ErrInvalidPeriod
|
return ErrInvalidPeriod
|
||||||
}
|
}
|
||||||
root := s.getCommitteeRoot(period)
|
root := s.getCommitteeRoot(period)
|
||||||
|
|
@ -269,25 +289,29 @@ func (s *CommitteeChain) AddCommittee(period uint64, committee *types.Serialized
|
||||||
if root != committee.Root() {
|
if root != committee.Root() {
|
||||||
return ErrWrongCommitteeRoot
|
return ErrWrongCommitteeRoot
|
||||||
}
|
}
|
||||||
if !s.committees.Includes(period) {
|
if !s.committees.periods.Includes(period) {
|
||||||
s.committees.add(nil, period, committee)
|
if err := s.committees.add(s.db, period, committee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.syncCommitteeCache.Remove(period)
|
s.syncCommitteeCache.Remove(period)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUpdate returns the update at the given period.
|
// GetUpdate returns the update at the given period.
|
||||||
|
// Note: GetUpdate can be called either with locked or unlocked chain mutex.
|
||||||
func (s *CommitteeChain) GetUpdate(period uint64) *types.LightClientUpdate {
|
func (s *CommitteeChain) GetUpdate(period uint64) *types.LightClientUpdate {
|
||||||
return s.updates.get(period)
|
update, _ := s.updates.get(period)
|
||||||
|
return update
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertUpdate adds a new update if possible.
|
// InsertUpdate adds a new update if possible.
|
||||||
func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error {
|
||||||
s.lock.Lock()
|
s.chainmu.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.chainmu.Unlock()
|
||||||
|
|
||||||
period := update.AttestedHeader.Header.SyncPeriod()
|
period := update.AttestedHeader.Header.SyncPeriod()
|
||||||
if !s.updates.CanExpand(period) || !s.committees.Includes(period) {
|
if !s.updates.periods.CanExpand(period) || !s.committees.periods.Includes(period) {
|
||||||
return ErrInvalidPeriod
|
return ErrInvalidPeriod
|
||||||
}
|
}
|
||||||
if s.minimumUpdateScore.BetterThan(update.Score()) {
|
if s.minimumUpdateScore.BetterThan(update.Score()) {
|
||||||
|
|
@ -295,20 +319,20 @@ func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommi
|
||||||
}
|
}
|
||||||
oldRoot := s.getCommitteeRoot(period + 1)
|
oldRoot := s.getCommitteeRoot(period + 1)
|
||||||
reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot
|
reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot
|
||||||
if oldUpdate := s.updates.get(period); oldUpdate != nil && !update.Score().BetterThan(oldUpdate.Score()) {
|
if oldUpdate, ok := s.updates.get(period); ok && !update.Score().BetterThan(oldUpdate.Score()) {
|
||||||
// a better or equal update already exists; no changes, only fail if new one tried to reorg
|
// a better or equal update already exists; no changes, only fail if new one tried to reorg
|
||||||
if reorg {
|
if reorg {
|
||||||
return ErrCannotReorg
|
return ErrCannotReorg
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if s.fixedRoots.Includes(period+1) && reorg {
|
if s.fixedRoots.periods.Includes(period+1) && reorg {
|
||||||
return ErrCannotReorg
|
return ErrCannotReorg
|
||||||
}
|
}
|
||||||
if !s.verifyUpdate(update) {
|
if !s.verifyUpdate(update) {
|
||||||
return ErrInvalidUpdate
|
return ErrInvalidUpdate
|
||||||
}
|
}
|
||||||
addCommittee := !s.committees.Includes(period+1) || reorg
|
addCommittee := !s.committees.periods.Includes(period+1) || reorg
|
||||||
if addCommittee {
|
if addCommittee {
|
||||||
if nextCommittee == nil {
|
if nextCommittee == nil {
|
||||||
return ErrNeedCommittee
|
return ErrNeedCommittee
|
||||||
|
|
@ -322,10 +346,14 @@ func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommi
|
||||||
s.rollback(batch, period+1)
|
s.rollback(batch, period+1)
|
||||||
}
|
}
|
||||||
if addCommittee {
|
if addCommittee {
|
||||||
s.committees.add(batch, period+1, nextCommittee)
|
if err := s.committees.add(batch, period+1, nextCommittee); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
s.syncCommitteeCache.Remove(period + 1)
|
s.syncCommitteeCache.Remove(period + 1)
|
||||||
}
|
}
|
||||||
s.updates.add(batch, period, update)
|
if err := s.updates.add(batch, period, update); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := batch.Write(); err != nil {
|
if err := batch.Write(); err != nil {
|
||||||
log.Error("Error writing batch into chain database", "error", err)
|
log.Error("Error writing batch into chain database", "error", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -337,16 +365,16 @@ func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommi
|
||||||
// NextSyncPeriod returns the next period where an update can be added and also
|
// NextSyncPeriod returns the next period where an update can be added and also
|
||||||
// whether the chain is initialized at all.
|
// whether the chain is initialized at all.
|
||||||
func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) {
|
func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) {
|
||||||
s.lock.RLock()
|
s.chainmu.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
if s.committees.IsEmpty() {
|
if s.committees.periods.IsEmpty() {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
if !s.updates.IsEmpty() {
|
if !s.updates.periods.IsEmpty() {
|
||||||
return s.updates.AfterLast, true
|
return s.updates.periods.AfterLast, true
|
||||||
}
|
}
|
||||||
return s.committees.AfterLast - 1, true
|
return s.committees.periods.AfterLast - 1, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// rollback removes all committees and fixed roots from the given period and updates
|
// rollback removes all committees and fixed roots from the given period and updates
|
||||||
|
|
@ -364,10 +392,10 @@ func (s *CommitteeChain) rollback(batch ethdb.Batch, period uint64) {
|
||||||
// proven by a previous update or both. It returns an empty hash if the committee
|
// proven by a previous update or both. It returns an empty hash if the committee
|
||||||
// root is unknown.
|
// root is unknown.
|
||||||
func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash {
|
func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash {
|
||||||
if root := s.fixedRoots.get(period); root != (common.Hash{}) || period == 0 {
|
if root, ok := s.fixedRoots.get(period); ok || period == 0 {
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
if update := s.updates.get(period - 1); update != nil {
|
if update, ok := s.updates.get(period - 1); ok {
|
||||||
return update.NextSyncCommitteeRoot
|
return update.NextSyncCommitteeRoot
|
||||||
}
|
}
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
|
|
@ -378,7 +406,7 @@ func (s *CommitteeChain) getSyncCommittee(period uint64) syncCommittee {
|
||||||
if c, ok := s.syncCommitteeCache.Get(period); ok {
|
if c, ok := s.syncCommitteeCache.Get(period); ok {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
if sc := s.committees.get(period); sc != nil {
|
if sc, ok := s.committees.get(period); ok {
|
||||||
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
c, err := s.sigVerifier.deserializeSyncCommittee(sc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Sync committee deserialization error", "error", err)
|
log.Error("Sync committee deserialization error", "error", err)
|
||||||
|
|
@ -399,17 +427,20 @@ func (s *CommitteeChain) getSyncCommittee(period uint64) syncCommittee {
|
||||||
// of the given slot, according to the local system clock). If enforceTime is
|
// of the given slot, according to the local system clock). If enforceTime is
|
||||||
// true then negative age (future) headers are rejected.
|
// true then negative age (future) headers are rejected.
|
||||||
func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration) {
|
func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration) {
|
||||||
s.lock.RLock()
|
s.chainmu.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.chainmu.RUnlock()
|
||||||
|
|
||||||
return s.verifySignedHeader(head)
|
return s.verifySignedHeader(head)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration) {
|
func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration) {
|
||||||
var (
|
var age time.Duration
|
||||||
slotTime = int64(time.Second) * int64(s.config.GenesisTime+head.Header.Slot*12)
|
now := s.unixNano()
|
||||||
age = time.Duration(s.unixNano() - slotTime)
|
if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 {
|
||||||
)
|
age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12))
|
||||||
|
} else {
|
||||||
|
age = time.Duration(math.MinInt64)
|
||||||
|
}
|
||||||
if s.enforceTime && age < 0 {
|
if s.enforceTime && age < 0 {
|
||||||
return false, age
|
return false, age
|
||||||
}
|
}
|
||||||
|
|
@ -441,9 +472,9 @@ func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) bool {
|
||||||
// canonicalStore stores instances of the given type in a database and caches
|
// canonicalStore stores instances of the given type in a database and caches
|
||||||
// them in memory, associated with a continuous range of period numbers.
|
// them in memory, associated with a continuous range of period numbers.
|
||||||
type canonicalStore[T any] struct {
|
type canonicalStore[T any] struct {
|
||||||
Range
|
|
||||||
db ethdb.KeyValueStore
|
db ethdb.KeyValueStore
|
||||||
keyPrefix []byte
|
keyPrefix []byte
|
||||||
|
periods Range
|
||||||
cache *lru.Cache[uint64, T]
|
cache *lru.Cache[uint64, T]
|
||||||
encode func(T) ([]byte, error)
|
encode func(T) ([]byte, error)
|
||||||
decode func([]byte) (T, error)
|
decode func([]byte) (T, error)
|
||||||
|
|
@ -464,23 +495,27 @@ func newCanonicalStore[T any](db ethdb.KeyValueStore, keyPrefix []byte,
|
||||||
kl = len(keyPrefix)
|
kl = len(keyPrefix)
|
||||||
)
|
)
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
|
if len(iter.Key()) != kl+8 {
|
||||||
|
log.Error("Invalid key length in the canonical chain database")
|
||||||
|
continue
|
||||||
|
}
|
||||||
period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8])
|
period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8])
|
||||||
if cs.First == 0 {
|
if cs.periods.First == 0 {
|
||||||
cs.First = period
|
cs.periods.First = period
|
||||||
} else if cs.AfterLast != period {
|
} else if cs.periods.AfterLast != period {
|
||||||
if iter.Next() {
|
if iter.Next() {
|
||||||
log.Error("Gap in the canonical chain database")
|
log.Error("Gap in the canonical chain database")
|
||||||
}
|
}
|
||||||
break // continuity guaranteed
|
break // continuity guaranteed
|
||||||
}
|
}
|
||||||
cs.AfterLast = period + 1
|
cs.periods.AfterLast = period + 1
|
||||||
}
|
}
|
||||||
iter.Release()
|
iter.Release()
|
||||||
return cs
|
return cs
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDbKey returns the database key belonging to the given period.
|
// databaseKey returns the database key belonging to the given period.
|
||||||
func (cs *canonicalStore[T]) getDbKey(period uint64) []byte {
|
func (cs *canonicalStore[T]) databaseKey(period uint64) []byte {
|
||||||
var (
|
var (
|
||||||
kl = len(cs.keyPrefix)
|
kl = len(cs.keyPrefix)
|
||||||
key = make([]byte, kl+8)
|
key = make([]byte, kl+8)
|
||||||
|
|
@ -491,65 +526,62 @@ func (cs *canonicalStore[T]) getDbKey(period uint64) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add adds the given item to the database. It also ensures that the range remains
|
// add adds the given item to the database. It also ensures that the range remains
|
||||||
// continuous. Can be used both in batch mode and as a standalone operation.
|
// continuous. Can be used either with a batch or database backend.
|
||||||
func (cs *canonicalStore[T]) add(batch ethdb.Batch, period uint64, value T) {
|
func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error {
|
||||||
if !cs.CanExpand(period) {
|
if !cs.periods.CanExpand(period) {
|
||||||
log.Error("Cannot expand canonical store", "range.first", cs.First, "range.afterLast", cs.AfterLast, "new period", period)
|
log.Error("Cannot expand canonical store", "range.first", cs.periods.First, "range.afterLast", cs.periods.AfterLast, "new period", period)
|
||||||
return
|
return errors.New("Cannot expand canonical store")
|
||||||
}
|
}
|
||||||
enc, err := cs.encode(value)
|
enc, err := cs.encode(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error encoding canonical store value", "error", err)
|
log.Error("Error encoding canonical store value", "error", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
key := cs.getDbKey(period)
|
if err := backend.Put(cs.databaseKey(period), enc); err != nil {
|
||||||
if batch != nil {
|
|
||||||
err = batch.Put(key, enc)
|
|
||||||
} else {
|
|
||||||
err = cs.db.Put(key, enc)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error writing into canonical store value database", "error", err)
|
log.Error("Error writing into canonical store value database", "error", err)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
cs.cache.Add(period, value)
|
cs.cache.Add(period, value)
|
||||||
cs.Expand(period)
|
cs.periods.Expand(period)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteFrom removes items starting from the given period. Can only be used in
|
// deleteFrom removes items starting from the given period. Should be used with a
|
||||||
// batch mode.
|
// batch backend.
|
||||||
func (cs *canonicalStore[T]) deleteFrom(batch ethdb.Batch, fromPeriod uint64) (deleted Range) {
|
func (cs *canonicalStore[T]) deleteFrom(backend ethdb.KeyValueWriter, fromPeriod uint64) (deleted Range) {
|
||||||
if fromPeriod >= cs.AfterLast {
|
if fromPeriod >= cs.periods.AfterLast {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if fromPeriod < cs.First {
|
if fromPeriod < cs.periods.First {
|
||||||
fromPeriod = cs.First
|
fromPeriod = cs.periods.First
|
||||||
}
|
}
|
||||||
deleted = Range{First: fromPeriod, AfterLast: cs.AfterLast}
|
deleted = Range{First: fromPeriod, AfterLast: cs.periods.AfterLast}
|
||||||
for period := fromPeriod; period < cs.AfterLast; period++ {
|
for period := fromPeriod; period < cs.periods.AfterLast; period++ {
|
||||||
batch.Delete(cs.getDbKey(period))
|
backend.Delete(cs.databaseKey(period))
|
||||||
cs.cache.Remove(period)
|
cs.cache.Remove(period)
|
||||||
}
|
}
|
||||||
if fromPeriod > cs.First {
|
if fromPeriod > cs.periods.First {
|
||||||
cs.AfterLast = fromPeriod
|
cs.periods.AfterLast = fromPeriod
|
||||||
} else {
|
} else {
|
||||||
cs.Range = Range{}
|
cs.periods = Range{}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns the item at the given period or the null value of the given type
|
// get returns the item at the given period or the null value of the given type
|
||||||
// if no item is present.
|
// if no item is present.
|
||||||
func (cs *canonicalStore[T]) get(period uint64) T {
|
// Note: get is thread safe in itself and therefore can be called either with
|
||||||
if value, ok := cs.cache.Get(period); ok {
|
// locked or unlocked chain mutex.
|
||||||
return value
|
func (cs *canonicalStore[T]) get(period uint64) (value T, ok bool) {
|
||||||
|
if value, ok = cs.cache.Get(period); ok {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
var value T
|
if enc, err := cs.db.Get(cs.databaseKey(period)); err == nil {
|
||||||
if enc, err := cs.db.Get(cs.getDbKey(period)); err == nil {
|
|
||||||
if v, err := cs.decode(enc); err == nil {
|
if v, err := cs.decode(enc); err == nil {
|
||||||
value = v
|
value, ok = v, true
|
||||||
} else {
|
} else {
|
||||||
log.Error("Error decoding canonical store value", "error", err)
|
log.Error("Error decoding canonical store value", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,9 @@ func (a Range) Includes(period uint64) bool {
|
||||||
return period >= a.First && period < a.AfterLast
|
return period >= a.First && period < a.AfterLast
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanExpand returns true if the range can be expanded with the given period
|
// CanExpand returns true if the range includes or can be expanded with the given
|
||||||
// (either the range is empty or the new period is right before or after the range).
|
// period (either the range is empty or the given period is inside, right before or
|
||||||
|
// right after the range).
|
||||||
func (a Range) CanExpand(period uint64) bool {
|
func (a Range) CanExpand(period uint64) bool {
|
||||||
return a.IsEmpty() || (period+1 >= a.First && period <= a.AfterLast)
|
return a.IsEmpty() || (period+1 >= a.First && period <= a.AfterLast)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,10 @@ var (
|
||||||
|
|
||||||
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
|
||||||
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
|
||||||
|
|
||||||
|
BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash)
|
||||||
|
FixedRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash
|
||||||
|
SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee
|
||||||
)
|
)
|
||||||
|
|
||||||
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
// LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue