Skip to content

Commit

Permalink
core, eth: only save 10 bad blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
rjl493456442 committed Dec 16, 2020
1 parent eb1f3c7 commit c165a9d
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 104 deletions.
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()
}
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.
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)

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")

// 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

0 comments on commit c165a9d

Please sign in to comment.