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 {
|
switch status {
|
||||||
case CanonStatTy:
|
case CanonStatTy:
|
||||||
|
bc.hc.hashHistory.Set(block.Header())
|
||||||
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(),
|
||||||
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
|
"uncles", len(block.Uncles()), "txs", len(block.Transactions()), "gas", block.GasUsed(),
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)),
|
"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
|
// Write lookup entries for hash based transaction/receipt searches
|
||||||
rawdb.WriteTxLookupEntries(bc.db, newChain[i])
|
rawdb.WriteTxLookupEntries(bc.db, newChain[i])
|
||||||
addedTxs = append(addedTxs, newChain[i].Transactions()...)
|
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
|
// When transactions get deleted from the database, the receipts that were
|
||||||
// created in the fork must also be deleted
|
// 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 {
|
func (bc *BlockChain) SubscribeBlockProcessingEvent(ch chan<- bool) event.Subscription {
|
||||||
return bc.scope.Track(bc.blockProcFeed.Subscribe(ch))
|
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 retrieves the chain's consensus engine.
|
||||||
Engine() consensus.Engine
|
Engine() consensus.Engine
|
||||||
|
|
||||||
// GetHeader returns the hash corresponding to their hash.
|
// GetAncestorHash return the hash of ancestor at the given number
|
||||||
GetHeader(common.Hash, uint64) *types.Header
|
GetAncestorHash(child *types.Header, number uint64) common.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEVMContext creates a new context for use in the EVM.
|
// 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
|
// GetHashFn returns a GetHashFunc which retrieves header hashes by number
|
||||||
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash {
|
||||||
var cache map[uint64]common.Hash
|
|
||||||
|
|
||||||
return func(n uint64) common.Hash {
|
return func(n uint64) common.Hash {
|
||||||
// If there's no hash cache yet, make one
|
return chain.GetAncestorHash(ref, n)
|
||||||
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{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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
|
tdCache *lru.Cache // Cache for the most recent block total difficulties
|
||||||
numberCache *lru.Cache // Cache for the most recent block numbers
|
numberCache *lru.Cache // Cache for the most recent block numbers
|
||||||
|
|
||||||
|
hashHistory *hashBuffer // Cache for recent hashes
|
||||||
|
|
||||||
procInterrupt func() bool
|
procInterrupt func() bool
|
||||||
|
|
||||||
rand *mrand.Rand
|
rand *mrand.Rand
|
||||||
|
|
@ -96,6 +98,7 @@ func NewHeaderChain(chainDb ethdb.Database, config *params.ChainConfig, engine c
|
||||||
if hc.genesisHeader == nil {
|
if hc.genesisHeader == nil {
|
||||||
return nil, ErrNoGenesis
|
return nil, ErrNoGenesis
|
||||||
}
|
}
|
||||||
|
hc.hashHistory = newHashBuffer(hc.genesisHeader)
|
||||||
|
|
||||||
hc.currentHeader.Store(hc.genesisHeader)
|
hc.currentHeader.Store(hc.genesisHeader)
|
||||||
if head := rawdb.ReadHeadBlockHash(chainDb); head != (common.Hash{}) {
|
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 {
|
if err := writeHeader(header); err != nil {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
hc.hashHistory.Set(header)
|
||||||
stats.processed++
|
stats.processed++
|
||||||
}
|
}
|
||||||
// Report some public statistics so the user has a clue what's going on
|
// 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 {
|
if ancestor > number {
|
||||||
return common.Hash{}, 0
|
return common.Hash{}, 0
|
||||||
}
|
}
|
||||||
if ancestor == 1 {
|
ref := hc.GetHeader(hash, number)
|
||||||
// in this case it is cheaper to just read the header
|
if ref == nil {
|
||||||
if header := hc.GetHeader(hash, number); header != nil {
|
return common.Hash{}, 0
|
||||||
return header.ParentHash, number - 1
|
|
||||||
} else {
|
|
||||||
return common.Hash{}, 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for ancestor != 0 {
|
if number == 0 {
|
||||||
if rawdb.ReadCanonicalHash(hc.chainDb, number) == hash {
|
return ref.Hash(), number
|
||||||
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--
|
|
||||||
}
|
}
|
||||||
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
|
// 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.currentHeader.Store(head)
|
||||||
hc.currentHeaderHash = head.Hash()
|
hc.currentHeaderHash = head.Hash()
|
||||||
|
hc.hashHistory.Set(head)
|
||||||
headHeaderGauge.Update(head.Number.Int64())
|
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 {
|
func (hc *HeaderChain) GetBlock(hash common.Hash, number uint64) *types.Block {
|
||||||
return nil
|
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())
|
headers = append(headers, pm.blockchain.GetBlockByHash(hash).Header())
|
||||||
}
|
}
|
||||||
// Send the hash request and verify the response
|
// Send the hash request and verify the response
|
||||||
p2p.Send(peer.app, 0x03, tt.query)
|
p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
|
||||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
|
||||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
t.Fatalf("test %d: headers mismatch: %v", i, err)
|
||||||
}
|
}
|
||||||
// If the test used number origins, repeat with hashes as the too
|
// If the test used number origins, repeat with hashes as the too
|
||||||
if tt.query.Origin.Hash == (common.Hash{}) {
|
if tt.query.Origin.Hash == (common.Hash{}) {
|
||||||
if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
if origin := pm.blockchain.GetBlockByNumber(tt.query.Origin.Number); origin != nil {
|
||||||
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0
|
||||||
|
|
||||||
p2p.Send(peer.app, 0x03, tt.query)
|
p2p.Send(peer.app, GetBlockHeadersMsg, tt.query)
|
||||||
if err := p2p.ExpectMsg(peer.app, 0x04, headers); err != nil {
|
if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, headers); err != nil {
|
||||||
t.Errorf("test %d: headers mismatch: %v", i, err)
|
t.Fatalf("test %d: headers mismatch: %v", i, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -542,3 +542,12 @@ func (lc *LightChain) DisableCheckFreq() {
|
||||||
func (lc *LightChain) EnableCheckFreq() {
|
func (lc *LightChain) EnableCheckFreq() {
|
||||||
atomic.StoreInt32(&lc.disableCheckFreq, 0)
|
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