This commit is contained in:
Martin Holst Swende 2018-02-23 10:01:24 +00:00 committed by GitHub
commit 3c519a44f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 155 additions and 20 deletions

View file

@ -0,0 +1,83 @@
// Copyright 2018 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 chainstats implements some chain utilities for sync-free blockchain info lookup
package chainstats
import (
"math/big"
"sync/atomic"
"github.com/ethereum/go-ethereum/core/types"
)
type Chainstats struct {
currentBlockNumber uint64
currentFastBlockNumber uint64
currentTd atomic.Value
}
func NewChainstats() *Chainstats {
stats := &Chainstats{}
stats.currentTd.Store(big.NewInt(0))
return stats
}
// GetNumber returns the latest block number
func (stats *Chainstats) GetNumber() uint64 {
return stats.currentBlockNumber
}
// Update is a convenience method to set all values
func (stats *Chainstats) Update(currentBlock, currentFastBlock *types.Block, totalDifficulty *big.Int) {
stats.SetNumber(currentBlock.NumberU64())
stats.SetFastNumber(currentFastBlock.NumberU64())
stats.SetTotalDifficulty(totalDifficulty)
}
// GetNumbers convenience-method to get all values
func (stats *Chainstats) Get() (uint64, uint64, *big.Int) {
return stats.currentBlockNumber,
stats.currentFastBlockNumber,
new(big.Int).Set(stats.currentTd.Load().(*big.Int))
}
// SetNumber stores latest block number
func (stats *Chainstats) SetNumber(number uint64) {
atomic.StoreUint64(&stats.currentBlockNumber, number)
}
// GetFastNumber return latest fast block number
func (stats *Chainstats) GetFastNumber() uint64 {
return stats.currentFastBlockNumber
}
// SetFastNumber stores latest fast block number
func (stats *Chainstats) SetFastNumber(number uint64) {
atomic.StoreUint64(&stats.currentFastBlockNumber, number)
}
// GetTotalDifficulty return latest total difficulty
func (stats *Chainstats) GetTotalDifficulty() *big.Int {
return new(big.Int).Set(stats.currentTd.Load().(*big.Int))
}
// SetTotalDifficulty sets latest total difficulty
func (stats *Chainstats) SetTotalDifficulty(newTd *big.Int) {
stats.currentTd.Store(newTd)
}

View file

