core: import header chains in batches

This commit is contained in:
Martin Holst Swende 2019-04-11 19:41:24 +02:00
parent d5b79e752e
commit df221885ac
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
6 changed files with 137 additions and 113 deletions

View file

@ -39,6 +39,9 @@ type ChainReader interface {
// GetHeader retrieves a block header from the database by hash and number. // GetHeader retrieves a block header from the database by hash and number.
GetHeader(hash common.Hash, number uint64) *types.Header GetHeader(hash common.Hash, number uint64) *types.Header
// HasHeader checks presence of header in the database by hash and number.
HasHeader(hash common.Hash, number uint64) bool
// GetHeaderByNumber retrieves a block header from the database by number. // GetHeaderByNumber retrieves a block header from the database by number.
GetHeaderByNumber(number uint64) *types.Header GetHeaderByNumber(number uint64) *types.Header

View file

@ -88,7 +88,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainReader, header *types.He
} }
// Short circuit if the header is known, or it's parent not // Short circuit if the header is known, or it's parent not
number := header.Number.Uint64() number := header.Number.Uint64()
if chain.GetHeader(header.Hash(), number) != nil { if chain.HasHeader(header.Hash(), number) {
return nil return nil
} }
parent := chain.GetHeader(header.ParentHash, number-1) parent := chain.GetHeader(header.ParentHash, number-1)
@ -174,7 +174,7 @@ func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []
if parent == nil { if parent == nil {
return consensus.ErrUnknownAncestor return consensus.ErrUnknownAncestor
} }
if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil { if chain.HasHeader(headers[index].Hash(), headers[index].Number.Uint64()) {
return nil // known block return nil // known block
} }
return ethash.verifyHeader(chain, headers[index], parent, false, seals[index]) return ethash.verifyHeader(chain, headers[index], parent, false, seals[index])
@ -199,15 +199,24 @@ func (ethash *Ethash) VerifyUncles(chain consensus.ChainReader, block *types.Blo
number, parent := block.NumberU64()-1, block.ParentHash() number, parent := block.NumberU64()-1, block.ParentHash()
for i := 0; i < 7; i++ { for i := 0; i < 7; i++ {
ancestor := chain.GetBlock(parent, number) ancestorHeader := chain.GetHeader(parent, number)
if ancestor == nil { if ancestorHeader == nil {
break break
} }
ancestors[ancestor.Hash()] = ancestor.Header() ancestors[parent] = ancestorHeader
for _, uncle := range ancestor.Uncles() {
uncles.Add(uncle.Hash()) // If the ancestor doesn't have any uncles, we don't have to iterate them
if ancestorHeader.UncleHash != types.EmptyUncleHash {
// Need to add those uncles to the blacklist too
ancestorBlock := chain.GetBlock(parent, number)
if ancestorBlock == nil {
break
}
for _, uncle := range ancestorBlock.Uncles() {
uncles.Add(uncle.Hash())
}
} }
parent, number = ancestor.ParentHash(), number-1 parent, number = ancestorHeader.ParentHash, number-1
} }
ancestors[block.Hash()] = block.Header() ancestors[block.Hash()] = block.Header()
uncles.Add(block.Hash()) uncles.Add(block.Hash())

View file

@ -977,9 +977,10 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
} }
var ( var (
stats = struct{ processed, ignored int32 }{} stats = struct{ processed, ignored int32 }{}
start = time.Now() start = time.Now()
size = 0 size = 0
checkBodyPresent = true
) )
// updateHead updates the head fast sync block if the inserted blocks are better // updateHead updates the head fast sync block if the inserted blocks are better
// and returns a indicator whether the inserted blocks are canonical. // and returns a indicator whether the inserted blocks are canonical.
@ -1161,9 +1162,18 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
if !bc.HasHeader(block.Hash(), block.NumberU64()) { if !bc.HasHeader(block.Hash(), block.NumberU64()) {
return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4]) return i, fmt.Errorf("containing header #%d [%x…] unknown", block.Number(), block.Hash().Bytes()[:4])
} }
if bc.HasBlock(block.Hash(), block.NumberU64()) {
stats.ignored++ // Ignore if the entire data is already known
continue if checkBodyPresent {
if bc.HasBlock(block.Hash(), block.NumberU64()) {
stats.ignored++
continue
} else {
// If block N is unavailable, so are the next blocks.
// This should hold, but if it does not, the shortcut
// here will only cause overwriting some existing data
checkBodyPresent = false
}
} }
// Write all the data out into the database // Write all the data out into the database
rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body()) rawdb.WriteBody(batch, block.Hash(), block.NumberU64(), block.Body())
@ -2103,11 +2113,8 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
bc.wg.Add(1) bc.wg.Add(1)
defer bc.wg.Done() defer bc.wg.Done()
whFunc := func(header *types.Header) error { errIndex, _, err := bc.hc.InsertHeaderChain(chain, start, nil)
_, err := bc.hc.WriteHeader(header) return errIndex, err
return err
}
return bc.hc.InsertHeaderChain(chain, whFunc, start)
} }
// CurrentHeader retrieves the current head header of the canonical chain. The // CurrentHeader retrieves the current head header of the canonical chain. The

