mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
beacon/light: LightChain test chain generator and GetParent optimization
This commit is contained in:
parent
e825f3458d
commit
36a7292861
4 changed files with 416 additions and 310 deletions
|
|
@ -45,14 +45,20 @@ var (
|
|||
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 {
|
||||
lock sync.RWMutex
|
||||
db ethdb.KeyValueStore
|
||||
|
||||
chainHead, chainTail types.Header
|
||||
chainInit bool
|
||||
stateHead, stateTail types.Header
|
||||
stateInit bool
|
||||
chainInit bool // true if chainHead and chainTail are valid
|
||||
chainHead, chainTail types.Header // canonical block roots are available in this section
|
||||
stateInit bool // true if stateHead and stateTail are valid
|
||||
stateHead, stateTail types.Header // state proofs of canonical headers are available in this section
|
||||
lastStoredRange chainRangeData
|
||||
|
||||
headerCache *lru.Cache[slotAndHash, types.Header]
|
||||
|
|
@ -79,6 +85,7 @@ type stateProofData struct {
|
|||
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 {
|
||||
lc := &LightChain{
|
||||
db: db,
|
||||
|
|
@ -145,111 +152,9 @@ func (lc *LightChain) storeChainRange(batch ethdb.Batch) {
|
|||
batch.Put(chainRangeKey, rangeEnc)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// SetChainHead sets the canonical chain head and also finds the new tail if it
|
||||
// does not share a common ancestor with the old head. The state range is also
|
||||
// automatically updated so that it applies to the new canonical chain.
|
||||
func (lc *LightChain) SetChainHead(head types.Header) {
|
||||
lc.lock.Lock()
|
||||
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) {
|
||||
lc.lock.RLock()
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
// StateProofRange returns the subset of the canonical chain range where all state proofs are available.
|
||||
func (lc *LightChain) StateProofRange() (head, tail types.Header, init bool) {
|
||||
lc.lock.RLock()
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// Prune removes either everything or just non-canonical data before the given slot.
|
||||
func (lc *LightChain) Prune(beforeSlot uint64, removeCanonical bool) {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
|
@ -610,3 +319,325 @@ func (lc *LightChain) deleteBefore(beforeSlot uint64, removeCanonical bool) {
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
beacon/light/light_chain_test.go
Normal file
66
beacon/light/light_chain_test.go
Normal 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
|
||||
}
|
||||
|
|
@ -79,6 +79,15 @@ func IsEqual(a, b ProofFormat) bool {
|
|||
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.
|
||||
// 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).
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ func (s *engineApiUpdater) Process(env *request.Environment) {
|
|||
if finalizedState, err := s.chain.GetStateProof(finalized); err == nil {
|
||||
finalizedExecRoot = common.Hash(finalizedState.Values[execBlockIndex])
|
||||
}
|
||||
s.chain.DeleteBefore(finalized.Slot)
|
||||
s.chain.Prune(finalized.Slot, true)
|
||||
}
|
||||
} else {
|
||||
if s.stateSync.HeadSyncPossible() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue