diff --git a/common/chainstats/chainstats.go b/common/chainstats/chainstats.go new file mode 100644 index 0000000000..c562879af5 --- /dev/null +++ b/common/chainstats/chainstats.go @@ -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 . + +// 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) +} diff --git a/core/blockchain.go b/core/blockchain.go index 644df123c5..e4a8972f28 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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" @@ -127,7 +128,8 @@ type BlockChain struct { validator Validator // block and state validator interface vmConfig vm.Config - badBlocks *lru.Cache // Bad block cache + 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() diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index d13247766a..ed4427619d 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -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 } } diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index cb671a7df4..066d8f8094 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -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 { diff --git a/eth/handler.go b/eth/handler.go index c2426544f6..71a0ee36ee 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -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) } } diff --git a/eth/sync.go b/eth/sync.go index 2da1464bc5..0ca57943b0 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -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) } }