View file

@ -297,3 +297,4 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil } func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
func (cr *fakeChainReader) HasHeader(hash common.Hash, number uint64) bool { return false }

View file

@ -39,7 +39,7 @@ import (
const ( const (
headerCacheLimit = 512 headerCacheLimit = 512
tdCacheLimit = 1024 tdCacheLimit = 1024
numberCacheLimit = 2048 numberCacheLimit = 4096
) )
// HeaderChain implements the basic block header chain logic that is shared by // HeaderChain implements the basic block header chain logic that is shared by
@ -123,42 +123,63 @@ func (hc *HeaderChain) GetBlockNumber(hash common.Hash) *uint64 {
return number return number
} }
// WriteHeader writes a header into the local chain, given that its parent is // WriteHeaders writes a chain of headers into the local chain, given that the parents
// already known. If the total difficulty of the newly inserted header becomes // are already known. If the total difficulty of the newly inserted chain becomes
// greater than the current known TD, the canonical chain is re-routed. // greater than the current known TD, the canonical chain is reorged.
// //
// Note: This method is not concurrent-safe with inserting blocks simultaneously // Note: This method is not concurrent-safe with inserting blocks simultaneously
// into the chain, as side effects caused by reorganisations cannot be emulated // into the chain, as side effects caused by reorganisations cannot be emulated
// without the real blocks. Hence, writing headers directly should only be done // without the real blocks. Hence, writing headers directly should only be done
// in two scenarios: pure-header mode of operation (light clients), or properly // in two scenarios: pure-header mode of operation (light clients), or properly
// separated header/block phases (non-archive clients). // separated header/block phases (non-archive clients).
func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, err error) { func (hc *HeaderChain) WriteHeaders(headers []*types.Header, pwCallbackFn PWCallback) (ignored, imported int, status WriteStatus, lastHash common.Hash, lastHeader *types.Header, err error) {
// Cache some values to prevent constant recalculation if len(headers) == 0 {
var ( return ignored, imported, NonStatTy, lastHash, nil, err
hash = header.Hash() }
number = header.Number.Uint64() ptd := hc.GetTd(headers[0].ParentHash, headers[0].Number.Uint64()-1)
)
// Calculate the total difficulty of the header
ptd := hc.GetTd(header.ParentHash, number-1)
if ptd == nil { if ptd == nil {
return NonStatTy, consensus.ErrUnknownAncestor return ignored, imported, NonStatTy, lastHash, nil, consensus.ErrUnknownAncestor
} }
type numberHash struct {
number uint64
hash common.Hash
}
var (
lastNumber uint64 // Last successfully imported number
externTd *big.Int // TD of successfully imported chain
inserted []numberHash // Quick lookup of number/hash for the chain
)
lastHash, lastNumber = headers[0].ParentHash, headers[0].Number.Uint64()-1 // Already validated above
batch := hc.chainDb.NewBatch()
for _, header := range headers {
hash, number := header.Hash(), header.Number.Uint64()
if header.ParentHash != lastHash {
log.Warn("Non-contiguous header insertion", "header.parent", header.ParentHash, "expected", hash, "number", number)
break
}
externTd = new(big.Int).Add(header.Difficulty, ptd)
// If the header's already known, skip it, otherwise store
if !hc.HasHeader(hash, number) {
// Irrelevant of the canonical status, write the td and header to the database
rawdb.WriteTd(batch, hash, number, externTd)
hc.tdCache.Add(hash, new(big.Int).Set(externTd))
rawdb.WriteHeader(batch, header)
inserted = append(inserted, numberHash{number, hash})
hc.headerCache.Add(hash, header)
hc.numberCache.Add(hash, number)
}
lastHeader, lastHash, lastNumber, ptd = header, hash, number, externTd
}
batch.Write()
batch.Reset()
localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64()) localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64())
externTd := new(big.Int).Add(header.Difficulty, ptd)
// Irrelevant of the canonical status, write the td and header to the database
if err := hc.WriteTd(hash, number, externTd); err != nil {
log.Crit("Failed to write header total difficulty", "err", err)
}
rawdb.WriteHeader(hc.chainDb, header)
// If the total difficulty is higher than our known, add it to the canonical chain // If the total difficulty is higher than our known, add it to the canonical chain
// Second clause in the if statement reduces the vulnerability to selfish mining. // Second clause in the if statement reduces the vulnerability to selfish mining.
// Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf // Please refer to http://www.cs.cornell.edu/~ie53/publications/btcProcFC.pdf
if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) { if externTd.Cmp(localTd) > 0 || (externTd.Cmp(localTd) == 0 && mrand.Float64() < 0.5) {
// Delete any canonical number assignments above the new head // Delete any canonical number assignments above the new head
batch := hc.chainDb.NewBatch() for i := lastNumber + 1; ; i++ {
for i := number + 1; ; i++ {
hash := rawdb.ReadCanonicalHash(hc.chainDb, i) hash := rawdb.ReadCanonicalHash(hc.chainDb, i)
if hash == (common.Hash{}) { if hash == (common.Hash{}) {
break break
@ -166,44 +187,54 @@ func (hc *HeaderChain) WriteHeader(header *types.Header) (status WriteStatus, er
rawdb.DeleteCanonicalHash(batch, i) rawdb.DeleteCanonicalHash(batch, i)
} }
batch.Write() batch.Write()
batch.Reset()
// Overwrite any stale canonical number assignments // Overwrite any stale canonical number assignments, work backwards
// from the first inserted header
var ( var (
headHash = header.ParentHash headHash = headers[0].ParentHash
headNumber = header.Number.Uint64() - 1 headNumber = headers[0].Number.Uint64() - 1
headHeader = hc.GetHeader(headHash, headNumber)
) )
for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash { for rawdb.ReadCanonicalHash(hc.chainDb, headNumber) != headHash {
rawdb.WriteCanonicalHash(hc.chainDb, headHash, headNumber) rawdb.WriteCanonicalHash(batch, headHash, headNumber)
headHeader := hc.GetHeader(headHash, headNumber)
headHash = headHeader.ParentHash headHash = headHeader.ParentHash
headNumber = headHeader.Number.Uint64() - 1 headNumber = headHeader.Number.Uint64() - 1
headHeader = hc.GetHeader(headHash, headNumber)
} }
// Extend the canonical chain with the new header // Now write the current chain in a batch
rawdb.WriteCanonicalHash(hc.chainDb, hash, number) for _, hdr := range inserted {
rawdb.WriteHeadHeaderHash(hc.chainDb, hash) rawdb.WriteCanonicalHash(batch, hdr.hash, hdr.number)
}
hc.currentHeaderHash = hash rawdb.WriteHeadHeaderHash(batch, lastHash)
hc.currentHeader.Store(types.CopyHeader(header)) batch.Write()
headHeaderGauge.Update(header.Number.Int64()) batch.Reset()
hc.currentHeaderHash = lastHash
hc.currentHeader.Store(types.CopyHeader(lastHeader))
headHeaderGauge.Update(lastHeader.Number.Int64())
status = CanonStatTy status = CanonStatTy
} else { } else {
status = SideStatTy status = SideStatTy
} }
hc.headerCache.Add(hash, header) // Execute any post-write callback function
hc.numberCache.Add(hash, number) if pwCallbackFn != nil {
for _, header := range headers {
return if header.Number.Uint64() > lastNumber {
break
}
pwCallbackFn(header, status)
}
}
imported = len(inserted)
ignored = len(headers) - imported
return ignored, imported, status, lastHash, lastHeader, nil
} }
// WhCallback is a callback function for inserting individual headers. // PWCallback (Post-Write Callback) is a callback function for inserting
// that is called after each header is inserted.
// A callback is used for two reasons: first, in a LightChain, status should be // A callback is used for two reasons: first, in a LightChain, status should be
// processed and light chain events sent, while in a BlockChain this is not // processed and light chain events sent, while in a BlockChain this is not
// necessary since chain events are sent after inserting blocks. Second, the // necessary since chain events are sent after inserting blocks.
// header writes should be protected by the parent chain mutex individually. type PWCallback func(header *types.Header, status WriteStatus)
type WhCallback func(*types.Header) error
func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) { func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked // Do a sanity check that the provided chain is actually ordered and linked
@ -256,55 +287,31 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
return 0, nil return 0, nil
} }
// InsertHeaderChain attempts to insert the given header chain in to the local // InsertHeaderChain inserts the given headers, and returns the
// chain, possibly creating a reorg. If an error is returned, it will return the // index at which something went wrong, the status for the last imported block, and
// index number of the failing header as well an error describing what went wrong. // the error (if any)
// // The caller should hold applicable mutexes
// The verify parameter can be used to fine tune whether nonce verification func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, start time.Time, pwCallBackFn PWCallback) (int, WriteStatus, error) {
// should be done or not. The reason behind the optional check is because some
// of the header retrieval mechanisms already need to verfy nonces, as well as
// because nonces can be verified sparsely, not needing to check each.
func (hc *HeaderChain) InsertHeaderChain(chain []*types.Header, writeHeader WhCallback, start time.Time) (int, error) {
// Collect some import statistics to report on // Collect some import statistics to report on
stats := struct{ processed, ignored int }{} ignored, imported, status, lastHash, last, err := hc.WriteHeaders(chain, pwCallBackFn)
// All headers passed verification, import them into the database
for i, header := range chain {
// Short circuit insertion if shutting down
if hc.procInterrupt() {
log.Debug("Premature abort during headers import")
return i, errors.New("aborted")
}
// If the header's already known, skip it, otherwise store
hash := header.Hash()
if hc.HasHeader(hash, header.Number.Uint64()) {
externTd := hc.GetTd(hash, header.Number.Uint64())
localTd := hc.GetTd(hc.currentHeaderHash, hc.CurrentHeader().Number.Uint64())
if externTd == nil || externTd.Cmp(localTd) <= 0 {
stats.ignored++
continue
}
}
if err := writeHeader(header); err != nil {
return i, err
}
stats.processed++
}
// Report some public statistics so the user has a clue what's going on
last := chain[len(chain)-1]
context := []interface{}{ context := []interface{}{
"count", stats.processed, "elapsed", common.PrettyDuration(time.Since(start)), "count", imported,
"number", last.Number, "hash", last.Hash(), "elapsed", common.PrettyDuration(time.Since(start)),
} }
if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute { if err != nil {
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) context = append(context, "err", err)
} }
if stats.ignored > 0 { if last != nil {
context = append(context, []interface{}{"ignored", stats.ignored}...) context = append(context, "number", last.Number, "hash", lastHash)
if timestamp := time.Unix(int64(last.Time), 0); time.Since(timestamp) > time.Minute {
context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...)
}
}
if ignored > 0 {
context = append(context, []interface{}{"ignored", ignored}...)
} }
log.Info("Imported new block headers", context...) log.Info("Imported new block headers", context...)
return 0, status, nil
return 0, nil
} }
// GetBlockHashesFromHash retrieves a number of block hashes starting at a given // GetBlockHashesFromHash retrieves a number of block hashes starting at a given

View file

@ -371,9 +371,7 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
defer lc.wg.Done() defer lc.wg.Done()
var events []interface{} var events []interface{}
whFunc := func(header *types.Header) error { postWriteCallback := func(header *types.Header, status core.WriteStatus) {
status, err := lc.hc.WriteHeader(header)
switch status { switch status {
case core.CanonStatTy: case core.CanonStatTy:
log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash()) log.Debug("Inserted new header", "number", header.Number, "hash", header.Hash())
@ -383,9 +381,8 @@ func (lc *LightChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash()) log.Debug("Inserted forked header", "number", header.Number, "hash", header.Hash())
events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)}) events = append(events, core.ChainSideEvent{Block: types.NewBlockWithHeader(header)})
} }
return err
} }
i, err := lc.hc.InsertHeaderChain(chain, whFunc, start) i, _, err := lc.hc.InsertHeaderChain(chain, start, postWriteCallback)
lc.postChainEvents(events) lc.postChainEvents(events)
return i, err return i, err
} }