Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: persist bad blocks #21827

Merged
merged 9 commits into from
Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
core, eth: only save 10 bad blocks
  • Loading branch information
rjl493456442 committed Dec 16, 2020
commit c165a9d09554ccb4d971aff3eaaf18b30baf5cd1
57 changes: 1 addition & 56 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const (
txLookupCacheLimit = 1024
maxFutureBlocks = 256
maxTimeFutureBlocks = 30
badBlockLimit = 10
TriesInMemory = 128

// BlockChainVersion ensures that an incompatible database forces a resync from scratch.
Expand Down Expand Up @@ -208,7 +207,6 @@ type BlockChain struct {
processor Processor // Block transaction processor interface
vmConfig vm.Config

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.
writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format.
Expand All @@ -227,7 +225,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
blockCache, _ := lru.New(blockCacheLimit)
txLookupCache, _ := lru.New(txLookupCacheLimit)
futureBlocks, _ := lru.New(maxFutureBlocks)
badBlocks, _ := lru.New(badBlockLimit)

bc := &BlockChain{
chainConfig: chainConfig,
Expand All @@ -249,7 +246,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
futureBlocks: futureBlocks,
engine: engine,
vmConfig: vmConfig,
badBlocks: badBlocks,
}
bc.validator = NewBlockValidator(chainConfig, bc, engine)
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
Expand Down Expand Up @@ -399,8 +395,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit)
}()
}
// Last step, load all persisted bad blocks.
bc.loadBadBlocks()
return bc, nil
}

Expand Down Expand Up @@ -2376,58 +2370,9 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
}
}

// BadBlocksByNumber implements the sort interface to allow sorting a list of
// bad blocks by their number.
type BadBlocksByNumber []*types.Block

func (s BadBlocksByNumber) Len() int { return len(s) }
func (s BadBlocksByNumber) Less(i, j int) bool { return s[i].NumberU64() > s[j].NumberU64() }
func (s BadBlocksByNumber) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// loadBadBlocks loads the persisted bad blocks from the database and
// stores the latest 10 bad blocks in the cache. If the total bad block
// is less than 10, then load all of them.
func (bc *BlockChain) loadBadBlocks() {
blocks, _ := rawdb.ReadAllBadBlocks(bc.db)
if len(blocks) == 0 {
return
}
sort.Sort(BadBlocksByNumber(blocks))
if len(blocks) > badBlockLimit {
blocks = blocks[:badBlockLimit]
}
for _, block := range blocks {
bc.badBlocks.Add(block.Hash(), block)
}
}

// BadBlocks returns a list of the last 'bad blocks' that the client has seen
// on the network. If the all is set then all the bad blocks from the database
// will be returned.
func (bc *BlockChain) BadBlocks(all bool) []*types.Block {
if all {
blocks, _ := rawdb.ReadAllBadBlocks(bc.db)
return blocks
}
blocks := make([]*types.Block, 0, bc.badBlocks.Len())
for _, hash := range bc.badBlocks.Keys() {
if blk, exist := bc.badBlocks.Peek(hash); exist {
block := blk.(*types.Block)
blocks = append(blocks, block)
}
}
return blocks
}

// addBadBlock adds a bad block to the bad-block LRU cache
func (bc *BlockChain) addBadBlock(block *types.Block) {
rawdb.WriteBadBlock(bc.db, block)
bc.badBlocks.Add(block.Hash(), block)
}

// reportBlock logs a bad block error.
func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
bc.addBadBlock(block)
rawdb.WriteBadBlock(bc.db, block)

var receiptString string
for i, receipt := range receipts {
Expand Down
86 changes: 60 additions & 26 deletions core/rawdb/accessors_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"encoding/binary"
"math/big"
"sort"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -702,59 +703,92 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number
DeleteTd(db, hash, number)
}

const badBlockToKeep = 10

type badBlock struct {
Header *types.Header
Body *types.Body
}

// badBlockList implements the sort interface to allow sorting a list of
// bad blocks by their number in the reverse order.
type badBlockList []*badBlock

