diff --git a/core/blockchain.go b/core/blockchain.go index ce3efec6a5..cff2ca417b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -77,6 +77,9 @@ var ( blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) errInsertionInterrupted = errors.New("insertion is interrupted") + + errReorgFinalizedBlock = errors.New("can not reorg finalized block") + errFinalizedBlockMissing = errors.New("finalized block missing") ) const ( @@ -193,6 +196,8 @@ type BlockChain struct { badBlocks *lru.Cache // Bad block cache 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. + + finalizedBlock *types.Block // Last finalized block } // 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 { return nil, ErrNoGenesis } + bc.finalizedBlock = bc.GetBlockByHash(rawdb.ReadFinalizedBlockHash(bc.db)) + if bc.finalizedBlock == nil { + bc.finalizedBlock = bc.genesisBlock + } var nilBlock *types.Block bc.currentBlock.Store(nilBlock) @@ -404,6 +413,17 @@ func (bc *BlockChain) loadLastState() error { 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 currentFastBlock := bc.CurrentFastBlock() @@ -428,6 +448,11 @@ func (bc *BlockChain) SetHead(head uint64) error { bc.chainmu.Lock() 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) { // 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() { @@ -581,7 +606,7 @@ func (bc *BlockChain) StateCache() state.Database { // Reset purges the entire blockchain, restoring it to its genesis state. func (bc *BlockChain) Reset() error { - return bc.ResetWithGenesisBlock(bc.genesisBlock) + return bc.ResetWithFinalizedBlock() } // ResetWithGenesisBlock purges the entire blockchain, restoring it to the @@ -614,6 +639,37 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { 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 // 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. @@ -632,6 +688,9 @@ func (bc *BlockChain) repair(head **types.Block) error { if block == nil { return fmt.Errorf("missing block %d [%x]", (*head).NumberU64()-1, (*head).ParentHash()) } + if block.Hash() == bc.finalizedBlock.Hash() { + return errReorgFinalizedBlock + } *head = block } } @@ -2096,6 +2155,12 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { commonBlock = oldBlock 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 oldChain = append(oldChain, oldBlock) deletedTxs = append(deletedTxs, oldBlock.Transactions()...) @@ -2323,6 +2388,15 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i 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 bc.chainmu.Lock() 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 { 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 +} diff --git a/core/rawdb/accessors_finalized.go b/core/rawdb/accessors_finalized.go new file mode 100644 index 0000000000..ee701658df --- /dev/null +++ b/core/rawdb/accessors_finalized.go @@ -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 . + +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 +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 5a41199a7c..9496ff5b9a 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -53,6 +53,9 @@ var ( // fastTxLookupLimitKey tracks the transaction lookup limit during fast sync. 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). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td