core: Add checks for block finalization

This commit is contained in:
Guillaume Ballet 2020-05-13 14:35:00 +02:00
parent 46698d7931
commit 7011ed92ca
3 changed files with 155 additions and 1 deletions

View file

@ -77,6 +77,9 @@ var (
blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil)
errInsertionInterrupted = errors.New("insertion is interrupted") errInsertionInterrupted = errors.New("insertion is interrupted")
errReorgFinalizedBlock = errors.New("can not reorg finalized block")
errFinalizedBlockMissing = errors.New("finalized block missing")
) )
const ( const (
@ -193,6 +196,8 @@ type BlockChain struct {
badBlocks *lru.Cache // Bad block cache badBlocks *lru.Cache // Bad block cache
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion. terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
finalizedBlock *types.Block // Last finalized block
} }
// NewBlockChain returns a fully initialised block chain using information // NewBlockChain returns a fully initialised block chain using information
@ -247,6 +252,10 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
if bc.genesisBlock == nil { if bc.genesisBlock == nil {
return nil, ErrNoGenesis return nil, ErrNoGenesis
} }
bc.finalizedBlock = bc.GetBlockByHash(rawdb.ReadFinalizedBlockHash(bc.db))
if bc.finalizedBlock == nil {
bc.finalizedBlock = bc.genesisBlock
}
var nilBlock *types.Block var nilBlock *types.Block
bc.currentBlock.Store(nilBlock) bc.currentBlock.Store(nilBlock)
@ -404,6 +413,17 @@ func (bc *BlockChain) loadLastState() error {
headFastBlockGauge.Update(int64(block.NumberU64())) headFastBlockGauge.Update(int64(block.NumberU64()))
} }
} }
finalizedHash := rawdb.ReadFinalizedBlockHash(bc.db)
if (common.Hash{}) != finalizedHash {
bc.finalizedBlock = bc.GetBlockByHash(finalizedHash)
if bc.finalizedBlock == nil {
return errFinalizedBlockMissing
}
} else {
bc.finalizedBlock = bc.genesisBlock
}
// Issue a status log for the user // Issue a status log for the user
currentFastBlock := bc.CurrentFastBlock() currentFastBlock := bc.CurrentFastBlock()
@ -428,6 +448,11 @@ func (bc *BlockChain) SetHead(head uint64) error {
bc.chainmu.Lock() bc.chainmu.Lock()
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
// Check that the head isn't before the last finalized block
if head < bc.finalizedBlock.NumberU64() {
return errReorgFinalizedBlock
}
updateFn := func(db ethdb.KeyValueWriter, header *types.Header) { updateFn := func(db ethdb.KeyValueWriter, header *types.Header) {
// Rewind the block chain, ensuring we don't end up with a stateless head block // Rewind the block chain, ensuring we don't end up with a stateless head block
if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() < currentBlock.NumberU64() { if currentBlock := bc.CurrentBlock(); currentBlock != nil && header.Number.Uint64() < currentBlock.NumberU64() {
@ -581,7 +606,7 @@ func (bc *BlockChain) StateCache() state.Database {
// Reset purges the entire blockchain, restoring it to its genesis state. // Reset purges the entire blockchain, restoring it to its genesis state.
func (bc *BlockChain) Reset() error { func (bc *BlockChain) Reset() error {
return bc.ResetWithGenesisBlock(bc.genesisBlock) return bc.ResetWithFinalizedBlock()
} }
// ResetWithGenesisBlock purges the entire blockchain, restoring it to the // ResetWithGenesisBlock purges the entire blockchain, restoring it to the
@ -614,6 +639,37 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {
return nil return nil
} }
// ResetWithFinalizedBlock purges the entire blockchain, restoring it to the
// specified genesis state.
func (bc *BlockChain) ResetWithFinalizedBlock() error {
fbh := rawdb.ReadFinalizedBlockHash(bc.db)
fb := bc.GetBlockByHash(fbh)
// Dump the entire block chain and purge the caches
if err := bc.SetHead(fb.NumberU64()); err != nil {
return err
}
bc.chainmu.Lock()
defer bc.chainmu.Unlock()
// Prepare the genesis block and reinitialise the chain
batch := bc.db.NewBatch()
rawdb.WriteTd(batch, fb.Hash(), fb.NumberU64(), fb.Difficulty())
if err := batch.Write(); err != nil {
log.Crit("Failed to write genesis block", "err", err)
}
bc.writeHeadBlock(fb)
// Last update all in-memory chain markers
bc.currentBlock.Store(fb)
headBlockGauge.Update(int64(fb.NumberU64()))
bc.hc.SetGenesis(fb.Header())
bc.hc.SetCurrentHeader(fb.Header())
bc.currentFastBlock.Store(fb)
headFastBlockGauge.Update(int64(fb.NumberU64()))
return nil
}
// repair tries to repair the current blockchain by rolling back the current block // repair tries to repair the current blockchain by rolling back the current block
// until one with associated state is found. This is needed to fix incomplete db // until one with associated state is found. This is needed to fix incomplete db
// writes caused either by crashes/power outages, or simply non-committed tries. // writes caused either by crashes/power outages, or simply non-committed tries.
@ -632,6 +688,9 @@ func (bc *BlockChain) repair(head **types.Block) error {
if block == nil { if block == nil {
return fmt.Errorf("missing block %d [%x]", (*head).NumberU64()-1, (*head).ParentHash()) return fmt.Errorf("missing block %d [%x]", (*head).NumberU64()-1, (*head).ParentHash())
} }
if block.Hash() == bc.finalizedBlock.Hash() {
return errReorgFinalizedBlock
}
*head = block *head = block
} }
} }
@ -2096,6 +2155,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
commonBlock = oldBlock commonBlock = oldBlock
break break
} }
// Ensure the reorg doesn't occur pas the last finalized block
if oldBlock.Hash() == bc.finalizedBlock.Hash() {
return fmt.Errorf("trying to reorg past the last finalized block")
}
// Remove an old block as well as stash away a new block // Remove an old block as well as stash away a new block
oldChain = append(oldChain, oldBlock) oldChain = append(oldChain, oldBlock)
deletedTxs = append(deletedTxs, oldBlock.Transactions()...) deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
@ -2323,6 +2388,15 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
return i, err return i, err
} }
for idx, header := range chain {
if header.Hash() == bc.finalizedBlock.Hash() {
if idx == 0 {
break
}
return idx, errReorgFinalizedBlock
}
}
// Make sure only one thread manipulates the chain at once // Make sure only one thread manipulates the chain at once
bc.chainmu.Lock() bc.chainmu.Lock()
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
@ -2451,3 +2525,39 @@ 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))
} }
// FinalizeBlock performs block finalization by inserting the hash of the
// latest finalized block in the db.
func (bc *BlockChain) FinalizeBlock(fbh common.Hash) error {
bc.chainmu.Lock()
defer bc.chainmu.Unlock()
// TODO check that the finalized block is on the canonical head,
// and reorg if not.
newFinal := bc.GetBlockByHash(fbh)
if newFinal.NumberU64() <= bc.finalizedBlock.NumberU64() {
return errReorgFinalizedBlock
}
// TODO remove all the previous blocks
//batch := bc.db.NewBatch()
//for h := newFinal.NumberU64() - 1; h >= oldFinal.NumberU64(); h-- {
//b := bc.GetBlockByNumber(h)
//for _, uncle := range b.Uncles() {
//rawdb.DeleteBlock(batch, uncle.Hash(), uncle.Number.Uint64())
//}
//rawdb.DeleteBlock(batch, b.Hash(), h)
//}
//batch.Write()
//if err = batch.Write(); err != nil {
//return err
//}
err := rawdb.WriteFinalizedBlockHash(bc.db, fbh)
if err == nil {
bc.finalizedBlock = newFinal
}
return err
}

View file

@ -0,0 +1,41 @@
// Copyright 2020 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 rawdb
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)
// ReadFinalizedBlockHash retrieves the hash of the last finalized block.
func ReadFinalizedBlockHash(db ethdb.KeyValueReader) common.Hash {
data, _ := db.Get(finalizedBlockKey)
if len(data) != common.HashLength {
return common.Hash{}
}
return common.BytesToHash(data)
}
// WriteFinalizedBlockHash stores the hash of the last finalized block.
func WriteFinalizedBlockHash(db ethdb.KeyValueWriter, h common.Hash) error {
if err := db.Put(finalizedBlockKey, h[:]); err != nil {
log.Crit("Failed to store the finalized block hash", "err", err)
return err
}
return nil
}

View file

@ -53,6 +53,9 @@ var (
// fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync.
fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") fastTxLookupLimitKey = []byte("FastTransactionLookupLimit")
// finalizedBlockKey tracks the hash of the last finalized block.
finalizedBlockKey = []byte("FinalizedBlock")
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td