From e928ff052df8477c49dc271df8a9d316765b7e33 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 11 Nov 2020 21:50:21 +0800 Subject: [PATCH 1/9] core: persist bad blocks --- core/blockchain.go | 12 ++----- core/rawdb/accessors_chain.go | 55 ++++++++++++++++++++++++++++++ core/rawdb/accessors_chain_test.go | 28 +++++++++++++++ core/rawdb/schema.go | 6 ++++ 4 files changed, 91 insertions(+), 10 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index d9505dcf69a1..1d0cc2d9dde4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -208,7 +208,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. @@ -249,7 +248,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) @@ -2376,19 +2374,13 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { // BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network func (bc *BlockChain) BadBlocks() []*types.Block { - 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) - } - } + blocks, _ := rawdb.ReadAllBadBlocks(bc.db) return blocks } // addBadBlock adds a bad block to the bad-block LRU cache func (bc *BlockChain) addBadBlock(block *types.Block) { - bc.badBlocks.Add(block.Hash(), block) + rawdb.WriteBadBlock(bc.db, block) } // reportBlock logs a bad block error. diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index c948cdc7c60e..b41397ec6706 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -702,6 +702,61 @@ func DeleteBlockWithoutNumber(db ethdb.KeyValueWriter, hash common.Hash, number DeleteTd(db, hash, number) } +type badBlock struct { + Header *types.Header + Body *types.Body +} + +// 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)) + if err != nil { + return nil + } + var block badBlock + if err := rlp.DecodeBytes(blob, &block); err != nil { + return nil + } + return types.NewBlockWithHeader(block.Header).WithBody(block.Body.Transactions, block.Body.Uncles) +} + +// ReadAllBadBlocks retrieves all the bad blocks in the database +func ReadAllBadBlocks(db ethdb.Database) ([]*types.Block, error) { + var blocks []*types.Block + iterator := db.NewIterator(badBlockPrefix, nil) + 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)) + } + iterator.Release() + return blocks, nil +} + +// WriteBadBlock serializes the bad block into the database +func WriteBadBlock(db ethdb.KeyValueWriter, block *types.Block) { + blockRLP, err := rlp.EncodeToBytes(&badBlock{ + Header: block.Header(), + Body: block.Body(), + }) + if err != nil { + log.Crit("Failed to RLP encode bad block", "err", err) + } + if err := db.Put(badBlockKey(block.Hash()), blockRLP); err != nil { + log.Crit("Failed to store bad block", "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) + } +} + // FindCommonAncestor returns the last common ancestor of two block headers func FindCommonAncestor(db ethdb.Reader, a, b *types.Header) *types.Header { for bn := b.Number.Uint64(); a.Number.Uint64() > bn; { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 074c24d8fec7..bc6b9e403d61 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -188,6 +188,34 @@ func TestPartialBlockStorage(t *testing.T) { } } +// Tests block storage and retrieval operations. +func TestBadBlockStorage(t *testing.T) { + db := NewMemoryDatabase() + + // Create a test block to move around the database and make sure it's really new + block := types.NewBlockWithHeader(&types.Header{ + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + if entry := ReadBadBlock(db, block.Hash()); entry != nil { + t.Fatalf("Non existent block returned: %v", entry) + } + // Write and verify the block in the database + WriteBadBlock(db, block) + if entry := ReadBadBlock(db, block.Hash()); entry == nil { + t.Fatalf("Stored block not found") + } 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) + } +} + // Tests block total difficulty storage and retrieval operations. func TestTdStorage(t *testing.T) { db := NewMemoryDatabase() diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 2aabfd3baa53..a139e1fb62d6 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -83,6 +83,7 @@ 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 @@ -190,6 +191,11 @@ 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()...) From cc44fcb5ef558fd4e89c72e1ca527dc15834a40c Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 18 Nov 2020 14:23:32 +0800 Subject: [PATCH 2/9] core, eth, internal: address comments --- core/blockchain.go | 2 -- eth/api.go | 57 ++++++++++++++++++++++++++++++------- internal/web3ext/web3ext.go | 3 +- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 1d0cc2d9dde4..95656279b1b7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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. @@ -226,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, diff --git a/eth/api.go b/eth/api.go index fd3565647647..af47bd4d1d94 100644 --- a/eth/api.go +++ b/eth/api.go @@ -328,25 +328,60 @@ type BadBlockArgs struct { RLP string `json:"rlp"` } +// BadBlockCriteria represents a request to retrieve the bad blocks. +type BadBlockCriteria struct { + From *int `json:"from"` + To *int `json:"to"` + Targets []common.Hash `json:"targets"` +} + // GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network // and returns them as a JSON list of block-hashes -func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { - blocks := api.eth.BlockChain().BadBlocks() - results := make([]*BadBlockArgs, len(blocks)) - - var err error +func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context, criteria *BadBlockCriteria) ([]*BadBlockArgs, error) { + var ( + err error + targets map[common.Hash]struct{} + blocks = api.eth.BlockChain().BadBlocks() + results = make([]*BadBlockArgs, 0, len(blocks)) + ) + if criteria != nil { + targets = make(map[common.Hash]struct{}) + for _, target := range criteria.Targets { + targets[target] = struct{}{} + } + } for i, block := range blocks { - results[i] = &BadBlockArgs{ - Hash: block.Hash(), + // Filter out the blocks if the criteria is specified + if criteria != nil { + if criteria.From != nil && i < *criteria.From { + continue + } + if criteria.To != nil && i >= *criteria.To { + continue + } + if targets != nil { + if _, exist := targets[block.Hash()]; !exist { + continue + } + } } + var ( + blockRlp string + blockJSON map[string]interface{} + ) if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { - results[i].RLP = err.Error() // Hacky, but hey, it works + blockRlp = err.Error() // Hacky, but hey, it works } else { - results[i].RLP = fmt.Sprintf("0x%x", rlpBytes) + blockRlp = fmt.Sprintf("0x%x", rlpBytes) } - if results[i].Block, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { - results[i].Block = map[string]interface{}{"error": err.Error()} + if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true); err != nil { + blockJSON = map[string]interface{}{"error": err.Error()} } + results = append(results, &BadBlockArgs{ + Hash: block.Hash(), + RLP: blockRlp, + Block: blockJSON, + }) } return results, nil } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 77954bbbf001..c76f1347b74f 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -446,7 +446,8 @@ web3._extend({ new web3._extend.Method({ name: 'getBadBlocks', call: 'debug_getBadBlocks', - params: 0, + params: 1, + inputFormatter: [null] }), new web3._extend.Method({ name: 'storageRangeAt', From 9f2c6f7caeb833d0d6f29fb0bea7fe3daa2b9b5f Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 18 Nov 2020 14:35:41 +0800 Subject: [PATCH 3/9] core/rawdb: add badblocks to inspector --- core/rawdb/database.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b01a31ebcdd5..0153aeb6f4c2 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -294,6 +294,7 @@ func InspectDatabase(db ethdb.Database) error { preimages stat bloomBits stat cliqueSnaps stat + badBlocks stat // Ancient store statistics ancientHeadersSize common.StorageSize @@ -353,6 +354,8 @@ 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} { @@ -401,6 +404,7 @@ 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()}, From d80ed4fc2dbc6be9aa7b0cc44db9116bc3d75a8e Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 11 Dec 2020 13:43:00 +0800 Subject: [PATCH 4/9] core, eth: update --- core/blockchain.go | 48 +++++++++++++++++++++++++++++++++-- core/rawdb/accessors_chain.go | 5 ++-- eth/api.go | 34 +++---------------------- eth/api_tracer.go | 4 +-- 4 files changed, 54 insertions(+), 37 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 95656279b1b7..5ed6601565a6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -89,6 +89,7 @@ const ( txLookupCacheLimit = 1024 maxFutureBlocks = 256 maxTimeFutureBlocks = 30 + badBlockLimit = 10 TriesInMemory = 128 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. @@ -207,6 +208,7 @@ 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. @@ -225,6 +227,7 @@ 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, @@ -246,6 +249,7 @@ 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) @@ -395,6 +399,8 @@ 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 } @@ -2370,15 +2376,53 @@ func (bc *BlockChain) maintainTxIndex(ancients uint64) { } } -// BadBlocks returns a list of the last 'bad blocks' that the client has seen on the network -func (bc *BlockChain) BadBlocks() []*types.Block { +// 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. diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index b41397ec6706..8fabcfe04ad7 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -722,8 +722,10 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { // ReadAllBadBlocks retrieves all the bad blocks in the database func ReadAllBadBlocks(db ethdb.Database) ([]*types.Block, error) { - var blocks []*types.Block iterator := db.NewIterator(badBlockPrefix, nil) + defer iterator.Release() + + var blocks []*types.Block for iterator.Next() { blob := iterator.Value() var block badBlock @@ -732,7 +734,6 @@ func ReadAllBadBlocks(db ethdb.Database) ([]*types.Block, error) { } blocks = append(blocks, types.NewBlockWithHeader(block.Header).WithBody(block.Body.Transactions, block.Body.Uncles)) } - iterator.Release() return blocks, nil } diff --git a/eth/api.go b/eth/api.go index af47bd4d1d94..33500b04c8ed 100644 --- a/eth/api.go +++ b/eth/api.go @@ -328,43 +328,15 @@ type BadBlockArgs struct { RLP string `json:"rlp"` } -// BadBlockCriteria represents a request to retrieve the bad blocks. -type BadBlockCriteria struct { - From *int `json:"from"` - To *int `json:"to"` - Targets []common.Hash `json:"targets"` -} - // GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network // and returns them as a JSON list of block-hashes -func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context, criteria *BadBlockCriteria) ([]*BadBlockArgs, error) { +func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { var ( err error - targets map[common.Hash]struct{} - blocks = api.eth.BlockChain().BadBlocks() + blocks = api.eth.BlockChain().BadBlocks(false) // Load last 10 results = make([]*BadBlockArgs, 0, len(blocks)) ) - if criteria != nil { - targets = make(map[common.Hash]struct{}) - for _, target := range criteria.Targets { - targets[target] = struct{}{} - } - } - for i, block := range blocks { - // Filter out the blocks if the criteria is specified - if criteria != nil { - if criteria.From != nil && i < *criteria.From { - continue - } - if criteria.To != nil && i >= *criteria.To { - continue - } - if targets != nil { - if _, exist := targets[block.Hash()]; !exist { - continue - } - } - } + for _, block := range blocks { var ( blockRlp string blockJSON map[string]interface{} diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 2497c8d95ea0..25ce6509e5c9 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -404,7 +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() + blocks := api.eth.blockchain.BadBlocks(true) for _, block := range blocks { if block.Hash() == hash { return api.traceBlock(ctx, block, config) @@ -428,7 +428,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() + blocks := api.eth.blockchain.BadBlocks(true) for _, block := range blocks { if block.Hash() == hash { return api.standardTraceBlockToFile(ctx, block, config) From eb1f3c7d816a2d26a1e1d147c961ba76eee0a151 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 11 Dec 2020 13:46:51 +0800 Subject: [PATCH 5/9] internal: revert --- internal/web3ext/web3ext.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index c76f1347b74f..77954bbbf001 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -446,8 +446,7 @@ web3._extend({ new web3._extend.Method({ name: 'getBadBlocks', call: 'debug_getBadBlocks', - params: 1, - inputFormatter: [null] + params: 0, }), new web3._extend.Method({ name: 'storageRangeAt', From c165a9d09554ccb4d971aff3eaaf18b30baf5cd1 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 16 Dec 2020 19:04:34 +0800 Subject: [PATCH 6/9] core, eth: only save 10 bad blocks --- core/blockchain.go | 57 +------------------- core/rawdb/accessors_chain.go | 86 +++++++++++++++++++++--------- core/rawdb/accessors_chain_test.go | 23 ++++++-- core/rawdb/database.go | 6 +-- core/rawdb/schema.go | 14 +++-- eth/api.go | 2 +- eth/api_tracer.go | 6 +-- 7 files changed, 90 insertions(+), 104 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 5ed6601565a6..b8f483b85eb6 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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. @@ -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. @@ -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, @@ -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) @@ -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 } @@ -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 { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 8fabcfe04ad7..181f2ebc4152 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "math/big" + "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -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) } } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index bc6b9e403d61..14788b523da2 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -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, @@ -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") } } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 0153aeb6f4c2..b15230f51c88 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -294,7 +294,6 @@ func InspectDatabase(db ethdb.Database) error { preimages stat bloomBits stat cliqueSnaps stat - badBlocks stat // Ancient store statistics ancientHeadersSize common.StorageSize @@ -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 @@ -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()}, diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index a139e1fb62d6..dbb337998744 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -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 @@ -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 @@ -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()...) diff --git a/eth/api.go b/eth/api.go index 33500b04c8ed..6cd0fd7005b5 100644 --- a/eth/api.go +++ b/eth/api.go @@ -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 { diff --git a/eth/api_tracer.go b/eth/api_tracer.go index 25ce6509e5c9..de2ca579f5f5 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -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) } @@ -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) } From 06130b03831ea7246fe2037b8ac366f7d4ef8589 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 8 Jan 2021 10:26:18 +0800 Subject: [PATCH 7/9] core/rawdb: address comments --- core/rawdb/accessors_chain.go | 4 ++-- core/rawdb/schema.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 181f2ebc4152..a000d7c5c4ec 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -716,7 +716,7 @@ 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() + 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] } @@ -772,7 +772,7 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { Header: block.Header(), Body: block.Body(), }) - sort.Sort(badBlocks) + sort.Reverse(badBlocks) if len(badBlocks) > badBlockToKeep { badBlocks = badBlocks[:badBlockToKeep] } diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dbb337998744..9749a30d8a3f 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -67,7 +67,7 @@ var ( fastTxLookupLimitKey = []byte("FastTransactionLookupLimit") // badBlockKey tracks the list of bad blocks seen by local - badBlockKey = []byte("BadBlock") + badBlockKey = []byte("InvalidBlock") // uncleanShutdownKey tracks the list of local crashes uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db From a3f247e550f12506be635e98df96f4389a09cdd0 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Fri, 8 Jan 2021 10:32:54 +0800 Subject: [PATCH 8/9] core/rawdb: fix --- core/rawdb/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b15230f51c88..a2cc9c7be91e 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -355,7 +355,7 @@ func InspectDatabase(db ethdb.Database) error { bloomTrieNodes.Add(size) default: var accounted bool - for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey, badBlockKey} { + for _, meta := range [][]byte{databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, fastTrieProgressKey, uncleanShutdownKey, badBlockKey} { if bytes.Equal(key, meta) { metadata.Add(size) accounted = true From 7b67be62a0366685f7a453c2d1e83cc3334fc860 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Sun, 10 Jan 2021 16:05:49 +0800 Subject: [PATCH 9/9] core: address comments --- core/rawdb/accessors_chain.go | 11 +++++++++-- core/rawdb/accessors_chain_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index a000d7c5c4ec..461e1cbb17f6 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -738,7 +738,8 @@ func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { return nil } -// ReadAllBadBlocks retrieves all the bad blocks in the database +// ReadAllBadBlocks retrieves all the bad blocks in the database. +// All returned blocks are sorted in reverse order by number. func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { blob, err := db.Get(badBlockKey) if err != nil { @@ -768,11 +769,17 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { log.Crit("Failed to decode old bad blocks", "error", err) } } + for _, b := range badBlocks { + if b.Header.Number.Uint64() == block.NumberU64() && b.Header.Hash() == block.Hash() { + log.Info("Skip duplicated bad block", "number", block.NumberU64(), "hash", block.Hash()) + return + } + } badBlocks = append(badBlocks, &badBlock{ Header: block.Header(), Body: block.Body(), }) - sort.Reverse(badBlocks) + sort.Sort(sort.Reverse(badBlocks)) if len(badBlocks) > badBlockToKeep { badBlocks = badBlocks[:badBlockToKeep] } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 14788b523da2..a5804cd30945 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -22,6 +22,7 @@ import ( "fmt" "io/ioutil" "math/big" + "math/rand" "os" "reflect" "testing" @@ -220,10 +221,36 @@ func TestBadBlockStorage(t *testing.T) { }) WriteBadBlock(db, blockTwo) + // Write the block one again, should be filtered out. + WriteBadBlock(db, block) badBlocks := ReadAllBadBlocks(db) if len(badBlocks) != 2 { t.Fatalf("Failed to load all bad blocks") } + + // Write a bunch of bad blocks, all the blocks are should sorted + // in reverse order. The extra blocks should be truncated. + for _, n := range rand.Perm(100) { + block := types.NewBlockWithHeader(&types.Header{ + Number: big.NewInt(int64(n)), + Extra: []byte("bad block"), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + }) + WriteBadBlock(db, block) + } + badBlocks = ReadAllBadBlocks(db) + if len(badBlocks) != badBlockToKeep { + t.Fatalf("The number of persised bad blocks in incorrect %d", len(badBlocks)) + } + for i := 0; i < len(badBlocks)-1; i++ { + if badBlocks[i].NumberU64() < badBlocks[i+1].NumberU64() { + t.Fatalf("The bad blocks are not sorted #[%d](%d) < #[%d](%d)", i, i+1, badBlocks[i].NumberU64(), badBlocks[i+1].NumberU64()) + } + } + + // Delete all bad blocks DeleteBadBlocks(db) badBlocks = ReadAllBadBlocks(db) if len(badBlocks) != 0 {