beacon/light: LightChain test chain generator and GetParent optimization

This commit is contained in:
Zsolt Felfoldi 2023-04-07 00:54:15 +02:00
parent e825f3458d
commit 36a7292861
4 changed files with 416 additions and 310 deletions

View file

@ -45,14 +45,20 @@ var (
hashToSlotKey = []byte("hash2slot-") // blockRoot -> RLP(slot) hashToSlotKey = []byte("hash2slot-") // blockRoot -> RLP(slot)
) )
// LightChain stores beacon headers and optionally partial merkle proofs of the
// belonging beacon states. It maintains a canonical header chain indexed by slots.
// The canonical chain head is set externally, the chain tail is updated automatically
// after adding more headers or setting a new head that has no common ancestor to
// the old one. A state range (state head to state tail) where all canonical headers
// are guaranteed to also have a corresponding state proof is also automatically maintained.
type LightChain struct { type LightChain struct {
lock sync.RWMutex lock sync.RWMutex
db ethdb.KeyValueStore db ethdb.KeyValueStore
chainHead, chainTail types.Header chainInit bool // true if chainHead and chainTail are valid
chainInit bool chainHead, chainTail types.Header // canonical block roots are available in this section
stateHead, stateTail types.Header stateInit bool // true if stateHead and stateTail are valid
stateInit bool stateHead, stateTail types.Header // state proofs of canonical headers are available in this section
lastStoredRange chainRangeData lastStoredRange chainRangeData
headerCache *lru.Cache[slotAndHash, types.Header] headerCache *lru.Cache[slotAndHash, types.Header]
@ -79,6 +85,7 @@ type stateProofData struct {
Values merkle.Values Values merkle.Values
} }
// NewLightChain creates a new LightChain and loads canonical chain info from the database.
func NewLightChain(db ethdb.KeyValueStore, stateProofFormat merkle.ProofFormat) *LightChain { func NewLightChain(db ethdb.KeyValueStore, stateProofFormat merkle.ProofFormat) *LightChain {
lc := &LightChain{ lc := &LightChain{
db: db, db: db,
@ -145,111 +152,9 @@ func (lc *LightChain) storeChainRange(batch ethdb.Batch) {
batch.Put(chainRangeKey, rangeEnc) batch.Put(chainRangeKey, rangeEnc)
} }
func getHeaderKey(slot uint64, blockRoot common.Hash) []byte { // SetChainHead sets the canonical chain head and also finds the new tail if it
var ( // does not share a common ancestor with the old head. The state range is also
kl = len(headerKey) // automatically updated so that it applies to the new canonical chain.
key = make([]byte, kl+8+32)
)
copy(key[:kl], headerKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
copy(key[kl+8:], blockRoot[:])
return key
}
func getStateKey(slot uint64, stateRoot common.Hash) []byte {
var (
kl = len(stateKey)
key = make([]byte, kl+8+32)
)
copy(key[:kl], stateKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
copy(key[kl+8:], stateRoot[:])
return key
}
func getCanonicalKey(slot uint64) []byte {
var (
kl = len(canonicalKey)
key = make([]byte, kl+8)
)
copy(key[:kl], canonicalKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
return key
}
func getHashToSlotKey(blockRoot common.Hash) []byte {
var (
kl = len(hashToSlotKey)
key = make([]byte, kl+32)
)
copy(key[:kl], hashToSlotKey)
copy(key[kl:], blockRoot[:])
return key
}
func (lc *LightChain) getCanonicalHash(slot uint64) common.Hash {
if blockRoot, ok := lc.canonicalCache.Get(slot); ok {
return blockRoot
}
var blockRoot common.Hash
if data, err := lc.db.Get(getCanonicalKey(slot)); err == nil && len(data) == len(blockRoot) {
copy(blockRoot[:], data)
}
lc.canonicalCache.Add(slot, blockRoot)
return blockRoot
}
func (lc *LightChain) storeCanonicalHash(batch ethdb.Batch, slot uint64, blockRoot common.Hash) {
if blockRoot == (common.Hash{}) {
lc.deleteCanonicalHash(batch, slot)
return
}
batch.Put(getCanonicalKey(slot), blockRoot[:])
lc.canonicalCache.Add(slot, blockRoot)
}
func (lc *LightChain) deleteCanonicalHash(batch ethdb.Batch, slot uint64) {
batch.Delete(getCanonicalKey(slot))
lc.canonicalCache.Add(slot, common.Hash{})
}
func (lc *LightChain) AddHeader(header types.Header) {
lc.lock.Lock()
defer lc.lock.Unlock()
batch := lc.db.NewBatch()
blockRoot := header.Hash()
headerEnc, err := rlp.EncodeToBytes(&header)
if err != nil {
log.Error("Failed to encode beacon header", "error", err)
return
}
batch.Put(getHeaderKey(header.Slot, blockRoot), headerEnc)
lc.headerCache.Add(slotAndHash{header.Slot, blockRoot}, header)
slotEnc, err := rlp.EncodeToBytes(&header.Slot)
if err != nil {
log.Error("Failed to encode slot number", "error", err)
return
}
batch.Put(getHashToSlotKey(blockRoot), slotEnc)
lc.hashToSlotCache.Add(blockRoot, header.Slot)
if lc.chainInit && blockRoot == lc.chainTail.ParentRoot {
var err error
for err == nil {
lc.storeCanonicalHash(batch, header.Slot, header.Hash())
for slot := header.Slot + 1; slot < lc.chainTail.Slot; slot++ {
lc.deleteCanonicalHash(batch, slot)
}
lc.chainTail = header
header, err = lc.GetParent(header)
}
lc.storeChainRange(batch)
}
if err := batch.Write(); err != nil {
log.Error("Failed to write batch to database", "error", err)
}
}
func (lc *LightChain) SetChainHead(head types.Header) { func (lc *LightChain) SetChainHead(head types.Header) {
lc.lock.Lock() lc.lock.Lock()
defer lc.lock.Unlock() defer lc.lock.Unlock()
@ -302,6 +207,7 @@ func (lc *LightChain) SetChainHead(head types.Header) {
} }
} }
// HeaderRange returns the canonical header chain range.
func (lc *LightChain) HeaderRange() (head, tail types.Header, init bool) { func (lc *LightChain) HeaderRange() (head, tail types.Header, init bool) {
lc.lock.RLock() lc.lock.RLock()
defer lc.lock.RUnlock() defer lc.lock.RUnlock()
@ -309,85 +215,7 @@ func (lc *LightChain) HeaderRange() (head, tail types.Header, init bool) {
return lc.chainHead, lc.chainTail, lc.chainInit return lc.chainHead, lc.chainTail, lc.chainInit
} }
func (lc *LightChain) getHeader(slot uint64, blockRoot common.Hash) (types.Header, error) { // StateProofRange returns the subset of the canonical chain range where all state proofs are available.
if header, ok := lc.headerCache.Get(slotAndHash{slot, blockRoot}); ok {
return header, nil
}
headerEnc, err := lc.db.Get(getHeaderKey(slot, blockRoot))
if err != nil {
return types.Header{}, ErrNotFound
}
var header types.Header
if err := rlp.DecodeBytes(headerEnc, &header); err != nil {
log.Error("Failed to decode beacon header", "error", err)
return types.Header{}, ErrNotFound
}
return header, nil
}
func (lc *LightChain) getSlotByHash(blockRoot common.Hash) (uint64, bool) {
if slot, ok := lc.hashToSlotCache.Get(blockRoot); ok {
return slot, true
}
slotEnc, err := lc.db.Get(getHashToSlotKey(blockRoot))
if err != nil {
return 0, false
}
var slot uint64
if err := rlp.DecodeBytes(slotEnc, &slot); err != nil {
log.Error("Failed to decode slot number", "error", err)
return 0, false
}
return slot, true
}
func (lc *LightChain) HasHeader(blockRoot common.Hash) bool {
_, ok := lc.getSlotByHash(blockRoot)
return ok
}
func (lc *LightChain) GetHeaderByHash(blockRoot common.Hash) (types.Header, error) {
if slot, ok := lc.getSlotByHash(blockRoot); ok {
header, err := lc.getHeader(slot, blockRoot)
if err != nil {
log.Error("LightChain slot -> blockRoot entry found but header is missing", "slot", slot, "blockRoot", blockRoot)
}
return header, err
}
return types.Header{}, ErrNotFound
}
func (lc *LightChain) GetHeaderBySlot(slot uint64) (types.Header, error) {
lc.lock.RLock()
defer lc.lock.RUnlock()
return lc.getHeaderBySlot(slot)
}
func (lc *LightChain) getHeaderBySlot(slot uint64) (types.Header, error) {
if !lc.chainInit || slot < lc.chainTail.Slot || slot > lc.chainHead.Slot {
return types.Header{}, ErrNotFound
}
blockRoot := lc.getCanonicalHash(slot)
if blockRoot == (common.Hash{}) {
return types.Header{}, ErrEmptySlot
}
header, err := lc.getHeader(slot, blockRoot)
if err != nil {
log.Error("LightChain canonical blockRoot entry found but header is missing", "slot", slot, "blockRoot", blockRoot)
}
return header, err
}
func (lc *LightChain) GetParent(header types.Header) (types.Header, error) {
return lc.GetHeaderByHash(header.ParentRoot)
}
func (lc *LightChain) IsCanonical(header types.Header) bool {
return lc.getCanonicalHash(header.Slot) == header.Hash()
}
func (lc *LightChain) StateProofRange() (head, tail types.Header, init bool) { func (lc *LightChain) StateProofRange() (head, tail types.Header, init bool) {
lc.lock.RLock() lc.lock.RLock()
defer lc.lock.RUnlock() defer lc.lock.RUnlock()
@ -395,130 +223,11 @@ func (lc *LightChain) StateProofRange() (head, tail types.Header, init bool) {
return lc.stateHead, lc.stateTail, lc.stateInit return lc.stateHead, lc.stateTail, lc.stateInit
} }
func (lc *LightChain) extendStateHead(batch ethdb.Batch) { // Prune removes either everything or just non-canonical data before the given slot.
for slot := lc.stateHead.Slot + 1; slot <= lc.chainHead.Slot; slot++ { func (lc *LightChain) Prune(beforeSlot uint64, removeCanonical bool) {
if header, err := lc.getHeaderBySlot(slot); err == nil {
if lc.HasStateProof(header) {
lc.stateHead = header
} else {
break
}
}
}
}
func (lc *LightChain) extendStateTail(batch ethdb.Batch) {
if lc.stateTail.Slot == 0 {
return
}
for slot := lc.stateTail.Slot - 1; slot >= lc.chainTail.Slot; slot-- {
if header, err := lc.getHeaderBySlot(slot); err == nil {
if lc.HasStateProof(header) {
lc.stateTail = header
} else {
break
}
}
}
}
func (lc *LightChain) reinitStateChain(batch ethdb.Batch, header types.Header) {
for slot := header.Slot; slot <= lc.chainHead.Slot; slot++ {
if header, err := lc.getHeaderBySlot(slot); err == nil && lc.HasStateProof(header) {
lc.stateInit = true
lc.stateHead = header
lc.stateTail = header
lc.extendStateHead(batch)
return
}
}
}
func (lc *LightChain) HasStateProof(header types.Header) bool {
if _, ok := lc.stateCache.Get(slotAndHash{header.Slot, header.StateRoot}); ok {
return true
}
ok, err := lc.db.Has(getStateKey(header.Slot, header.StateRoot))
return ok && err == nil
}
func (lc *LightChain) GetStateProof(header types.Header) (merkle.MultiProof, error) {
if values, ok := lc.stateCache.Get(slotAndHash{header.Slot, header.StateRoot}); ok {
return merkle.MultiProof{Format: lc.stateProofFormat, Values: values}, nil
}
stateEnc, err := lc.db.Get(getStateKey(header.Slot, header.StateRoot))
if err != nil {
return merkle.MultiProof{}, ErrNotFound
}
var state stateProofData
if err := rlp.DecodeBytes(stateEnc, &state); err != nil {
log.Error("Failed to decode state proof data", "error", err)
return merkle.MultiProof{}, ErrNotFound
}
return merkle.MultiProof{Format: lc.stateProofFormat, Values: state.Values}, nil
}
func (lc *LightChain) StateProofFormat(header types.Header) merkle.ProofFormat {
return lc.stateProofFormat
}
func (lc *LightChain) AddStateProof(header types.Header, proof merkle.MultiProof) error {
lc.lock.Lock() lc.lock.Lock()
defer lc.lock.Unlock() defer lc.lock.Unlock()
if !merkle.IsEqual(proof.Format, lc.StateProofFormat(header)) {
return ErrInvalidProofFormat
}
if proof.RootHash() != header.StateRoot {
return ErrInvalidStateRoot
}
batch := lc.db.NewBatch()
stateEnc, err := rlp.EncodeToBytes(&stateProofData{Values: proof.Values})
if err != nil {
log.Error("Failed to encode state proof data", "error", err)
return err
}
batch.Put(getStateKey(header.Slot, header.StateRoot), stateEnc)
lc.stateCache.Add(slotAndHash{header.Slot, header.StateRoot}, proof.Values)
if !lc.IsCanonical(header) {
return nil
}
if !lc.stateInit {
lc.stateInit = true
lc.stateHead = header
lc.stateTail = header
return nil
}
if header.Slot > lc.stateHead.Slot && header.Slot <= lc.chainHead.Slot {
lc.extendStateHead(batch)
} else if header.Slot < lc.stateTail.Slot && header.Slot >= lc.chainTail.Slot {
lc.extendStateTail(batch)
}
lc.storeChainRange(batch)
if err := batch.Write(); err != nil {
log.Error("Failed to write batch to database", "error", err)
return err
}
return nil
}
func (lc *LightChain) DeleteBefore(beforeSlot uint64) {
lc.lock.Lock()
defer lc.lock.Unlock()
lc.deleteBefore(beforeSlot, true)
}
func (lc *LightChain) DeleteNonCanonical(beforeSlot uint64) {
lc.lock.Lock()
defer lc.lock.Unlock()
lc.deleteBefore(beforeSlot, false)
}
func (lc *LightChain) deleteBefore(beforeSlot uint64, removeCanonical bool) {
if !lc.chainInit { if !lc.chainInit {
return return
} }
@ -610,3 +319,325 @@ func (lc *LightChain) deleteBefore(beforeSlot uint64, removeCanonical bool) {
lc.stateCache.Remove(slotAndHash{slot: slot, hash: stateRoot}) lc.stateCache.Remove(slotAndHash{slot: slot, hash: stateRoot})
} }
} }
func getHeaderKey(slot uint64, blockRoot common.Hash) []byte {
var (
kl = len(headerKey)
key = make([]byte, kl+8+32)
)
copy(key[:kl], headerKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
copy(key[kl+8:], blockRoot[:])
return key
}
func getStateKey(slot uint64, stateRoot common.Hash) []byte {
var (
kl = len(stateKey)
key = make([]byte, kl+8+32)
)
copy(key[:kl], stateKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
copy(key[kl+8:], stateRoot[:])
return key
}
func getCanonicalKey(slot uint64) []byte {
var (
kl = len(canonicalKey)
key = make([]byte, kl+8)
)
copy(key[:kl], canonicalKey)
binary.BigEndian.PutUint64(key[kl:kl+8], slot)
return key
}
func getHashToSlotKey(blockRoot common.Hash) []byte {
var (
kl = len(hashToSlotKey)
key = make([]byte, kl+32)
)
copy(key[:kl], hashToSlotKey)
copy(key[kl:], blockRoot[:])
return key
}
// AddHeader adds the given header and automatically extends the chain tail if possible.
func (lc *LightChain) AddHeader(header types.Header) {
lc.lock.Lock()
defer lc.lock.Unlock()
batch := lc.db.NewBatch()
blockRoot := header.Hash()
headerEnc, err := rlp.EncodeToBytes(&header)
if err != nil {
log.Error("Failed to encode beacon header", "error", err)
return
}
batch.Put(getHeaderKey(header.Slot, blockRoot), headerEnc)
lc.headerCache.Add(slotAndHash{header.Slot, blockRoot}, header)
slotEnc, err := rlp.EncodeToBytes(&header.Slot)
if err != nil {
log.Error("Failed to encode slot number", "error", err)
return
}
batch.Put(getHashToSlotKey(blockRoot), slotEnc)
lc.hashToSlotCache.Add(blockRoot, header.Slot)
if lc.chainInit && blockRoot == lc.chainTail.ParentRoot {
var err error
for err == nil {
lc.storeCanonicalHash(batch, header.Slot, header.Hash())
for slot := header.Slot + 1; slot < lc.chainTail.Slot; slot++ {
lc.deleteCanonicalHash(batch, slot)
}
lc.chainTail = header
header, err = lc.GetParent(header)
}
lc.storeChainRange(batch)
}
if err := batch.Write(); err != nil {
log.Error("Failed to write batch to database", "error", err)
}
}
// HasHeader returns true if a header with the given block root exists.
func (lc *LightChain) HasHeader(blockRoot common.Hash) bool {
_, ok := lc.getSlotByHash(blockRoot)
return ok
}
// GetHeaderByHash returns the header with the given block root.
func (lc *LightChain) GetHeaderByHash(blockRoot common.Hash) (types.Header, error) {
if slot, ok := lc.getSlotByHash(blockRoot); ok {
header, err := lc.getHeader(slot, blockRoot)
if err != nil {
log.Error("LightChain blockRoot -> slot entry found but header is missing", "slot", slot, "blockRoot", blockRoot)
}
return header, err
}
return types.Header{}, ErrNotFound
}
// GetParent returns the parent of the given header if available.
func (lc *LightChain) GetParent(header types.Header) (types.Header, error) {
if parentSlot, ok := lc.hashToSlotCache.Get(header.ParentRoot); ok {
parent, err := lc.getHeader(parentSlot, header.ParentRoot)
if err != nil {
log.Error("LightChain blockRoot -> slot entry found in cache but header is missing", "slot", parentSlot, "blockRoot", header.ParentRoot)
}
return parent, err
}
parentSlot, maxReverseCount := header.Slot, 3
for parentSlot > 0 && maxReverseCount > 0 {
parentSlot--
maxReverseCount--
parent, err := lc.getHeader(parentSlot, header.ParentRoot)
if err == nil {
return parent, nil
}
}
return lc.GetHeaderByHash(header.ParentRoot)
}
// IsCanonical returns true if the given header is part of the current canonical chain.
func (lc *LightChain) IsCanonical(header types.Header) bool {
return lc.getCanonicalHash(header.Slot) == header.Hash()
}
// GetHeaderBySlot returns the canonical header at the given slot. Note that empty
// slots inside the canonical range return ErrEmptySlot while out-of-range request
// return ErrNotFound.
func (lc *LightChain) GetHeaderBySlot(slot uint64) (types.Header, error) {
lc.lock.RLock()
defer lc.lock.RUnlock()
return lc.getHeaderBySlot(slot)
}
func (lc *LightChain) getHeaderBySlot(slot uint64) (types.Header, error) {
if !lc.chainInit || slot < lc.chainTail.Slot || slot > lc.chainHead.Slot {
return types.Header{}, ErrNotFound
}
blockRoot := lc.getCanonicalHash(slot)
if blockRoot == (common.Hash{}) {
return types.Header{}, ErrEmptySlot
}
header, err := lc.getHeader(slot, blockRoot)
if err != nil {
log.Error("LightChain canonical blockRoot entry found but header is missing", "slot", slot, "blockRoot", blockRoot)
}
return header, err
}
func (lc *LightChain) getCanonicalHash(slot uint64) common.Hash {
if blockRoot, ok := lc.canonicalCache.Get(slot); ok {
return blockRoot
}
var blockRoot common.Hash
if data, err := lc.db.Get(getCanonicalKey(slot)); err == nil && len(data) == len(blockRoot) {
copy(blockRoot[:], data)
}
lc.canonicalCache.Add(slot, blockRoot)
return blockRoot
}
func (lc *LightChain) storeCanonicalHash(batch ethdb.Batch, slot uint64, blockRoot common.Hash) {
if blockRoot == (common.Hash{}) {
lc.deleteCanonicalHash(batch, slot)
return
}
batch.Put(getCanonicalKey(slot), blockRoot[:])
lc.canonicalCache.Add(slot, blockRoot)
}
func (lc *LightChain) deleteCanonicalHash(batch ethdb.Batch, slot uint64) {
batch.Delete(getCanonicalKey(slot))
lc.canonicalCache.Add(slot, common.Hash{})
}
func (lc *LightChain) getHeader(slot uint64, blockRoot common.Hash) (types.Header, error) {
if header, ok := lc.headerCache.Get(slotAndHash{slot, blockRoot}); ok {
return header, nil
}
headerEnc, err := lc.db.Get(getHeaderKey(slot, blockRoot))
if err != nil {
return types.Header{}, ErrNotFound
}
var header types.Header
if err := rlp.DecodeBytes(headerEnc, &header); err != nil {
log.Error("Failed to decode beacon header", "error", err)
return types.Header{}, ErrNotFound
}
return header, nil
}
func (lc *LightChain) getSlotByHash(blockRoot common.Hash) (uint64, bool) {
if slot, ok := lc.hashToSlotCache.Get(blockRoot); ok {
return slot, true
}
slotEnc, err := lc.db.Get(getHashToSlotKey(blockRoot))
if err != nil {
return 0, false
}
var slot uint64
if err := rlp.DecodeBytes(slotEnc, &slot); err != nil {
log.Error("Failed to decode slot number", "error", err)
return 0, false
}
return slot, true
}
// HasStateProof returns true if a state proof belonging to the given header exists.
func (lc *LightChain) HasStateProof(header types.Header) bool {
if _, ok := lc.stateCache.Get(slotAndHash{header.Slot, header.StateRoot}); ok {
return true
}
ok, err := lc.db.Has(getStateKey(header.Slot, header.StateRoot))
return ok && err == nil
}
// GetStateProof returns the state proof belonging to the given header.
func (lc *LightChain) GetStateProof(header types.Header) (merkle.MultiProof, error) {
if values, ok := lc.stateCache.Get(slotAndHash{header.Slot, header.StateRoot}); ok {
return merkle.MultiProof{Format: lc.stateProofFormat, Values: values}, nil
}
stateEnc, err := lc.db.Get(getStateKey(header.Slot, header.StateRoot))
if err != nil {
return merkle.MultiProof{}, ErrNotFound
}
var state stateProofData
if err := rlp.DecodeBytes(stateEnc, &state); err != nil {
log.Error("Failed to decode state proof data", "error", err)
return merkle.MultiProof{}, ErrNotFound
}
return merkle.MultiProof{Format: lc.stateProofFormat, Values: state.Values}, nil
}
// AddStateProof adds a state proof. If it belongs to a canonical header then
// the state range is also updated.
func (lc *LightChain) AddStateProof(header types.Header, proof merkle.MultiProof) error {
lc.lock.Lock()
defer lc.lock.Unlock()
if !merkle.IsEqual(proof.Format, lc.StateProofFormat(header)) {
return ErrInvalidProofFormat
}
if proof.RootHash() != header.StateRoot {
return ErrInvalidStateRoot
}
batch := lc.db.NewBatch()
stateEnc, err := rlp.EncodeToBytes(&stateProofData{Values: proof.Values})
if err != nil {
log.Error("Failed to encode state proof data", "error", err)
return err
}
batch.Put(getStateKey(header.Slot, header.StateRoot), stateEnc)
lc.stateCache.Add(slotAndHash{header.Slot, header.StateRoot}, proof.Values)
if !lc.IsCanonical(header) {
return nil
}
if !lc.stateInit {
lc.stateInit = true
lc.stateHead = header
lc.stateTail = header
return nil
}
if header.Slot > lc.stateHead.Slot && header.Slot <= lc.chainHead.Slot {
lc.extendStateHead(batch)
} else if header.Slot < lc.stateTail.Slot && header.Slot >= lc.chainTail.Slot {
lc.extendStateTail(batch)
}
lc.storeChainRange(batch)
if err := batch.Write(); err != nil {
log.Error("Failed to write batch to database", "error", err)
return err
}
return nil
}
// StateProofFormat returns the expected state proof format for the given header.
func (lc *LightChain) StateProofFormat(header types.Header) merkle.ProofFormat {
return lc.stateProofFormat
}
func (lc *LightChain) extendStateHead(batch ethdb.Batch) {
for slot := lc.stateHead.Slot + 1; slot <= lc.chainHead.Slot; slot++ {
if header, err := lc.getHeaderBySlot(slot); err == nil {
if lc.HasStateProof(header) {
lc.stateHead = header
} else {
break
}
}
}
}
func (lc *LightChain) extendStateTail(batch ethdb.Batch) {
if lc.stateTail.Slot == 0 {
return
}
for slot := lc.stateTail.Slot - 1; slot >= lc.chainTail.Slot; slot-- {
if header, err := lc.getHeaderBySlot(slot); err == nil {
if lc.HasStateProof(header) {
lc.stateTail = header
} else {
break
}
}
}
}
func (lc *LightChain) reinitStateChain(batch ethdb.Batch, header types.Header) {
for slot := header.Slot; slot <= lc.chainHead.Slot; slot++ {
if header, err := lc.getHeaderBySlot(slot); err == nil && lc.HasStateProof(header) {
lc.stateInit = true
lc.stateHead = header
lc.stateTail = header
lc.extendStateHead(batch)
return
}
}
}

View file

@ -0,0 +1,66 @@
// 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 light
import (
"math/rand"
"testing"
"github.com/ethereum/go-ethereum/beacon/light/types"
"github.com/ethereum/go-ethereum/beacon/merkle"
"github.com/ethereum/go-ethereum/common"
)
func makeChain(tail types.Header, headSlot uint64, format merkle.ProofFormat) (headers []types.Header, stateProofs []merkle.MultiProof) {
valueCount := merkle.ValueCount(format)
for tail.Slot < headSlot {
var (
slot uint64
parentRoot common.Hash
)
if tail != (types.Header{}) {
slot = tail.Slot + 1
parentRoot = tail.Hash()
}
for slot < headSlot && rand.Intn(5) == 0 {
slot++
}
stateProof := merkle.MultiProof{
Format: format,
Values: make(merkle.Values, valueCount),
}
for i, _ := range stateProof.Values {
stateProof.Values[i] = merkle.Value(randomHash())
}
header := types.Header{
Slot: slot,
ProposerIndex: uint64(rand.Intn(10000)),
BodyRoot: randomHash(),
StateRoot: stateProof.RootHash(),
ParentRoot: parentRoot,
}
headers = append(headers, header)
stateProofs = append(stateProofs, stateProof)
tail = header
}
}
func randomHash() (hash common.Hash) {
rand.Read(hash[:])
return
}

View file

@ -79,6 +79,15 @@ func IsEqual(a, b ProofFormat) bool {
return IsEqual(al, bl) && IsEqual(ar, br) return IsEqual(al, bl) && IsEqual(ar, br)
} }
// ValueCount returns the number of merkle values required for this proof format
func ValueCount(f ProofFormat) int {
if f == nil {
return 0
}
l, r := f.Children()
return ValueCount(l) + ValueCount(r)
}
// ProofReader allows traversing and reading a tree structure or a subset of it. // ProofReader allows traversing and reading a tree structure or a subset of it.
// Note: the hash of each traversed node is always requested. If the internal // Note: the hash of each traversed node is always requested. If the internal
// hash is not available then subtrees are always traversed (first left, then right). // hash is not available then subtrees are always traversed (first left, then right).

View file

@ -269,7 +269,7 @@ func (s *engineApiUpdater) Process(env *request.Environment) {
if finalizedState, err := s.chain.GetStateProof(finalized); err == nil { if finalizedState, err := s.chain.GetStateProof(finalized); err == nil {
finalizedExecRoot = common.Hash(finalizedState.Values[execBlockIndex]) finalizedExecRoot = common.Hash(finalizedState.Values[execBlockIndex])
} }
s.chain.DeleteBefore(finalized.Slot) s.chain.Prune(finalized.Slot, true)
} }
} else { } else {
if s.stateSync.HeadSyncPossible() { if s.stateSync.HeadSyncPossible() {