@ -28,6 +28,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/chainstats"
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
@ -128,6 +129,7 @@ type BlockChain struct {
vmConfig vm.Config
badBlocks *lru.Cache // Bad block cache
chainStats *chainstats.Chainstats // Mutex-free lookups of chain stats
}
// NewBlockChain returns a fully initialised block chain using information
@ -160,6 +162,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
engine: engine,
vmConfig: vmConfig,
badBlocks: badBlocks,
chainStats: chainstats.NewChainstats(),
}
bc.SetValidator(NewBlockValidator(chainConfig, bc, engine))
bc.SetProcessor(NewStateProcessor(chainConfig, bc, engine))
@ -248,6 +251,8 @@ func (bc *BlockChain) loadLastState() error {
blockTd := bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64())
fastTd := bc.GetTd(bc.currentFastBlock.Hash(), bc.currentFastBlock.NumberU64())
bc.chainStats.Update(bc.currentBlock, bc.currentFastBlock, blockTd)
log.Info("Loaded most recent local header", "number", currentHeader.Number, "hash", currentHeader.Hash(), "td", headerTd)
log.Info("Loaded most recent local full block", "number", bc.currentBlock.Number(), "hash", bc.currentBlock.Hash(), "td", blockTd)
log.Info("Loaded most recent local fast block", "number", bc.currentFastBlock.Number(), "hash", bc.currentFastBlock.Hash(), "td", fastTd)
@ -305,6 +310,9 @@ func (bc *BlockChain) SetHead(head uint64) error {
if err := WriteHeadFastBlockHash(bc.db, bc.currentFastBlock.Hash()); err != nil {
log.Crit("Failed to reset head fast block", "err", err)
}
bc.chainStats.Update(bc.currentBlock, bc.currentFastBlock, bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64()))
return bc.loadLastState()
}
@ -322,6 +330,8 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {
// If all checks out, manually set the head block
bc.mu.Lock()
bc.currentBlock = block
bc.chainStats.SetNumber(block.Number().Uint64())
bc.chainStats.SetTotalDifficulty(bc.GetTd(block.Hash(), block.NumberU64()))
bc.mu.Unlock()
log.Info("Committed new head block", "number", block.Number(), "hash", hash)
@ -421,6 +431,8 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
bc.hc.SetCurrentHeader(bc.genesisBlock.Header())
bc.currentFastBlock = bc.genesisBlock
bc.chainStats.Update(bc.currentBlock, bc.currentFastBlock, bc.genesisBlock.Difficulty())
return nil
}
@ -499,6 +511,7 @@ func (bc *BlockChain) insert(block *types.Block) {
}
bc.currentFastBlock = block
}
bc.chainStats.Update(bc.currentBlock, bc.currentFastBlock, bc.GetTd(block.Hash(), block.NumberU64()))
}
// Genesis retrieves the chain's genesis block.
@ -693,6 +706,24 @@ func (bc *BlockChain) procFutureBlocks() {
}
}
// Stats returns the chainstats which can be queried non-blocking for info about difficulty and numbers
// These are provided on a best-effort, and it's theoretically possible that two consecutive calls to
// number and difficulty return number for X and difficulty for Y, if the stats is updated between the calls
func (bc *BlockChain) Stats() *chainstats.Chainstats {
return bc.chainStats
}
func (bc *BlockChain) CurrentNumber() uint64 {
return bc.chainStats.GetNumber()
}
func (bc *BlockChain) CurrentFastNumber() uint64 {
return bc.chainStats.GetFastNumber()
}
func (bc *BlockChain) CurrentTD() *big.Int {
return bc.chainStats.GetTotalDifficulty()
}
// WriteStatus status of write
type WriteStatus byte
@ -724,6 +755,7 @@ func (bc *BlockChain) Rollback(chain []common.Hash) {
WriteHeadBlockHash(bc.db, bc.currentBlock.Hash())
}
}
bc.chainStats.Update(bc.currentBlock, bc.currentFastBlock, bc.GetTd(bc.currentBlock.Hash(), bc.currentBlock.NumberU64()))
}
// SetReceiptsData computes all the non-consensus fields of the receipts
@ -835,6 +867,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [
log.Crit("Failed to update head fast block hash", "err", err)
}
bc.currentFastBlock = head
bc.chainStats.SetFastNumber(bc.currentFastBlock.NumberU64())
}
}
bc.mu.Unlock()

View file

