From 36a7292861aad38f50101ff8b22b82c1b792f90b Mon Sep 17 00:00:00 2001 From: Zsolt Felfoldi Date: Fri, 7 Apr 2023 00:54:15 +0200 Subject: [PATCH] beacon/light: LightChain test chain generator and GetParent optimization --- beacon/light/light_chain.go | 649 ++++++++++++++++--------------- beacon/light/light_chain_test.go | 66 ++++ beacon/merkle/binary_merkle.go | 9 + cmd/blsync/block_sync.go | 2 +- 4 files changed, 416 insertions(+), 310 deletions(-) create mode 100644 beacon/light/light_chain_test.go diff --git a/beacon/light/light_chain.go b/beacon/light/light_chain.go index 120d2df802..ab8534285b 100644 --- a/beacon/light/light_chain.go +++ b/beacon/light/light_chain.go @@ -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 + } + } +} diff --git a/beacon/light/light_chain_test.go b/beacon/light/light_chain_test.go new file mode 100644 index 0000000000..73d9932784 --- /dev/null +++ b/beacon/light/light_chain_test.go @@ -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 . + +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 +} diff --git a/beacon/merkle/binary_merkle.go b/beacon/merkle/binary_merkle.go index 54cefaa997..d534f306e3 100644 --- a/beacon/merkle/binary_merkle.go +++ b/beacon/merkle/binary_merkle.go @@ -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). diff --git a/cmd/blsync/block_sync.go b/cmd/blsync/block_sync.go index d44f726f20..50789e51cb 100755 --- a/cmd/blsync/block_sync.go +++ b/cmd/blsync/block_sync.go @@ -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() {