func (s badBlockList) Len() int { return len(s) }
func (s badBlockList) Less(i, j int) bool {
return s[i].Header.Number.Uint64() > s[j].Header.Number.Uint64()
}
rjl493456442 marked this conversation as resolved.
Show resolved Hide resolved
func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// ReadBadBlock retrieves the bad block with the corresponding block hash.
func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
blob, err := db.Get(badBlockKey(hash))
blob, err := db.Get(badBlockKey)
if err != nil {
return nil
}
var block badBlock
if err := rlp.DecodeBytes(blob, &block); err != nil {
var badBlocks badBlockList
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
return nil
}
return types.NewBlockWithHeader(block.Header).WithBody(block.Body.Transactions, block.Body.Uncles)
for _, bad := range badBlocks {
if bad.Header.Hash() == hash {
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)
}
}
return nil
}

// ReadAllBadBlocks retrieves all the bad blocks in the database
func ReadAllBadBlocks(db ethdb.Database) ([]*types.Block, error) {
iterator := db.NewIterator(badBlockPrefix, nil)
defer iterator.Release()

func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
blob, err := db.Get(badBlockKey)
if err != nil {
return nil
}
var badBlocks badBlockList
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
return nil
}
var blocks []*types.Block
for iterator.Next() {
blob := iterator.Value()
var block badBlock
if err := rlp.DecodeBytes(blob, &block); err != nil {
return nil, nil
}
blocks = append(blocks, types.NewBlockWithHeader(block.Header).WithBody(block.Body.Transactions, block.Body.Uncles))
for _, bad := range badBlocks {
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles))
}
return blocks, nil
return blocks
}

