mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
core,light: implement fast lookup of ancestor hashes
This commit is contained in:
parent
c5c5e0dbe8
commit
7aa5ec37d2
7 changed files with 465 additions and 55 deletions
|
|
@ -1679,6 +1679,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
|
|||
|
||||
switch status {
|
||||
case CanonStatTy:
|
||||
bc.hc.hashHistory.Set(block.Header())
|
||||
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
||||
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
|
||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
||||
|
|
@ -1967,6 +1968,8 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
|||
// Write lookup entries for hash based transaction/receipt searches
|
||||
rawdb.WriteTxLookupEntries(bc.db, newChain[i])
|
||||
addedTxs = append(addedTxs, newChain[i].Transactions()...)
|
||||
// Add to hash history
|
||||
bc.hc.hashHistory.Set(newChain[i].Header())
|
||||
}
|
||||
// When transactions get deleted from the database, the receipts that were
|
||||
// created in the fork must also be deleted
|
||||
|
|
@ -2224,3 +2227,8 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript
|
|||
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
|
||||
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
//GetAncestorHash return the hash of ancestor at the given number
|
||||
func (bc *BlockChain) GetAncestorHash(ref *types.Header, target uint64) common.Hash {
|
||||
return bc.hc.GetAncestorHash(ref, target)
|
||||
}
|
||||
|
|
|
|||
25
core/evm.go
25
core/evm.go
|
|
@ -31,8 +31,8 @@ type ChainContext interface {
|
|||
// Engine retrieves the chain's consensus engine.
|
||||
Engine() consensus.Engine
|
||||
|
||||
// GetHeader returns the hash corresponding to their hash.
|
||||
GetHeader(common.Hash, uint64) *types.Header
|
||||
// GetAncestorHash return the hash of ancestor at the given number
|
||||
GetAncestorHash(child *types.Header, number uint64) common.Hash
|
||||
}
|
||||
|
||||
// NewEVMContext creates a new context for use in the EVM.
|
||||
|
|
@ -60,27 +60,8 @@ func NewEVMContext(msg Message, header *types.Header, chain ChainContext, author
|
|||
|
||||
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
||||
var cache map[uint64]common.Hash
|
||||
|
||||
return func(n uint64) common.Hash {
|
||||
// If there's no hash cache yet, make one
|
||||
if cache == nil {
|
||||
cache = map[uint64]common.Hash{
|
||||
ref.Number.Uint64() - 1: ref.ParentHash,
|
||||
}
|
||||
}
|
||||
// Try to fulfill the request from the cache
|
||||
if hash, ok := cache[n]; ok {
|
||||
return hash
|
||||
}
|
||||
// Not cached, iterate the blocks and cache the hashes
|
||||
for header := chain.GetHeader(ref.ParentHash, ref.Number.Uint64()-1); header != nil; header = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) {
|
||||
cache[header.Number.Uint64()-1] = header.ParentHash
|
||||
if n == header.Number.Uint64()-1 {
|
||||
return header.ParentHash
|
||||
}
|
||||
}
|
||||
return common.Hash{}
|
||||
return chain.GetAncestorHash(ref, n)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
173
core/hashbuffer.go
Normal file
173
core/hashbuffer.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2019 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 core
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
// For EVM execution, we need around 256 items. We add a few more to allow reorgs.
|
||||
// For mainnet, a couple more would suffice, but a few more added for
|
||||
// testnets/private nets
|
||||
// For LES, we use a larger buffer. 500K * 32 bytes = 16M
|
||||
const hashBufferElems = 500000
|
||||
|
||||
var (
|
||||
hashHitCounter = metrics.NewRegisteredGauge("chain/headerhash/hit", nil)
|
||||
hashMissCounter = metrics.NewRegisteredGauge("chain/headerhash/miss", nil)
|
||||
hashHeadGauge = metrics.NewRegisteredGauge("chain/headerhash/head", nil)
|
||||
hashTailGauge = metrics.NewRegisteredGauge("chain/headerhash/tail", nil)
|
||||
)
|
||||
|
||||
// hashBuffer implements a storage for chains of hashes, intended to be used for quick lookup of block hashes.
|
||||
// Internally, it uses an array of hashes in a circular buffer.
|
||||
// It enforces that all hashes added have a contiguous parent-child relation, and supports rollbacks
|
||||
// It is thread-safe.
|
||||
type hashBuffer struct {
|
||||
// The data holds the hashes. The hashes are sequential, but also a
|
||||
// circular buffer.
|
||||
// The `head` points to the position of the latest hash.
|
||||
// The parent, if present, is located 32 bytes back.
|
||||
// [.., .., ..., head-2 , head-1, head, oldest, ... ]
|
||||
data [hashBufferElems]common.Hash
|
||||
|
||||
head uint64 // index of hash for the head block
|
||||
|
||||
headNumber uint64 // The block number for head (the most recent block)
|
||||
tailNumber uint64 // The block number for tail (the oldest block)
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// newHashBuffer creates a new storage with a header in it.
|
||||
// Since we take a header here, a hash storage can never be empty.
|
||||
// This makes things easier later on (in Set)
|
||||
func newHashBuffer(header *types.Header) *hashBuffer {
|
||||
return &hashBuffer{
|
||||
headNumber: header.Number.Uint64(),
|
||||
tailNumber: header.Number.Uint64(),
|
||||
data: [hashBufferElems]common.Hash{header.Hash()},
|
||||
}
|
||||
}
|
||||
|
||||
// Get locates the hash for the requested number
|
||||
func (hs *hashBuffer) Get(number uint64) (common.Hash, bool) {
|
||||
hs.mu.RLock()
|
||||
defer hs.mu.RUnlock()
|
||||
if !hs.has(number) {
|
||||
hashMissCounter.Inc(1)
|
||||
return common.Hash{}, false
|
||||
}
|
||||
hashHitCounter.Inc(1)
|
||||
distance := hs.headNumber - number
|
||||
index := (hs.head + hashBufferElems - distance) % hashBufferElems
|
||||
return hs.data[index], true
|
||||
}
|
||||
|
||||
// has returns if the storage has a hash for the given number
|
||||
func (hs *hashBuffer) has(number uint64) bool {
|
||||
return number <= hs.headNumber && number >= hs.tailNumber
|
||||
}
|
||||
|
||||
// Contains checks if the hash at the given number matches the expected
|
||||
func (hs *hashBuffer) Contains(number uint64, expected common.Hash) bool {
|
||||
hs.mu.RLock()
|
||||
defer hs.mu.RUnlock()
|
||||
if hs.contains(number, expected) {
|
||||
hashHitCounter.Inc(1)
|
||||
return true
|
||||
} else {
|
||||
hashMissCounter.Inc(1)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// contains is the non-concurrency safe internal version of Contains
|
||||
func (hs *hashBuffer) contains(number uint64, expected common.Hash) bool {
|
||||
if !hs.has(number) {
|
||||
return false
|
||||
}
|
||||
distance := hs.headNumber - number
|
||||
index := (hs.head + hashBufferElems - distance) % hashBufferElems
|
||||
return hs.data[index] == expected
|
||||
}
|
||||
|
||||
// Newest returns the most recent (number, hash) stored
|
||||
func (hs *hashBuffer) Newest() (uint64, common.Hash) {
|
||||
hs.mu.RLock()
|
||||
defer hs.mu.RUnlock()
|
||||
return hs.headNumber, hs.data[hs.head]
|
||||
}
|
||||
|
||||
// Oldest returns the oldest (number, hash) found
|
||||
func (hs *hashBuffer) Oldest() (uint64, common.Hash) {
|
||||
hs.mu.RLock()
|
||||
defer hs.mu.RUnlock()
|
||||
distance := hs.headNumber - hs.tailNumber
|
||||
index := (hs.head + hashBufferElems - distance) % hashBufferElems
|
||||
return hs.tailNumber, hs.data[index]
|
||||
}
|
||||
|
||||
// Set inserts a new header (hash) to the storage.
|
||||
// If
|
||||
// a) Header already exists, this is a no-op
|
||||
// b) Number is occupied by other header, the new header replaces it, and also
|
||||
// truncates any descendants
|
||||
//
|
||||
// If the new header does not have any ancestors, it replaces the entire storage.
|
||||
func (hs *hashBuffer) Set(header *types.Header) {
|
||||
var (
|
||||
number = header.Number.Uint64()
|
||||
index uint64
|
||||
hash = header.Hash()
|
||||
)
|
||||
hs.mu.Lock()
|
||||
defer hs.mu.Unlock()
|
||||
if hs.contains(number-1, header.ParentHash) {
|
||||
if hs.headNumber >= number {
|
||||
distance := hs.headNumber - number
|
||||
index = (hs.head + hashBufferElems - distance) % hashBufferElems
|
||||
if hs.data[index] == hash {
|
||||
return
|
||||
}
|
||||
// Continue by replacing this number and wipe descendants
|
||||
} else {
|
||||
// head is parent of this new header - regular append
|
||||
index = (hs.head + 1) % hashBufferElems
|
||||
}
|
||||
} else {
|
||||
// This should not normally happen, and indicates a programming error
|
||||
log.Error("Hash storage wiping ancestors", "oldhead", hs.headNumber, "newhead", number, "oldtail", hs.tailNumber)
|
||||
// Wipe ancestors
|
||||
hs.tailNumber = number
|
||||
}
|
||||
hs.head = index
|
||||
hs.headNumber = number
|
||||
hs.data[hs.head] = hash
|
||||
|
||||
if number-hs.tailNumber == hashBufferElems {
|
||||
// It's full, need to move the tail
|
||||
hs.tailNumber++
|
||||
}
|
||||
hashTailGauge.Update(int64(hs.tailNumber))
|
||||
hashHeadGauge.Update(int64(hs.headNumber))
|
||||
}
|
||||
204
core/hashbuffer_test.go
Normal file
204
core/hashbuffer_test.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// Copyright 2019 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 core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
||||
func dummyHeader(n int, prev common.Hash) *types.Header {
|
||||
return &types.Header{
|
||||
Number: new(big.Int).SetUint64(uint64(n)),
|
||||
ParentHash: prev,
|
||||
}
|
||||
}
|
||||
|
||||
func uniqueHeader(n int, prev common.Hash) *types.Header {
|
||||
return &types.Header{
|
||||
Number: new(big.Int).SetUint64(uint64(n)),
|
||||
ParentHash: prev,
|
||||
GasUsed: rand.Uint64(),
|
||||
}
|
||||
}
|
||||
|
||||
// TestConsecutiveHashes does inserts and rollback, but uses contiguous chains
|
||||
func TestConsecutiveHashes(t *testing.T) {
|
||||
t.Parallel()
|
||||
// This should we swapped out very quickly
|
||||
hs := newHashBuffer(uniqueHeader(0, common.Hash{0xaa}))
|
||||
|
||||
parent := common.Hash{}
|
||||
expected := make(map[int]common.Hash)
|
||||
|
||||
assertEmpty := func(num int) {
|
||||
h, found := hs.Get(uint64(num))
|
||||
if found {
|
||||
t.Fatalf("expected %d not to be present", num)
|
||||
}
|
||||
if h != (common.Hash{}) {
|
||||
t.Fatalf("expected empty hash, got %x", h)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test 10 entries
|
||||
for n := 1; n < 10; n++ {
|
||||
h := dummyHeader(n, parent)
|
||||
hs.Set(h)
|
||||
if _, lh := hs.Newest(); lh != h.Hash() {
|
||||
t.Fatalf("num %d, wrong last hash, got %x exp %x", n, lh, h.Hash())
|
||||
}
|
||||
expected[n] = h.Hash()
|
||||
parent = h.Hash()
|
||||
}
|
||||
|
||||
n, _ := hs.Oldest()
|
||||
if n != 1 {
|
||||
t.Fatalf("wrong oldest, expected %d got %d", 1, n)
|
||||
}
|
||||
|
||||
for n := 1; n < 10; n++ {
|
||||
got, _ := hs.Get(uint64(n))
|
||||
exp := expected[n]
|
||||
if got != exp {
|
||||
t.Errorf("num %d, got %x expected %x", n, got, exp)
|
||||
}
|
||||
}
|
||||
assertEmpty(11)
|
||||
assertEmpty(0)
|
||||
|
||||
// Write another 300, overflowing the storage
|
||||
for n := 10; n < hashBufferElems+10; n++ {
|
||||
h := dummyHeader(n, parent)
|
||||
hs.Set(h)
|
||||
if _, lh := hs.Newest(); lh != h.Hash() {
|
||||
t.Fatalf("num %d, wrong last hash, got %x exp %x", n, lh, h.Hash())
|
||||
}
|
||||
expected[n] = h.Hash()
|
||||
parent = h.Hash()
|
||||
}
|
||||
x, _ := hs.Oldest()
|
||||
if x != 10 {
|
||||
t.Fatalf("wrong oldest, expected %d got %d", 10, x)
|
||||
}
|
||||
|
||||
// The last 256 should be available
|
||||
for n := hashBufferElems + 10 - 1; n > 10; n-- {
|
||||
got, found := hs.Get(uint64(n))
|
||||
exp := expected[n]
|
||||
if !found {
|
||||
t.Fatalf("expected %d to be found", n)
|
||||
}
|
||||
if got != exp {
|
||||
t.Fatalf("num %d, got %x expected %x", n, got, exp)
|
||||
}
|
||||
}
|
||||
// The older ones should be flushed
|
||||
for ; n > 0; n-- {
|
||||
got, found := hs.Get(n)
|
||||
if found {
|
||||
t.Fatalf("expected %d to be flushed", n)
|
||||
}
|
||||
if got != (common.Hash{}) {
|
||||
t.Fatalf("expected empty hash, got %x", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashStorageNonContiguous(t *testing.T) {
|
||||
|
||||
hs := newHashBuffer(uniqueHeader(0, common.Hash{0xff}))
|
||||
parent := common.Hash{}
|
||||
expected := make(map[int]common.Hash)
|
||||
|
||||
for n := 1; n < 10; n++ {
|
||||
hs.Set(uniqueHeader(n, parent))
|
||||
hdr := uniqueHeader(n, parent)
|
||||
hs.Set(hdr)
|
||||
parent = hdr.Hash()
|
||||
expected[n] = hdr.Hash()
|
||||
}
|
||||
n, _ := hs.Oldest()
|
||||
if n != 1 {
|
||||
t.Fatalf("wrong oldest, expected %d got %d", 1, n)
|
||||
}
|
||||
for n := 1; n < 10; n++ {
|
||||
got, _ := hs.Get(uint64(n))
|
||||
exp := expected[n]
|
||||
if got != exp {
|
||||
t.Errorf("num %d, got %x expected %x", n, got, exp)
|
||||
}
|
||||
}
|
||||
// 9 headers there [ 1,2,3,4a,5,6,7,8,9]
|
||||
// Setting a new in the middle should change it to
|
||||
// [ 1, 2, 3, 4b]
|
||||
{
|
||||
parent = expected[4]
|
||||
hdr := uniqueHeader(5, parent)
|
||||
hs.Set(hdr)
|
||||
if hs.headNumber != 5 {
|
||||
t.Fatalf("expected head num 5, got %d", hs.headNumber)
|
||||
}
|
||||
got, found := hs.Get(5)
|
||||
if !found {
|
||||
t.Fatalf("expected hash to exist")
|
||||
}
|
||||
if !bytes.Equal(got[:], hdr.Hash().Bytes()) {
|
||||
t.Fatalf("expected %x, got %x", hdr.Hash(), got)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a totally new header at 3, should clean out everything else
|
||||
{
|
||||
hdr := uniqueHeader(4, common.Hash{0x1})
|
||||
hs.Set(hdr)
|
||||
if hs.headNumber != 4 {
|
||||
t.Fatalf("expected head num 4, got %d", hs.headNumber)
|
||||
}
|
||||
if hs.tailNumber != 4 {
|
||||
t.Fatalf("expected head num 4, got %d", hs.tailNumber)
|
||||
}
|
||||
if _, exist := hs.Get(3); exist {
|
||||
t.Fatalf("should be gone: %d", 3)
|
||||
}
|
||||
if _, exist := hs.Get(5); exist {
|
||||
t.Fatalf("should be gone: %d", 5)
|
||||
}
|
||||
if _, exist := hs.Get(0); exist {
|
||||
t.Fatalf("should be gone: %d", 0)
|
||||
}
|
||||
// 4 should be there
|
||||
got, found := hs.Get(4)
|
||||
if !found {
|
||||
t.Fatalf("expected hash to exist")
|
||||
}
|
||||
if !bytes.Equal(got[:], hdr.Hash().Bytes()) {
|
||||
t.Fatalf("expected %x, got %x", hdr.Hash(), got)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -60,6 +60,8 @@ type HeaderChain struct {
|
|||
tdCache *lru.Cache // Cache for the most recent block total difficulties
|
||||
numberCache *lru.Cache // Cache for the most recent block numbers
|
||||
|
||||
hashHistory *hashBuffer // Cache for recent hashes
|
||||
|
||||
procInterrupt func() bool
|
||||
|
||||
rand *mrand.Rand
|
||||
|
|
@ -96,6 +98,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
|||
if hc.genesisHeader == nil {
|
||||
return nil, ErrNoGenesis
|
||||
}
|
||||
hc.hashHistory = newHashBuffer(hc.genesisHeader)
|
||||
|
||||
hc.currentHeader.Store(hc.genesisHeader)
|
||||
if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) {
|
||||
|
|
@ -287,6 +290,7 @@ func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCa
|
|||
if err := writeHeader(header); err != nil {
|
||||
return i, err
|
||||
}
|
||||
hc.hashHistory.Set(header)
|
||||
stats.processed++
|
||||
}
|
||||
// Report some public statistics so the user has a clue what's going on
|
||||
|
|
@ -339,35 +343,19 @@ func (hc *HeaderChain) GetAncestor(hash common.Hash, number, ancestor uint64, ma
|
|||
if ancestor > number {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
if ancestor == 1 {
|
||||
// in this case it is cheaper to just read the header
|
||||
if header := hc.GetHeader(hash, number); header != nil {
|
||||
return header.ParentHash, number - 1
|
||||
} else {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
ref := hc.GetHeader(hash, number)
|
||||
if ref == nil {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
for ancestor != 0 {
|
||||
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
|
||||
ancestorHash := rawdb.ReadCanonicalHash(hc.chainDb, number-ancestor)
|
||||
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
|
||||
number -= ancestor
|
||||
return ancestorHash, number
|
||||
}
|
||||
}
|
||||
if *maxNonCanonical == 0 {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
*maxNonCanonical--
|
||||
ancestor--
|
||||
header := hc.GetHeader(hash, number)
|
||||
if header == nil {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
hash = header.ParentHash
|
||||
number--
|
||||
if number == 0 {
|
||||
return ref.Hash(), number
|
||||
}
|
||||
return hash, number
|
||||
target := number - ancestor
|
||||
ancestorHash := hc.GetAncestorHash(ref, target)
|
||||
if ancestorHash == (common.Hash{}) {
|
||||
return common.Hash{}, 0
|
||||
}
|
||||
return ancestorHash, target
|
||||
}
|
||||
|
||||
// GetTd retrieves a block's total difficulty in the canonical chain from the
|
||||
|
|
@ -464,6 +452,7 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.Header) {
|
|||
|
||||
hc.currentHeader.Store(head)
|
||||
hc.currentHeaderHash = head.Hash()
|
||||
hc.hashHistory.Set(head)
|
||||
headHeaderGauge.Update(head.Number.Int64())
|
||||
}
|
||||
|
||||
|
|
@ -543,3 +532,49 @@ func (hc *HeaderChain) Engine() consensus.Engine { return hc.engine }
|
|||
func (hc *HeaderChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAncestorHash return the hash of ancestor at the given number
|
||||
func (hc *HeaderChain) GetAncestorHash(ref *types.Header, target uint64) common.Hash {
|
||||
number := ref.Number.Uint64() - 1
|
||||
hash := ref.ParentHash
|
||||
if target == number {
|
||||
return hash
|
||||
}
|
||||
if target > number {
|
||||
// Should never happen
|
||||
log.Error("Ancestor number must be <= descendant", "target", target, "descendant", number)
|
||||
return common.Hash{}
|
||||
}
|
||||
var (
|
||||
maxNonCanonLookups = uint64(100)
|
||||
)
|
||||
if hashHistoryTail, _ := hc.hashHistory.Oldest(); hashHistoryTail <= target {
|
||||
// Iterate the chain until we hit the target or we hit a ancestor
|
||||
// within the storage
|
||||
for ; !hc.hashHistory.Contains(number, hash); maxNonCanonLookups-- {
|
||||
if maxNonCanonLookups == 0 {
|
||||
return common.Hash{}
|
||||
}
|
||||
header := hc.GetHeader(hash, number)
|
||||
if header == nil {
|
||||
return common.Hash{}
|
||||
}
|
||||
number, hash = header.Number.Uint64()-1, header.ParentHash
|
||||
if number == target {
|
||||
return hash
|
||||
}
|
||||
}
|
||||
// The hash storage has the right ancestor chain
|
||||
if h, ok := hc.hashHistory.Get(target); ok {
|
||||
return h
|
||||
}
|
||||
}
|
||||
// At this point, we have failed to find the ancestor in our hash history lookup.
|
||||
// Either it's some very long sidefork, or the 'ref' is a very old header, too
|
||||
// old to be in the history. In that case, it should exist in the canon chain.
|
||||
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
|
||||
return rawdb.ReadCanonicalHash(hc.chainDb, target)
|
||||
}
|
||||
// They are requesting a very old header which is not in the canon chain,
|
||||
return common.Hash{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,18 +179,18 @@ func testGetBlockHeaders(t *testing.T, protocol int) {
|
|||
headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header())
|
||||
}
|
||||
// Send the hash request and verify the response
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
|
||||
t.Fatalf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
// If the test used number origins, repeat with hashes as the too
|
||||
if tt.query.Origin.Hash == (common.Hash{}) {
|
||||
if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
||||
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
||||
|
||||
p2p.Send(peer.app, 0x03, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
||||
p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
|
||||
if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
|
||||
t.Fatalf("test %d: headers mismatch: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -542,3 +542,12 @@ func (lc *LightChain) DisableCheckFreq() {
|
|||
func (lc *LightChain) EnableCheckFreq() {
|
||||
atomic.StoreInt32(&lc.disableCheckFreq, 0)
|
||||
}
|
||||
|
||||
// GetAncestorHash return the hash of ancestor at the given number
|
||||
// This implementation is slow, since it does not use a hashstorage
|
||||
// lookup like blockchain. However, the light clients does not require
|
||||
// fast lookups for EVM execution. This should probably be improved if this
|
||||
// method becomes more used.
|
||||
func (lc *LightChain) GetAncestorHash(ref *types.Header, target uint64) common.Hash {
|
||||
return lc.hc.GetAncestorHash(ref, target)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue