-
Notifications
You must be signed in to change notification settings - Fork 20.5k
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
core: persist bad blocks #21827
Changes from 7 commits
e928ff0
cc44fcb
9f2c6f7
d80ed4f
eb1f3c7
c165a9d
06130b0
a3f247e
7b67be6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,6 +703,95 @@ 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) | ||
if err != nil { | ||
return nil | ||
} | ||
var badBlocks badBlockList | ||
if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { | ||
return nil | ||
} | ||
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.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 _, bad := range badBlocks { | ||
blocks = append(blocks, types.NewBlockWithHeader(bad.Header).WithBody(bad.Body.Transactions, bad.Body.Uncles)) | ||
} | ||
return blocks | ||
} | ||
|
||
// 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.Reverse(badBlocks) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, wait, is there anything here which makes the blocks unique? It may well happen that the blockchain tells the db to save the same exact bad block several times, so we shouldn't store a list with ten instances of the same block. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for pointing it out. I have fixed it(also another sort issue). Please take another look. |
||
if len(badBlocks) > badBlockToKeep { | ||
badBlocks = badBlocks[:badBlockToKeep] | ||
} | ||
data, err := rlp.EncodeToBytes(badBlocks) | ||
if err != nil { | ||
log.Crit("Failed to encode bad blocks", "err", err) | ||
} | ||
if err := db.Put(badBlockKey, data); err != nil { | ||
log.Crit("Failed to write bad blocks", "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) | ||
} | ||
} | ||
|
||
// 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; { | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -188,6 +188,49 @@ 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{ | ||||||||
Number: big.NewInt(1), | ||||||||
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) | ||||||||
} | ||||||||
// 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) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test uniqueness too:
Suggested change
|
||||||||
|
||||||||
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") | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
// Tests block total difficulty storage and retrieval operations. | ||||||||
func TestTdStorage(t *testing.T) { | ||||||||
db := NewMemoryDatabase() | ||||||||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.