// WriteBadBlock serializes the bad block into the database
func WriteBadBlock(db ethdb.KeyValueWriter, block *types.Block) {
blockRLP, err := rlp.EncodeToBytes(&badBlock{
// WriteBadBlock serializes the bad block into the database. If the cumulated
// bad blocks exceeds the limitation, the oldest will be dropped.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm thinking out loud: the first bad block is usually the most interesting as it contains the chain split. Maybe it's not a great idea to yeet it out

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but usually all the following blocks will be skipped. So we meet this specific bad block over and over again.

func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) {
blob, err := db.Get(badBlockKey)
if err != nil {
log.Warn("Failed to load old bad blocks", "error", err)
}
var badBlocks badBlockList
if len(blob) > 0 {
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
log.Crit("Failed to decode old bad blocks", "error", err)
}
}
badBlocks = append(badBlocks, &badBlock{
Header: block.Header(),
Body: block.Body(),
})
sort.Sort(badBlocks)
if len(badBlocks) > badBlockToKeep {
badBlocks = badBlocks[:badBlockToKeep]
}
data, err := rlp.EncodeToBytes(badBlocks)
if err != nil {
log.Crit("Failed to RLP encode bad block", "err", err)
log.Crit("Failed to encode bad blocks", "err", err)
}
if err := db.Put(badBlockKey(block.Hash()), blockRLP); err != nil {
log.Crit("Failed to store bad block", "err", err)
if err := db.Put(badBlockKey, data); err != nil {
log.Crit("Failed to write bad blocks", "err", err)
}
}

// DeleteBadBlock deletes the specific bad block from the database.
func DeleteBadBlock(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(badBlockKey(hash)); err != nil {
log.Crit("Failed to delete block body", "err", err)
// DeleteBadBlocks deletes all the bad blocks from the database
func DeleteBadBlocks(db ethdb.KeyValueWriter) {
if err := db.Delete(badBlockKey); err != nil {
log.Crit("Failed to delete bad blocks", "err", err)
}
}

Expand Down
23 changes: 19 additions & 4 deletions core/rawdb/accessors_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ func TestBadBlockStorage(t *testing.T) {

// Create a test block to move around the database and make sure it's really new
block := types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(1),
Extra: []byte("bad block"),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
Expand All @@ -209,10 +210,24 @@ func TestBadBlockStorage(t *testing.T) {
} else if entry.Hash() != block.Hash() {
t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
}
// Delete the block and verify the execution
DeleteBadBlock(db, block.Hash())
if entry := ReadBadBlock(db, block.Hash()); entry != nil {
t.Fatalf("Deleted block returned: %v", entry)
// Write one more bad block
blockTwo := types.NewBlockWithHeader(&types.Header{
Number: big.NewInt(2),
Extra: []byte("bad block two"),
UncleHash: types.EmptyUncleHash,
TxHash: types.EmptyRootHash,
ReceiptHash: types.EmptyRootHash,
})
WriteBadBlock(db, blockTwo)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test uniqueness too:

Suggested change
WriteBadBlock(db, blockTwo)
WriteBadBlock(db, blockTwo)
WriteBadBlock(db, blockOne)


badBlocks := ReadAllBadBlocks(db)
if len(badBlocks) != 2 {
t.Fatalf("Failed to load all bad blocks")
}
DeleteBadBlocks(db)
badBlocks = ReadAllBadBlocks(db)
if len(badBlocks) != 0 {
t.Fatalf("Failed to delete bad blocks")
}
}

Expand Down
6 changes: 1 addition & 5 deletions core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ func InspectDatabase(db ethdb.Database) error {
preimages stat
bloomBits stat
cliqueSnaps stat
badBlocks stat

// Ancient store statistics
ancientHeadersSize common.StorageSize
Expand Down Expand Up @@ -354,11 +353,9 @@ func InspectDatabase(db ethdb.Database) error {
chtTrieNodes.Add(size)
case bytes.HasPrefix(key, []byte("blt-")) && len(key) == 4+common.HashLength:
bloomTrieNodes.Add(size)
case bytes.HasPrefix(key, badBlockPrefix) && len(key) == (len(badBlockPrefix)+common.HashLength):
badBlocks.Add(size)
default:
var accounted bool
for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} {
for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey, badBlockKey} {
if bytes.Equal(key, meta) {
metadata.Add(size)
accounted = true
Expand Down Expand Up @@ -404,7 +401,6 @@ func InspectDatabase(db ethdb.Database) error {
{"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()},
{"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()},
{"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()},
{"Key-Value store", "Bad blocks", badBlocks.Size(), badBlocks.Count()},
{"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()},
{"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()},
{"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()},
Expand Down
14 changes: 6 additions & 8 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ var (
// fastTxLookupLimitKey tracks the transaction lookup limit during fast sync.
fastTxLookupLimitKey = []byte("FastTransactionLookupLimit")

// badBlockKey tracks the list of bad blocks seen by local
badBlockKey = []byte("BadBlock")
rjl493456442 marked this conversation as resolved.
Show resolved Hide resolved

// uncleanShutdownKey tracks the list of local crashes
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db

// 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
Expand All @@ -83,9 +89,6 @@ var (

preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
badBlockPrefix = []byte("BadBlock") // badBlockPrefix + hash -> bad block

uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db

// Chain index prefixes (use `i` + single byte to avoid mixing data types).
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
Expand Down Expand Up @@ -191,11 +194,6 @@ func storageSnapshotsKey(accountHash common.Hash) []byte {
return append(SnapshotStoragePrefix, accountHash.Bytes()...)
}

// badBlockKey = badBlockPrefix + block hash
func badBlockKey(hash common.Hash) []byte {
return append(badBlockPrefix, hash.Bytes()...)
}

// bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash
func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte {
key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...)
Expand Down
2 changes: 1 addition & 1 deletion eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ type BadBlockArgs struct {
func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) {
var (
err error
blocks = api.eth.BlockChain().BadBlocks(false) // Load last 10
blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb)
results = make([]*BadBlockArgs, 0, len(blocks))
)
for _, block := range blocks {
Expand Down
6 changes: 2 additions & 4 deletions eth/api_tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
// EVM against a block pulled from the pool of bad ones and returns them as a JSON
// object.
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
blocks := api.eth.blockchain.BadBlocks(true)
for _, block := range blocks {
for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) {
if block.Hash() == hash {
return api.traceBlock(ctx, block, config)
}
Expand All @@ -428,8 +427,7 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c
// execution of EVM against a block pulled from the pool of bad ones to the
// local file system and returns a list of files to the caller.
func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
blocks := api.eth.blockchain.BadBlocks(true)
for _, block := range blocks {
for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) {
if block.Hash() == hash {
return api.standardTraceBlockToFile(ctx, block, config)
}
Expand Down