Skip to content

Commit 5a1b384

Browse files
authored
core: persist bad blocks (#21827)
* core: persist bad blocks * core, eth, internal: address comments * core/rawdb: add badblocks to inspector * core, eth: update * internal: revert * core, eth: only save 10 bad blocks * core/rawdb: address comments * core/rawdb: fix * core: address comments
1 parent 89030ec commit 5a1b384

File tree

7 files changed

+196
-41
lines changed

7 files changed

+196
-41
lines changed

core/blockchain.go

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ const (
8989
txLookupCacheLimit = 1024
9090
maxFutureBlocks = 256
9191
maxTimeFutureBlocks = 30
92-
badBlockLimit = 10
9392
TriesInMemory = 128
9493

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

211-
badBlocks *lru.Cache // Bad block cache
212210
shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
213211
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.
214212
writeLegacyJournal bool // Testing flag used to flush the snapshot journal in legacy format.
@@ -227,7 +225,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
227225
blockCache, _ := lru.New(blockCacheLimit)
228226
txLookupCache, _ := lru.New(txLookupCacheLimit)
229227
futureBlocks, _ := lru.New(maxFutureBlocks)
230-
badBlocks, _ := lru.New(badBlockLimit)
231228

232229
bc := &BlockChain{
233230
chainConfig: chainConfig,
@@ -249,7 +246,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
249246
futureBlocks: futureBlocks,
250247
engine: engine,
251248
vmConfig: vmConfig,
252-
badBlocks: badBlocks,
253249
}
254250
bc.validator = NewBlockValidator(chainConfig, bc, engine)
255251
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@@ -2374,26 +2370,9 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) {
23742370
}
23752371
}
23762372

2377-
// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
2378-
func (bc *BlockChain) BadBlocks() []*types.Block {
2379-
blocks := make([]*types.Block, 0, bc.badBlocks.Len())
2380-
for _, hash := range bc.badBlocks.Keys() {
2381-
if blk, exist := bc.badBlocks.Peek(hash); exist {
2382-
block := blk.(*types.Block)
2383-
blocks = append(blocks, block)
2384-
}
2385-
}
2386-
return blocks
2387-
}
2388-
2389-
// addBadBlock adds a bad block to the bad-block LRU cache
2390-
func (bc *BlockChain) addBadBlock(block *types.Block) {
2391-
bc.badBlocks.Add(block.Hash(), block)
2392-
}
2393-
23942373
// reportBlock logs a bad block error.
23952374
func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
2396-
bc.addBadBlock(block)
2375+
rawdb.WriteBadBlock(bc.db, block)
23972376

23982377
var receiptString string
23992378
for i, receipt := range receipts {

core/rawdb/accessors_chain.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"bytes"
2121
"encoding/binary"
2222
"math/big"
23+
"sort"
2324

2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/core/types"
@@ -702,6 +703,102 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number
702703
DeleteTd(db, hash, number)
703704
}
704705