@ -193,6 +193,15 @@ type BlockChain interface {
// InsertReceiptChain inserts a batch of receipts into the local chain.
InsertReceiptChain(types.Blocks, []types.Receipts) (int, error)
// CurrentNumber retrieves number of the current head block
CurrentNumber() uint64
// CurrentNumber retrieves number of the current head fast block
CurrentFastNumber() uint64
// CurrentTD retrives the total difficulty of the current head block
CurrentTD() *big.Int
}
// New creates a new downloader to fetch hashes and blocks from remote peers.
@ -583,9 +592,9 @@ func (d *Downloader) findAncestor(p *peerConnection, height uint64) (uint64, err
floor, ceil := int64(-1), d.lightchain.CurrentHeader().Number.Uint64()
if d.mode == FullSync {
ceil = d.blockchain.CurrentBlock().NumberU64()
ceil = d.blockchain.CurrentNumber()
} else if d.mode == FastSync {
ceil = d.blockchain.CurrentFastBlock().NumberU64()
ceil = d.blockchain.CurrentFastNumber()
}
if ceil >= MaxForkAncestry {
floor = int64(ceil - MaxForkAncestry)
@ -1156,16 +1165,16 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
for i, header := range rollback {
hashes[i] = header.Hash()
}
lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0
lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, uint64(0), uint64(0)
if d.mode != LightSync {
lastFastBlock = d.blockchain.CurrentFastBlock().Number()
lastBlock = d.blockchain.CurrentBlock().Number()
lastFastBlock = d.blockchain.CurrentFastNumber()
lastBlock = d.blockchain.CurrentNumber()
}
d.lightchain.Rollback(hashes)
curFastBlock, curBlock := common.Big0, common.Big0
curFastBlock, curBlock := uint64(0), uint64(0)
if d.mode != LightSync {
curFastBlock = d.blockchain.CurrentFastBlock().Number()
curBlock = d.blockchain.CurrentBlock().Number()
curFastBlock = d.blockchain.CurrentFastNumber()
curBlock = d.blockchain.CurrentNumber()
}
log.Warn("Rolled back headers", "count", len(hashes),
"header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number),
@ -1205,8 +1214,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
// L: Request new headers up from 11 (R's TD was higher, it must have something)
// R: Nothing to give
if d.mode != LightSync {
head := d.blockchain.CurrentBlock()
if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.NumberU64())) > 0 {
if !gotHeaders && td.Cmp(d.blockchain.CurrentTD()) > 0 {
return errStallingPeer
}
}

View file

@ -282,6 +282,18 @@ func (dl *downloadTester) CurrentFastBlock() *types.Block {
}
return dl.genesis
}
func (dl *downloadTester) CurrentNumber() uint64 {
return dl.CurrentBlock().NumberU64()
}
func (dl *downloadTester) CurrentFastNumber() uint64 {
return dl.CurrentFastBlock().NumberU64()
}
func (dl *downloadTester) CurrentTD() *big.Int {
cur := dl.CurrentBlock()
return dl.GetTd(cur.Hash(), cur.NumberU64())
}
// FastSyncCommitHead manually sets the head block to a given hash.
func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error {

View file

@ -113,7 +113,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
quitSync: make(chan struct{}),
}
// Figure out whether to allow fast sync or not
if mode == downloader.FastSync && blockchain.CurrentBlock().NumberU64() > 0 {
if mode == downloader.FastSync && blockchain.Stats().GetNumber() > 0 {
log.Warn("Blockchain not empty, fast sync disabled")
mode = downloader.FullSync
}
@ -165,7 +165,7 @@ func NewProtocolManager(config *params.ChainConfig, mode downloader.SyncMode, ne
return engine.VerifyHeader(blockchain, header, true)
}
heighter := func() uint64 {
return blockchain.CurrentBlock().NumberU64()
return blockchain.Stats().GetNumber()
}
inserter := func(blocks types.Blocks) (int, error) {
// If fast sync is running, deny importing weird blocks
@ -647,8 +647,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// Schedule a sync if above ours. Note, this will not fire a sync for a gap of
// a singe block (as the true TD is below the propagated block), however this
// scenario should easily be covered by the fetcher.
currentBlock := pm.blockchain.CurrentBlock()
if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 {
currentTd := pm.blockchain.Stats().GetTotalDifficulty()
if trueTD.Cmp(currentTd) > 0 {
go pm.synchronise(p)
}
}

View file

@ -167,8 +167,7 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
return
}
// Make sure the peer's TD is higher than our own
currentBlock := pm.blockchain.CurrentBlock()
td := pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
currentNumber, currentFastNumber, td := pm.blockchain.Stats().Get()
pHead, pTd := peer.Head()
if pTd.Cmp(td) <= 0 {
@ -179,7 +178,7 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
if atomic.LoadUint32(&pm.fastSync) == 1 {
// Fast sync was explicitly requested, and explicitly granted
mode = downloader.FastSync
} else if currentBlock.NumberU64() == 0 && pm.blockchain.CurrentFastBlock().NumberU64() > 0 {
} else if currentNumber == 0 && currentFastNumber > 0 {
// The database seems empty as the current block is the genesis. Yet the fast
// block is ahead, so fast sync was enabled for this node at a certain point.
// The only scenario where this can happen is if the user manually (or via a
@ -197,13 +196,13 @@ func (pm *ProtocolManager) synchronise(peer *peer) {
atomic.StoreUint32(&pm.fastSync, 0)
}
atomic.StoreUint32(&pm.acceptTxs, 1) // Mark initial sync done
if head := pm.blockchain.CurrentBlock(); head.NumberU64() > 0 {
if pm.blockchain.Stats().GetNumber() > 0 {
// We've completed a sync cycle, notify all peers of new state. This path is
// essential in star-topology networks where a gateway node needs to notify
// all its out-of-date peers of the availability of a new block. This failure
// scenario will most often crop up in private and hackathon networks with
// degenerate connectivity, but it should be healthy for the mainnet too to
// more reliably update peers or the local TD state.
go pm.BroadcastBlock(head, false)
go pm.BroadcastBlock(pm.blockchain.CurrentBlock(), false)
}
}