|
|
@ -64,44 +64,58 @@ func theBlockChain(db ethdb.Database, t *testing.T) *BlockChain { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Test fork of length N starting from block i
|
|
|
|
// Test fork of length N starting from block i
|
|
|
|
func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big.Int)) { |
|
|
|
func testFork(t *testing.T, processor *BlockProcessor, i, n int, full bool, comparator func(td1, td2 *big.Int)) { |
|
|
|
// switch databases to process the new chain
|
|
|
|
// Copy old chain up to #i into a new db
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
db, processor2, err := newCanonical(i, full) |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// copy old chain up to i into new db with deterministic canonical
|
|
|
|
|
|
|
|
bman2, err := newCanonical(i, db) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("could not make new canonical in testFork", err) |
|
|
|
t.Fatal("could not make new canonical in testFork", err) |
|
|
|
} |
|
|
|
} |
|
|
|
// assert the bmans have the same block at i
|
|
|
|
// Assert the chains have the same header/block at #i
|
|
|
|
bi1 := bman.bc.GetBlockByNumber(uint64(i)).Hash() |
|
|
|
var hash1, hash2 common.Hash |
|
|
|
bi2 := bman2.bc.GetBlockByNumber(uint64(i)).Hash() |
|
|
|
if full { |
|
|
|
if bi1 != bi2 { |
|
|
|
hash1 = processor.bc.GetBlockByNumber(uint64(i)).Hash() |
|
|
|
fmt.Printf("%+v\n%+v\n\n", bi1, bi2) |
|
|
|
hash2 = processor2.bc.GetBlockByNumber(uint64(i)).Hash() |
|
|
|
t.Fatal("chains do not have the same hash at height", i) |
|
|
|
} else { |
|
|
|
|
|
|
|
hash1 = processor.bc.GetHeaderByNumber(uint64(i)).Hash() |
|
|
|
|
|
|
|
hash2 = processor2.bc.GetHeaderByNumber(uint64(i)).Hash() |
|
|
|
} |
|
|
|
} |
|
|
|
bman2.bc.SetProcessor(bman2) |
|
|
|
if hash1 != hash2 { |
|
|
|
|
|
|
|
t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) |
|
|
|
// extend the fork
|
|
|
|
|
|
|
|
parent := bman2.bc.CurrentBlock() |
|
|
|
|
|
|
|
chainB := makeChain(parent, N, db, forkSeed) |
|
|
|
|
|
|
|
_, err = bman2.bc.InsertChain(chainB) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Fatal("Insert chain error for fork:", err) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Extend the newly created chain
|
|
|
|
tdpre := bman.bc.Td() |
|
|
|
var ( |
|
|
|
// Test the fork's blocks on the original chain
|
|
|
|
blockChainB []*types.Block |
|
|
|
td, err := testChain(chainB, bman) |
|
|
|
headerChainB []*types.Header |
|
|
|
if err != nil { |
|
|
|
) |
|
|
|
t.Fatal("expected chainB not to give errors:", err) |
|
|
|
if full { |
|
|
|
|
|
|
|
blockChainB = makeBlockChain(processor2.bc.CurrentBlock(), n, db, forkSeed) |
|
|
|
|
|
|
|
if _, err := processor2.bc.InsertChain(blockChainB); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to insert forking chain: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
headerChainB = makeHeaderChain(processor2.bc.CurrentHeader(), n, db, forkSeed) |
|
|
|
|
|
|
|
if _, err := processor2.bc.InsertHeaderChain(headerChainB, true); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to insert forking chain: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Compare difficulties
|
|
|
|
// Sanity check that the forked chain can be imported into the original
|
|
|
|
f(tdpre, td) |
|
|
|
var tdPre, tdPost *big.Int |
|
|
|
|
|
|
|
|
|
|
|
// Loop over parents making sure reconstruction is done properly
|
|
|
|
if full { |
|
|
|
|
|
|
|
tdPre = processor.bc.GetTd(processor.bc.CurrentBlock().Hash()) |
|
|
|
|
|
|
|
if err := testBlockChainImport(blockChainB, processor); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to import forked block chain: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
tdPost = processor.bc.GetTd(blockChainB[len(blockChainB)-1].Hash()) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
tdPre = processor.bc.GetTd(processor.bc.CurrentHeader().Hash()) |
|
|
|
|
|
|
|
if err := testHeaderChainImport(headerChainB, processor); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to import forked header chain: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
tdPost = processor.bc.GetTd(headerChainB[len(headerChainB)-1].Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Compare the total difficulties of the chains
|
|
|
|
|
|
|
|
comparator(tdPre, tdPost) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func printChain(bc *BlockChain) { |
|
|
|
func printChain(bc *BlockChain) { |
|
|
@ -111,22 +125,41 @@ func printChain(bc *BlockChain) { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// process blocks against a chain
|
|
|
|
// testBlockChainImport tries to process a chain of blocks, writing them into
|
|
|
|
func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { |
|
|
|
// the database if successful.
|
|
|
|
for _, block := range chainB { |
|
|
|
func testBlockChainImport(chain []*types.Block, processor *BlockProcessor) error { |
|
|
|
_, _, err := bman.bc.processor.Process(block) |
|
|
|
for _, block := range chain { |
|
|
|
if err != nil { |
|
|
|
// Try and process the block
|
|
|
|
|
|
|
|
if _, _, err := processor.Process(block); err != nil { |
|
|
|
if IsKnownBlockErr(err) { |
|
|
|
if IsKnownBlockErr(err) { |
|
|
|
continue |
|
|
|
continue |
|
|
|
} |
|
|
|
} |
|
|
|
return nil, err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
bman.bc.mu.Lock() |
|
|
|
// Manually insert the block into the database, but don't reorganize (allows subsequent testing)
|
|
|
|
WriteTd(bman.bc.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), bman.bc.GetTd(block.ParentHash()))) |
|
|
|
processor.bc.mu.Lock() |
|
|
|
WriteBlock(bman.bc.chainDb, block) |
|
|
|
WriteTd(processor.chainDb, block.Hash(), new(big.Int).Add(block.Difficulty(), processor.bc.GetTd(block.ParentHash()))) |
|
|
|
bman.bc.mu.Unlock() |
|
|
|
WriteBlock(processor.chainDb, block) |
|
|
|
|
|
|
|
processor.bc.mu.Unlock() |
|
|
|
} |
|
|
|
} |
|
|
|
return bman.bc.GetTd(chainB[len(chainB)-1].Hash()), nil |
|
|
|
return nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// testHeaderChainImport tries to process a chain of header, writing them into
|
|
|
|
|
|
|
|
// the database if successful.
|
|
|
|
|
|
|
|
func testHeaderChainImport(chain []*types.Header, processor *BlockProcessor) error { |
|
|
|
|
|
|
|
for _, header := range chain { |
|
|
|
|
|
|
|
// Try and validate the header
|
|
|
|
|
|
|
|
if err := processor.ValidateHeader(header, false, false); err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Manually insert the header into the database, but don't reorganize (allows subsequent testing)
|
|
|
|
|
|
|
|
processor.bc.mu.Lock() |
|
|
|
|
|
|
|
WriteTd(processor.chainDb, header.Hash(), new(big.Int).Add(header.Difficulty, processor.bc.GetTd(header.ParentHash))) |
|
|
|
|
|
|
|
WriteHeader(processor.chainDb, header) |
|
|
|
|
|
|
|
processor.bc.mu.Unlock() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func loadChain(fn string, t *testing.T) (types.Blocks, error) { |
|
|
|
func loadChain(fn string, t *testing.T) (types.Blocks, error) { |
|
|
@ -154,139 +187,147 @@ func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t * |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestLastBlock(t *testing.T) { |
|
|
|
func TestLastBlock(t *testing.T) { |
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bchain := theBlockChain(db, t) |
|
|
|
bchain := theBlockChain(db, t) |
|
|
|
block := makeChain(bchain.CurrentBlock(), 1, db, 0)[0] |
|
|
|
block := makeBlockChain(bchain.CurrentBlock(), 1, db, 0)[0] |
|
|
|
bchain.insert(block) |
|
|
|
bchain.insert(block) |
|
|
|
if block.Hash() != GetHeadBlockHash(db) { |
|
|
|
if block.Hash() != GetHeadBlockHash(db) { |
|
|
|
t.Errorf("Write/Get HeadBlockHash failed") |
|
|
|
t.Errorf("Write/Get HeadBlockHash failed") |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestExtendCanonical(t *testing.T) { |
|
|
|
// Tests that given a starting canonical chain of a given size, it can be extended
|
|
|
|
CanonicalLength := 5 |
|
|
|
// with various length chains.
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) } |
|
|
|
if err != nil { |
|
|
|
func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) } |
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
|
|
|
|
} |
|
|
|
func testExtendCanonical(t *testing.T, full bool) { |
|
|
|
// make first chain starting from genesis
|
|
|
|
length := 5 |
|
|
|
bman, err := newCanonical(CanonicalLength, db) |
|
|
|
|
|
|
|
|
|
|
|
// Make first chain starting from genesis
|
|
|
|
|
|
|
|
_, processor, err := newCanonical(length, full) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
t.Fatalf("failed to make new canonical chain: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
f := func(td1, td2 *big.Int) { |
|
|
|
// Define the difficulty comparator
|
|
|
|
|
|
|
|
better := func(td1, td2 *big.Int) { |
|
|
|
if td2.Cmp(td1) <= 0 { |
|
|
|
if td2.Cmp(td1) <= 0 { |
|
|
|
t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) |
|
|
|
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Start fork from current height (CanonicalLength)
|
|
|
|
// Start fork from current height
|
|
|
|
testFork(t, bman, CanonicalLength, 1, f) |
|
|
|
testFork(t, processor, length, 1, full, better) |
|
|
|
testFork(t, bman, CanonicalLength, 2, f) |
|
|
|
testFork(t, processor, length, 2, full, better) |
|
|
|
testFork(t, bman, CanonicalLength, 5, f) |
|
|
|
testFork(t, processor, length, 5, full, better) |
|
|
|
testFork(t, bman, CanonicalLength, 10, f) |
|
|
|
testFork(t, processor, length, 10, full, better) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestShorterFork(t *testing.T) { |
|
|
|
// Tests that given a starting canonical chain of a given size, creating shorter
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
// forks do not take canonical ownership.
|
|
|
|
if err != nil { |
|
|
|
func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) } |
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) } |
|
|
|
} |
|
|
|
|
|
|
|
// make first chain starting from genesis
|
|
|
|
func testShorterFork(t *testing.T, full bool) { |
|
|
|
bman, err := newCanonical(10, db) |
|
|
|
length := 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Make first chain starting from genesis
|
|
|
|
|
|
|
|
_, processor, err := newCanonical(length, full) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
t.Fatalf("failed to make new canonical chain: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
f := func(td1, td2 *big.Int) { |
|
|
|
// Define the difficulty comparator
|
|
|
|
|
|
|
|
worse := func(td1, td2 *big.Int) { |
|
|
|
if td2.Cmp(td1) >= 0 { |
|
|
|
if td2.Cmp(td1) >= 0 { |
|
|
|
t.Error("expected chainB to have lower difficulty. Got", td2, "expected less than", td1) |
|
|
|
t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Sum of numbers must be less than 10
|
|
|
|
// Sum of numbers must be less than `length` for this to be a shorter fork
|
|
|
|
// for this to be a shorter fork
|
|
|
|
testFork(t, processor, 0, 3, full, worse) |
|
|
|
testFork(t, bman, 0, 3, f) |
|
|
|
testFork(t, processor, 0, 7, full, worse) |
|
|
|
testFork(t, bman, 0, 7, f) |
|
|
|
testFork(t, processor, 1, 1, full, worse) |
|
|
|
testFork(t, bman, 1, 1, f) |
|
|
|
testFork(t, processor, 1, 7, full, worse) |
|
|
|
testFork(t, bman, 1, 7, f) |
|
|
|
testFork(t, processor, 5, 3, full, worse) |
|
|
|
testFork(t, bman, 5, 3, f) |
|
|
|
testFork(t, processor, 5, 4, full, worse) |
|
|
|
testFork(t, bman, 5, 4, f) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestLongerFork(t *testing.T) { |
|
|
|
// Tests that given a starting canonical chain of a given size, creating longer
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
// forks do take canonical ownership.
|
|
|
|
if err != nil { |
|
|
|
func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) } |
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) } |
|
|
|
} |
|
|
|
|
|
|
|
// make first chain starting from genesis
|
|
|
|
func testLongerFork(t *testing.T, full bool) { |
|
|
|
bman, err := newCanonical(10, db) |
|
|
|
length := 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Make first chain starting from genesis
|
|
|
|
|
|
|
|
_, processor, err := newCanonical(length, full) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
t.Fatalf("failed to make new canonical chain: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
f := func(td1, td2 *big.Int) { |
|
|
|
// Define the difficulty comparator
|
|
|
|
|
|
|
|
better := func(td1, td2 *big.Int) { |
|
|
|
if td2.Cmp(td1) <= 0 { |
|
|
|
if td2.Cmp(td1) <= 0 { |
|
|
|
t.Error("expected chainB to have higher difficulty. Got", td2, "expected more than", td1) |
|
|
|
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Sum of numbers must be greater than 10
|
|
|
|
// Sum of numbers must be greater than `length` for this to be a longer fork
|
|
|
|
// for this to be a longer fork
|
|
|
|
testFork(t, processor, 0, 11, full, better) |
|
|
|
testFork(t, bman, 0, 11, f) |
|
|
|
testFork(t, processor, 0, 15, full, better) |
|
|
|
testFork(t, bman, 0, 15, f) |
|
|
|
testFork(t, processor, 1, 10, full, better) |
|
|
|
testFork(t, bman, 1, 10, f) |
|
|
|
testFork(t, processor, 1, 12, full, better) |
|
|
|
testFork(t, bman, 1, 12, f) |
|
|
|
testFork(t, processor, 5, 6, full, better) |
|
|
|
testFork(t, bman, 5, 6, f) |
|
|
|
testFork(t, processor, 5, 8, full, better) |
|
|
|
testFork(t, bman, 5, 8, f) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestEqualFork(t *testing.T) { |
|
|
|
// Tests that given a starting canonical chain of a given size, creating equal
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
// forks do take canonical ownership.
|
|
|
|
if err != nil { |
|
|
|
func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) } |
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) } |
|
|
|
} |
|
|
|
|
|
|
|
bman, err := newCanonical(10, db) |
|
|
|
func testEqualFork(t *testing.T, full bool) { |
|
|
|
|
|
|
|
length := 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Make first chain starting from genesis
|
|
|
|
|
|
|
|
_, processor, err := newCanonical(length, full) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
t.Fatalf("failed to make new canonical chain: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
f := func(td1, td2 *big.Int) { |
|
|
|
// Define the difficulty comparator
|
|
|
|
|
|
|
|
equal := func(td1, td2 *big.Int) { |
|
|
|
if td2.Cmp(td1) != 0 { |
|
|
|
if td2.Cmp(td1) != 0 { |
|
|
|
t.Error("expected chainB to have equal difficulty. Got", td2, "expected ", td1) |
|
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Sum of numbers must be equal to 10
|
|
|
|
// Sum of numbers must be equal to `length` for this to be an equal fork
|
|
|
|
// for this to be an equal fork
|
|
|
|
testFork(t, processor, 0, 10, full, equal) |
|
|
|
testFork(t, bman, 0, 10, f) |
|
|
|
testFork(t, processor, 1, 9, full, equal) |
|
|
|
testFork(t, bman, 1, 9, f) |
|
|
|
testFork(t, processor, 2, 8, full, equal) |
|
|
|
testFork(t, bman, 2, 8, f) |
|
|
|
testFork(t, processor, 5, 5, full, equal) |
|
|
|
testFork(t, bman, 5, 5, f) |
|
|
|
testFork(t, processor, 6, 4, full, equal) |
|
|
|
testFork(t, bman, 6, 4, f) |
|
|
|
testFork(t, processor, 9, 1, full, equal) |
|
|
|
testFork(t, bman, 9, 1, f) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestBrokenChain(t *testing.T) { |
|
|
|
// Tests that chains missing links do not get accepted by the processor.
|
|
|
|
db, err := ethdb.NewMemDatabase() |
|
|
|
func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) } |
|
|
|
if err != nil { |
|
|
|
func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) } |
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
|
|
|
|
} |
|
|
|
func testBrokenChain(t *testing.T, full bool) { |
|
|
|
bman, err := newCanonical(10, db) |
|
|
|
// Make chain starting from genesis
|
|
|
|
if err != nil { |
|
|
|
db, processor, err := newCanonical(10, full) |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
db2, err := ethdb.NewMemDatabase() |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Fatal("Failed to create db:", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bman2, err := newCanonical(10, db2) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Fatal("Could not make new canonical chain:", err) |
|
|
|
t.Fatalf("failed to make new canonical chain: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
bman2.bc.SetProcessor(bman2) |
|
|
|
// Create a forked chain, and try to insert with a missing link
|
|
|
|
parent := bman2.bc.CurrentBlock() |
|
|
|
if full { |
|
|
|
chainB := makeChain(parent, 5, db2, forkSeed) |
|
|
|
chain := makeBlockChain(processor.bc.CurrentBlock(), 5, db, forkSeed)[1:] |
|
|
|
chainB = chainB[1:] |
|
|
|
if err := testBlockChainImport(chain, processor); err == nil { |
|
|
|
_, err = testChain(chainB, bman) |
|
|
|
t.Errorf("broken block chain not reported") |
|
|
|
if err == nil { |
|
|
|
} |
|
|
|
t.Error("expected broken chain to return error") |
|
|
|
} else { |
|
|
|
|
|
|
|
chain := makeHeaderChain(processor.bc.CurrentHeader(), 5, db, forkSeed)[1:] |
|
|
|
|
|
|
|
if err := testHeaderChainImport(chain, processor); err == nil { |
|
|
|
|
|
|
|
t.Errorf("broken header chain not reported") |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -376,7 +417,16 @@ type bproc struct{} |
|
|
|
|
|
|
|
|
|
|
|
func (bproc) Process(*types.Block) (vm.Logs, types.Receipts, error) { return nil, nil, nil } |
|
|
|
func (bproc) Process(*types.Block) (vm.Logs, types.Receipts, error) { return nil, nil, nil } |
|
|
|
|
|
|
|
|
|
|
|
func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block { |
|
|
|
func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { |
|
|
|
|
|
|
|
blocks := makeBlockChainWithDiff(genesis, d, seed) |
|
|
|
|
|
|
|
headers := make([]*types.Header, len(blocks)) |
|
|
|
|
|
|
|
for i, block := range blocks { |
|
|
|
|
|
|
|
headers[i] = block.Header() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return headers |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func makeBlockChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block { |
|
|
|
var chain []*types.Block |
|
|
|
var chain []*types.Block |
|
|
|
for i, difficulty := range d { |
|
|
|
for i, difficulty := range d { |
|
|
|
header := &types.Header{ |
|
|
|
header := &types.Header{ |
|
|
@ -410,142 +460,209 @@ func chm(genesis *types.Block, db ethdb.Database) *BlockChain { |
|
|
|
return bc |
|
|
|
return bc |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestReorgLongest(t *testing.T) { |
|
|
|
// Tests that reorganizing a long difficult chain after a short easy one
|
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
// overwrites the canonical numbers and links in the database.
|
|
|
|
|
|
|
|
func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) } |
|
|
|
|
|
|
|
func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) } |
|
|
|
|
|
|
|
|
|
|
|
genesis, err := WriteTestNetGenesisBlock(db, 0) |
|
|
|
func testReorgLong(t *testing.T, full bool) { |
|
|
|
if err != nil { |
|
|
|
testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full) |
|
|
|
t.Error(err) |
|
|
|
} |
|
|
|
t.FailNow() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bc := chm(genesis, db) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chain1 := makeChainWithDiff(genesis, []int{1, 2, 4}, 10) |
|
|
|
// Tests that reorganizing a short difficult chain after a long easy one
|
|
|
|
chain2 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11) |
|
|
|
// overwrites the canonical numbers and links in the database.
|
|
|
|
|
|
|
|
func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) } |
|
|
|
|
|
|
|
func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) } |
|
|
|
|
|
|
|
|
|
|
|
bc.InsertChain(chain1) |
|
|
|
func testReorgShort(t *testing.T, full bool) { |
|
|
|
bc.InsertChain(chain2) |
|
|
|
testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
prev := bc.CurrentBlock() |
|
|
|
func testReorg(t *testing.T, first, second []int, td int64, full bool) { |
|
|
|
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
|
|
|
// Create a pristine block chain
|
|
|
|
if prev.ParentHash() != block.Hash() { |
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash()) |
|
|
|
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
|
|
|
|
|
|
|
bc := chm(genesis, db) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Insert an easy and a difficult chain afterwards
|
|
|
|
|
|
|
|
if full { |
|
|
|
|
|
|
|
bc.InsertChain(makeBlockChainWithDiff(genesis, first, 11)) |
|
|
|
|
|
|
|
bc.InsertChain(makeBlockChainWithDiff(genesis, second, 22)) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, first, 11), false) |
|
|
|
|
|
|
|
bc.InsertHeaderChain(makeHeaderChainWithDiff(genesis, second, 22), false) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Check that the chain is valid number and link wise
|
|
|
|
|
|
|
|
if full { |
|
|
|
|
|
|
|
prev := bc.CurrentBlock() |
|
|
|
|
|
|
|
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
|
|
|
|
|
|
|
if prev.ParentHash() != block.Hash() { |
|
|
|
|
|
|
|
t.Errorf("parent block hash mismatch: have %x, want %x", prev.ParentHash(), block.Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
prev := bc.CurrentHeader() |
|
|
|
|
|
|
|
for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) { |
|
|
|
|
|
|
|
if prev.ParentHash != header.Hash() { |
|
|
|
|
|
|
|
t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// Make sure the chain total difficulty is the correct one
|
|
|
|
|
|
|
|
want := new(big.Int).Add(genesis.Difficulty(), big.NewInt(td)) |
|
|
|
|
|
|
|
if full { |
|
|
|
|
|
|
|
if have := bc.GetTd(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 { |
|
|
|
|
|
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if have := bc.GetTd(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 { |
|
|
|
|
|
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", have, want) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestBadHashes(t *testing.T) { |
|
|
|
// Tests that the insertion functions detect banned hashes.
|
|
|
|
|
|
|
|
func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) } |
|
|
|
|
|
|
|
func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func testBadHashes(t *testing.T, full bool) { |
|
|
|
|
|
|
|
// Create a pristine block chain
|
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
genesis, err := WriteTestNetGenesisBlock(db, 0) |
|
|
|
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Error(err) |
|
|
|
|
|
|
|
t.FailNow() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bc := chm(genesis, db) |
|
|
|
bc := chm(genesis, db) |
|
|
|
|
|
|
|
|
|
|
|
chain := makeChainWithDiff(genesis, []int{1, 2, 4}, 10) |
|
|
|
// Create a chain, ban a hash and try to import
|
|
|
|
BadHashes[chain[2].Header().Hash()] = true |
|
|
|
var err error |
|
|
|
|
|
|
|
if full { |
|
|
|
_, err = bc.InsertChain(chain) |
|
|
|
blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 4}, 10) |
|
|
|
|
|
|
|
BadHashes[blocks[2].Header().Hash()] = true |
|
|
|
|
|
|
|
_, err = bc.InsertChain(blocks) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 4}, 10) |
|
|
|
|
|
|
|
BadHashes[headers[2].Hash()] = true |
|
|
|
|
|
|
|
_, err = bc.InsertHeaderChain(headers, true) |
|
|
|
|
|
|
|
} |
|
|
|
if !IsBadHashError(err) { |
|
|
|
if !IsBadHashError(err) { |
|
|
|
t.Errorf("error mismatch: want: BadHashError, have: %v", err) |
|
|
|
t.Errorf("error mismatch: want: BadHashError, have: %v", err) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestReorgBadHashes(t *testing.T) { |
|
|
|
// Tests that bad hashes are detected on boot, and the chan rolled back to a
|
|
|
|
|
|
|
|
// good state prior to the bad hash.
|
|
|
|
|
|
|
|
func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) } |
|
|
|
|
|
|
|
func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func testReorgBadHashes(t *testing.T, full bool) { |
|
|
|
|
|
|
|
// Create a pristine block chain
|
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
genesis, err := WriteTestNetGenesisBlock(db, 0) |
|
|
|
genesis, _ := WriteTestNetGenesisBlock(db, 0) |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Error(err) |
|
|
|
|
|
|
|
t.FailNow() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
bc := chm(genesis, db) |
|
|
|
bc := chm(genesis, db) |
|
|
|
|
|
|
|
|
|
|
|
chain := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11) |
|
|
|
// Create a chain, import and ban aferwards
|
|
|
|
bc.InsertChain(chain) |
|
|
|
headers := makeHeaderChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
|
|
|
|
|
|
|
blocks := makeBlockChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
|
|
|
if chain[3].Header().Hash() != bc.LastBlockHash() { |
|
|
|
|
|
|
|
t.Errorf("last block hash mismatch: want: %x, have: %x", chain[3].Header().Hash(), bc.LastBlockHash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// NewChainManager should check BadHashes when loading it db
|
|
|
|
|
|
|
|
BadHashes[chain[3].Header().Hash()] = true |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var eventMux event.TypeMux |
|
|
|
|
|
|
|
ncm, err := NewBlockChain(db, FakePow{}, &eventMux) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
t.Errorf("NewChainManager err: %s", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// check it set head to (valid) parent of bad hash block
|
|
|
|
|
|
|
|
if chain[2].Header().Hash() != ncm.LastBlockHash() { |
|
|
|
|
|
|
|
t.Errorf("last block hash mismatch: want: %x, have: %x", chain[2].Header().Hash(), ncm.LastBlockHash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if chain[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 { |
|
|
|
if full { |
|
|
|
t.Errorf("current block gasLimit mismatch: want: %x, have: %x", chain[2].Header().GasLimit, ncm.GasLimit()) |
|
|
|
if _, err := bc.InsertChain(blocks); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to import blocks: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if bc.CurrentBlock().Hash() != blocks[3].Hash() { |
|
|
|
|
|
|
|
t.Errorf("last block hash mismatch: have: %x, want %x", bc.CurrentBlock().Hash(), blocks[3].Header().Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
BadHashes[blocks[3].Header().Hash()] = true |
|
|
|
|
|
|
|
defer func() { delete(BadHashes, blocks[3].Header().Hash()) }() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if _, err := bc.InsertHeaderChain(headers, true); err != nil { |
|
|
|
|
|
|
|
t.Fatalf("failed to import headers: %v", err) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if bc.CurrentHeader().Hash() != headers[3].Hash() { |
|
|
|
|
|
|
|
t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
BadHashes[headers[3].Hash()] = true |
|
|
|
|
|
|
|
defer func() { delete(BadHashes, headers[3].Hash()) }() |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// Create a new chain manager and check it rolled back the state
|
|
|
|
|
|
|
|
ncm, err := NewBlockChain(db, FakePow{}, new(event.TypeMux)) |
|
|
|
func TestReorgShortest(t *testing.T) { |
|
|
|
|
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
|
|
|
|
genesis, err := WriteTestNetGenesisBlock(db, 0) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Error(err) |
|
|
|
t.Fatalf("failed to create new chain manager: %v", err) |
|
|
|
t.FailNow() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
bc := chm(genesis, db) |
|
|
|
if full { |
|
|
|
|
|
|
|
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() { |
|
|
|
chain1 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 10) |
|
|
|
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash()) |
|
|
|
chain2 := makeChainWithDiff(genesis, []int{1, 10}, 11) |
|
|
|
} |
|
|
|
|
|
|
|
if blocks[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 { |
|
|
|
bc.InsertChain(chain1) |
|
|
|
t.Errorf("last block gasLimit mismatch: have: %x, want %x", ncm.GasLimit(), blocks[2].Header().GasLimit) |
|
|
|
bc.InsertChain(chain2) |
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
prev := bc.CurrentBlock() |
|
|
|
if ncm.CurrentHeader().Hash() != genesis.Hash() { |
|
|
|
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) { |
|
|
|
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), genesis.Hash()) |
|
|
|
if prev.ParentHash() != block.Hash() { |
|
|
|
|
|
|
|
t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash()) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func TestInsertNonceError(t *testing.T) { |
|
|
|
// Tests chain insertions in the face of one entity containing an invalid nonce.
|
|
|
|
|
|
|
|
func TestHeadersInsertNonceError(t *testing.T) { testInsertNonceError(t, false) } |
|
|
|
|
|
|
|
func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func testInsertNonceError(t *testing.T, full bool) { |
|
|
|
for i := 1; i < 25 && !t.Failed(); i++ { |
|
|
|
for i := 1; i < 25 && !t.Failed(); i++ { |
|
|
|
db, _ := ethdb.NewMemDatabase() |
|
|
|
// Create a pristine chain and database
|
|
|
|
genesis, err := WriteTestNetGenesisBlock(db, 0) |
|
|
|
db, processor, err := newCanonical(0, full) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
t.Error(err) |
|
|
|
t.Fatalf("failed to create pristine chain: %v", err) |
|
|
|
t.FailNow() |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
bc := chm(genesis, db) |
|
|
|
bc := processor.bc |
|
|
|
bc.processor = NewBlockProcessor(db, bc.pow, bc, bc.eventMux) |
|
|
|
|
|
|
|
blocks := makeChain(bc.currentBlock, i, db, 0) |
|
|
|
// Create and insert a chain with a failing nonce
|
|
|
|
|
|
|
|
var ( |
|
|
|
|
|
|
|
failAt int |
|
|
|
|
|
|
|
failRes int |
|
|
|
|
|
|
|
failNum uint64 |
|
|
|
|
|
|
|
failHash common.Hash |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
if full { |
|
|
|
|
|
|
|
blocks := makeBlockChain(processor.bc.CurrentBlock(), i, db, 0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
failAt = rand.Int() % len(blocks) |
|
|
|
|
|
|
|
failNum = blocks[failAt].NumberU64() |
|
|
|
|
|
|
|
failHash = blocks[failAt].Hash() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
processor.bc.pow = failPow{failNum} |
|
|
|
|
|
|
|
failRes, err = processor.bc.InsertChain(blocks) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
headers := makeHeaderChain(processor.bc.CurrentHeader(), i, db, 0) |
|
|
|
|
|
|
|
|
|
|
|
fail := rand.Int() % len(blocks) |
|
|
|
failAt = rand.Int() % len(headers) |
|
|
|
failblock := blocks[fail] |
|
|
|
failNum = headers[failAt].Number.Uint64() |
|
|
|
bc.pow = failPow{failblock.NumberU64()} |
|
|
|
failHash = headers[failAt].Hash() |
|
|
|
n, err := bc.InsertChain(blocks) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
processor.bc.pow = failPow{failNum} |
|
|
|
|
|
|
|
failRes, err = processor.bc.InsertHeaderChain(headers, true) |
|
|
|
|
|
|
|
} |
|
|
|
// Check that the returned error indicates the nonce failure.
|
|
|
|
// Check that the returned error indicates the nonce failure.
|
|
|
|
if n != fail { |
|
|
|
if failRes != failAt { |
|
|
|
t.Errorf("(i=%d) wrong failed block index: got %d, want %d", i, n, fail) |
|
|
|
t.Errorf("test %d: failure index mismatch: have %d, want %d", i, failRes, failAt) |
|
|
|
} |
|
|
|
} |
|
|
|
if !IsBlockNonceErr(err) { |
|
|
|
if !IsBlockNonceErr(err) { |
|
|
|
t.Fatalf("(i=%d) got %q, want a nonce error", i, err) |
|
|
|
t.Fatalf("test %d: error mismatch: have %v, want nonce error", i, err) |
|
|
|
} |
|
|
|
} |
|
|
|
nerr := err.(*BlockNonceErr) |
|
|
|
nerr := err.(*BlockNonceErr) |
|
|
|
if nerr.Number.Cmp(failblock.Number()) != 0 { |
|
|
|
if nerr.Number.Uint64() != failNum { |
|
|
|
t.Errorf("(i=%d) wrong block number in error, got %v, want %v", i, nerr.Number, failblock.Number()) |
|
|
|
t.Errorf("test %d: number mismatch: have %v, want %v", i, nerr.Number, failNum) |
|
|
|
} |
|
|
|
} |
|
|
|
if nerr.Hash != failblock.Hash() { |
|
|
|
if nerr.Hash != failHash { |
|
|
|
t.Errorf("(i=%d) wrong block hash in error, got %v, want %v", i, nerr.Hash, failblock.Hash()) |
|
|
|
t.Errorf("test %d: hash mismatch: have %x, want %x", i, nerr.Hash[:4], failHash[:4]) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Check that all no blocks after the failing block have been inserted.
|
|
|
|
// Check that all no blocks after the failing block have been inserted.
|
|
|
|
for _, block := range blocks[fail:] { |
|
|
|
for j := 0; j < i-failAt; j++ { |
|
|
|
if bc.HasBlock(block.Hash()) { |
|
|
|
if full { |
|
|
|
t.Errorf("(i=%d) invalid block %d present in chain", i, block.NumberU64()) |
|
|
|
if block := bc.GetBlockByNumber(failNum + uint64(j)); block != nil { |
|
|
|
|
|
|
|
t.Errorf("test %d: invalid block in chain: %v", i, block) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
if header := bc.GetHeaderByNumber(failNum + uint64(j)); header != nil { |
|
|
|
|
|
|
|
t.Errorf("test %d: invalid header in chain: %v", i, header) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|