706+
const badBlockToKeep = 10
707+
708+
type badBlock struct {
709+
Header *types.Header
710+
Body *types.Body
711+
}
712+
713+
// badBlockList implements the sort interface to allow sorting a list of
714+
// bad blocks by their number in the reverse order.
715+
type badBlockList []*badBlock
716+
717+
func (s badBlockList) Len() int { return len(s) }
718+
func (s badBlockList) Less(i, j int) bool {
719+
return s[i].Header.Number.Uint64() < s[j].Header.Number.Uint64()
720+
}
721+
func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
722+
723+
// ReadBadBlock retrieves the bad block with the corresponding block hash.
724+
func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block {
725+
blob, err := db.Get(badBlockKey)
726+
if err != nil {
727+
return nil
728+
}
729+
var badBlocks badBlockList
730+
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
731+
return nil
732+
}
733+
for _, bad := range badBlocks {
734+
if bad.Header.Hash() == hash {
735+
return types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)
736+
}
737+
}
738+
return nil
739+
}
740+
741+
// ReadAllBadBlocks retrieves all the bad blocks in the database.
742+
// All returned blocks are sorted in reverse order by number.
743+
func ReadAllBadBlocks(db ethdb.Reader) []*types.Block {
744+
blob, err := db.Get(badBlockKey)
745+
if err != nil {
746+
return nil
747+
}
748+
var badBlocks badBlockList
749+
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
750+
return nil
751+
}
752+
var blocks []*types.Block
753+
for _, bad := range badBlocks {
754+
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles))
755+
}
756+
return blocks
757+
}
758+
759+
// WriteBadBlock serializes the bad block into the database. If the cumulated
760+
// bad blocks exceeds the limitation, the oldest will be dropped.
761+
func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) {
762+
blob, err := db.Get(badBlockKey)
763+
if err != nil {
764+
log.Warn("Failed to load old bad blocks", "error", err)
765+
}
766+
var badBlocks badBlockList
767+
if len(blob) > 0 {
768+
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil {
769+
log.Crit("Failed to decode old bad blocks", "error", err)
770+
}
771+
}
772+
for _, b := range badBlocks {
773+
if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() {
774+
log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash())
775+
return
776+
}
777+
}
778+
badBlocks = append(badBlocks, &badBlock{
779+
Header: block.Header(),
780+
Body: block.Body(),
781+
})
782+
sort.Sort(sort.Reverse(badBlocks))
783+
if len(badBlocks) > badBlockToKeep {
784+
badBlocks = badBlocks[:badBlockToKeep]
785+
}
786+
data, err := rlp.EncodeToBytes(badBlocks)
787+
if err != nil {
788+
log.Crit("Failed to encode bad blocks", "err", err)
789+
}
790+
if err := db.Put(badBlockKey, data); err != nil {
791+
log.Crit("Failed to write bad blocks", "err", err)
792+
}
793+
}
794+
795+
// DeleteBadBlocks deletes all the bad blocks from the database
796+
func DeleteBadBlocks(db ethdb.KeyValueWriter) {
797+
if err := db.Delete(badBlockKey); err != nil {
798+
log.Crit("Failed to delete bad blocks", "err", err)
799+
}
800+
}
801+
705802
// FindCommonAncestor returns the last common ancestor of two block headers
706803
func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header {
707804
for bn := b.Number.Uint64(); a.Number.Uint64() > bn; {

core/rawdb/accessors_chain_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"io/ioutil"
2424
"math/big"
25+
"math/rand"
2526
"os"
2627
"reflect"
2728
"testing"
@@ -188,6 +189,75 @@ func TestPartialBlockStorage(t *testing.T) {
188189
}
189190
}
190191

192+
// Tests block storage and retrieval operations.
193+
func TestBadBlockStorage(t *testing.T) {
194+
db := NewMemoryDatabase()
195+
196+
// Create a test block to move around the database and make sure it's really new
197+
block := types.NewBlockWithHeader(&types.Header{
198+
Number: big.NewInt(1),
199+
Extra: []byte("bad block"),
200+
UncleHash: types.EmptyUncleHash,
201+
TxHash: types.EmptyRootHash,
202+
ReceiptHash: types.EmptyRootHash,
203+
})
204+
if entry := ReadBadBlock(db, block.Hash()); entry != nil {
205+
t.Fatalf("Non existent block returned: %v", entry)
206+
}
207+
// Write and verify the block in the database
208+
WriteBadBlock(db, block)
209+
if entry := ReadBadBlock(db, block.Hash()); entry == nil {
210+
t.Fatalf("Stored block not found")
211+
} else if entry.Hash() != block.Hash() {
212+
t.Fatalf("Retrieved block mismatch: have %v, want %v", entry, block)
213+
}
214+
// Write one more bad block
215+
blockTwo := types.NewBlockWithHeader(&types.Header{
216+
Number: big.NewInt(2),
217+
Extra: []byte("bad block two"),
218+
UncleHash: types.EmptyUncleHash,
219+
TxHash: types.EmptyRootHash,
220+
ReceiptHash: types.EmptyRootHash,
221+
})
222+
WriteBadBlock(db, blockTwo)
223+
224+
// Write the block one again, should be filtered out.
225+
WriteBadBlock(db, block)
226+
badBlocks := ReadAllBadBlocks(db)
227+
if len(badBlocks) != 2 {
228+
t.Fatalf("Failed to load all bad blocks")
229+
}
230+
231+
// Write a bunch of bad blocks, all the blocks are should sorted
232+
// in reverse order. The extra blocks should be truncated.
233+
for _, n := range rand.Perm(100) {
234+
block := types.NewBlockWithHeader(&types.Header{
235+
Number: big.NewInt(int64(n)),
236+
Extra: []byte("bad block"),
237+
UncleHash: types.EmptyUncleHash,
238+
TxHash: types.EmptyRootHash,
239+
ReceiptHash: types.EmptyRootHash,
240+
})
241+
WriteBadBlock(db, block)
242+
}
243+
badBlocks = ReadAllBadBlocks(db)
244+
if len(badBlocks) != badBlockToKeep {
245+
t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks))
246+
}
247+
for i := 0; i < len(badBlocks)-1; i++ {
248+
if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() {
249+
t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64())
250+
}
251+
}
252+
253+
// Delete all bad blocks
254+
DeleteBadBlocks(db)
255+
badBlocks = ReadAllBadBlocks(db)
256+
if len(badBlocks) != 0 {
257+
t.Fatalf("Failed to delete bad blocks")
258+
}
259+
}
260+
191261
// Tests block total difficulty storage and retrieval operations.
192262
func TestTdStorage(t *testing.T) {
193263
db := NewMemoryDatabase()

core/rawdb/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error {
355355
bloomTrieNodes.Add(size)
356356
default:
357357
var accounted bool
358-
for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey} {
358+
for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} {
359359
if bytes.Equal(key, meta) {
360360
metadata.Add(size)
361361
accounted = true

core/rawdb/schema.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ var (
6666
// fastTxLookupLimitKey tracks the transaction lookup limit during fast sync.
6767
fastTxLookupLimitKey = []byte("FastTransactionLookupLimit")
6868

69+
// badBlockKey tracks the list of bad blocks seen by local
70+
badBlockKey = []byte("InvalidBlock")
71+
72+
// uncleanShutdownKey tracks the list of local crashes
73+
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
74+
6975
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
7076
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
7177
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td
@@ -84,8 +90,6 @@ var (
8490
preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage
8591
configPrefix = []byte("ethereum-config-") // config prefix for the db
8692

87-
uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db
88-
8993
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
9094
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
9195

eth/api.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -331,22 +331,29 @@ type BadBlockArgs struct {
331331
// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network
332332
// and returns them as a JSON list of block-hashes
333333
func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) {
334-
blocks := api.eth.BlockChain().BadBlocks()
335-
results := make([]*BadBlockArgs, len(blocks))
336-
337-
var err error
338-
for i, block := range blocks {
339-
results[i] = &BadBlockArgs{
340-
Hash: block.Hash(),
341-
}
334+
var (
335+
err error
336+
blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb)
337+
results = make([]*BadBlockArgs, 0, len(blocks))
338+
)
339+
for _, block := range blocks {
340+
var (
341+
blockRlp string
342+
blockJSON map[string]interface{}
343+
)
342344
if rlpBytes, err := rlp.EncodeToBytes(block); err != nil {
343-
results[i].RLP = err.Error() // Hacky, but hey, it works
345+
blockRlp = err.Error() // Hacky, but hey, it works
344346
} else {
345-
results[i].RLP = fmt.Sprintf("0x%x", rlpBytes)
347+
blockRlp = fmt.Sprintf("0x%x", rlpBytes)
346348
}
347-
if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil {
348-
results[i].Block = map[string]interface{}{"error": err.Error()}
349+
if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true); err != nil {
350+
blockJSON = map[string]interface{}{"error": err.Error()}
349351
}
352+
results = append(results, &BadBlockArgs{
353+
Hash: block.Hash(),
354+
RLP: blockRlp,
355+
Block: blockJSON,
356+
})
350357
}
351358
return results, nil
352359
}

eth/api_tracer.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,7 @@ func (api *PrivateDebugAPI) TraceBlockFromFile(ctx context.Context, file string,
404404
// EVM against a block pulled from the pool of bad ones and returns them as a JSON
405405
// object.
406406
func (api *PrivateDebugAPI) TraceBadBlock(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) {
407-
blocks := api.eth.blockchain.BadBlocks()
408-
for _, block := range blocks {
407+
for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) {
409408
if block.Hash() == hash {
410409
return api.traceBlock(ctx, block, config)
411410
}
@@ -428,8 +427,7 @@ func (api *PrivateDebugAPI) StandardTraceBlockToFile(ctx context.Context, hash c
428427
// execution of EVM against a block pulled from the pool of bad ones to the
429428
// local file system and returns a list of files to the caller.
430429
func (api *PrivateDebugAPI) StandardTraceBadBlockToFile(ctx context.Context, hash common.Hash, config *StdTraceConfig) ([]string, error) {
431-
blocks := api.eth.blockchain.BadBlocks()
432-
for _, block := range blocks {
430+
for _, block := range rawdb.ReadAllBadBlocks(api.eth.chainDb) {
433431
if block.Hash() == hash {
434432
return api.standardTraceBlockToFile(ctx, block, config)
435433
}

0 commit comments

Comments
 (0)