forked from cerc-io/plugeth
5f8888e116
* accounts, consensus, core, eth: make chain maker consensus agnostic * consensus, core: move CalcDifficulty to Engine interface * consensus: add docs for calcDifficulty function * consensus, core: minor comment fixups
1200 lines
43 KiB
Go
1200 lines
43 KiB
Go
// Copyright 2014 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
"github.com/ethereum/go-ethereum/core/state"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/core/vm"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/ethdb"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
// newTestBlockChain creates a blockchain without validation.
|
|
func newTestBlockChain(fake bool) *BlockChain {
|
|
db, _ := ethdb.NewMemDatabase()
|
|
gspec := &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Difficulty: big.NewInt(1),
|
|
}
|
|
gspec.MustCommit(db)
|
|
engine := ethash.NewFullFaker()
|
|
if !fake {
|
|
engine = ethash.NewTester()
|
|
}
|
|
blockchain, err := NewBlockChain(db, gspec.Config, engine, vm.Config{})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
blockchain.SetValidator(bproc{})
|
|
return blockchain
|
|
}
|
|
|
|
// Test fork of length N starting from block i
|
|
func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) {
|
|
// Copy old chain up to #i into a new db
|
|
db, blockchain2, err := newCanonical(ethash.NewFaker(), i, full)
|
|
if err != nil {
|
|
t.Fatal("could not make new canonical in testFork", err)
|
|
}
|
|
defer blockchain2.Stop()
|
|
|
|
// Assert the chains have the same header/block at #i
|
|
var hash1, hash2 common.Hash
|
|
if full {
|
|
hash1 = blockchain.GetBlockByNumber(uint64(i)).Hash()
|
|
hash2 = blockchain2.GetBlockByNumber(uint64(i)).Hash()
|
|
} else {
|
|
hash1 = blockchain.GetHeaderByNumber(uint64(i)).Hash()
|
|
hash2 = blockchain2.GetHeaderByNumber(uint64(i)).Hash()
|
|
}
|
|
if hash1 != hash2 {
|
|
t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1)
|
|
}
|
|
// Extend the newly created chain
|
|
var (
|
|
blockChainB []*types.Block
|
|
headerChainB []*types.Header
|
|
)
|
|
if full {
|
|
blockChainB = makeBlockChain(blockchain2.CurrentBlock(), n, ethash.NewFaker(), db, forkSeed)
|
|
if _, err := blockchain2.InsertChain(blockChainB); err != nil {
|
|
t.Fatalf("failed to insert forking chain: %v", err)
|
|
}
|
|
} else {
|
|
headerChainB = makeHeaderChain(blockchain2.CurrentHeader(), n, ethash.NewFaker(), db, forkSeed)
|
|
if _, err := blockchain2.InsertHeaderChain(headerChainB, 1); err != nil {
|
|
t.Fatalf("failed to insert forking chain: %v", err)
|
|
}
|
|
}
|
|
// Sanity check that the forked chain can be imported into the original
|
|
var tdPre, tdPost *big.Int
|
|
|
|
if full {
|
|
tdPre = blockchain.GetTdByHash(blockchain.CurrentBlock().Hash())
|
|
if err := testBlockChainImport(blockChainB, blockchain); err != nil {
|
|
t.Fatalf("failed to import forked block chain: %v", err)
|
|
}
|
|
tdPost = blockchain.GetTdByHash(blockChainB[len(blockChainB)-1].Hash())
|
|
} else {
|
|
tdPre = blockchain.GetTdByHash(blockchain.CurrentHeader().Hash())
|
|
if err := testHeaderChainImport(headerChainB, blockchain); err != nil {
|
|
t.Fatalf("failed to import forked header chain: %v", err)
|
|
}
|
|
tdPost = blockchain.GetTdByHash(headerChainB[len(headerChainB)-1].Hash())
|
|
}
|
|
// Compare the total difficulties of the chains
|
|
comparator(tdPre, tdPost)
|
|
}
|
|
|
|
func printChain(bc *BlockChain) {
|
|
for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- {
|
|
b := bc.GetBlockByNumber(uint64(i))
|
|
fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty())
|
|
}
|
|
}
|
|
|
|
// testBlockChainImport tries to process a chain of blocks, writing them into
|
|
// the database if successful.
|
|
func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
|
for _, block := range chain {
|
|
// Try and process the block
|
|
err := blockchain.engine.VerifyHeader(blockchain, block.Header(), true)
|
|
if err == nil {
|
|
err = blockchain.validator.ValidateBody(block)
|
|
}
|
|
if err != nil {
|
|
if err == ErrKnownBlock {
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
receipts, _, usedGas, err := blockchain.Processor().Process(block, statedb, vm.Config{})
|
|
if err != nil {
|
|
blockchain.reportBlock(block, receipts, err)
|
|
return err
|
|
}
|
|
err = blockchain.validator.ValidateState(block, blockchain.GetBlockByHash(block.ParentHash()), statedb, receipts, usedGas)
|
|
if err != nil {
|
|
blockchain.reportBlock(block, receipts, err)
|
|
return err
|
|
}
|
|
blockchain.mu.Lock()
|
|
WriteTd(blockchain.chainDb, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTdByHash(block.ParentHash())))
|
|
WriteBlock(blockchain.chainDb, block)
|
|
statedb.CommitTo(blockchain.chainDb, false)
|
|
blockchain.mu.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// testHeaderChainImport tries to process a chain of header, writing them into
|
|
// the database if successful.
|
|
func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error {
|
|
for _, header := range chain {
|
|
// Try and validate the header
|
|
if err := blockchain.engine.VerifyHeader(blockchain, header, false); err != nil {
|
|
return err
|
|
}
|
|
// Manually insert the header into the database, but don't reorganise (allows subsequent testing)
|
|
blockchain.mu.Lock()
|
|
WriteTd(blockchain.chainDb, header.Hash(), header.Number.Uint64(), new(big.Int).Add(header.Difficulty, blockchain.GetTdByHash(header.ParentHash)))
|
|
WriteHeader(blockchain.chainDb, header)
|
|
blockchain.mu.Unlock()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func insertChain(done chan bool, blockchain *BlockChain, chain types.Blocks, t *testing.T) {
|
|
_, err := blockchain.InsertChain(chain)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
t.FailNow()
|
|
}
|
|
done <- true
|
|
}
|
|
|
|
func TestLastBlock(t *testing.T) {
|
|
bchain := newTestBlockChain(false)
|
|
defer bchain.Stop()
|
|
|
|
block := makeBlockChain(bchain.CurrentBlock(), 1, ethash.NewFaker(), bchain.chainDb, 0)[0]
|
|
bchain.insert(block)
|
|
if block.Hash() != GetHeadBlockHash(bchain.chainDb) {
|
|
t.Errorf("Write/Get HeadBlockHash failed")
|
|
}
|
|
}
|
|
|
|
// Tests that given a starting canonical chain of a given size, it can be extended
|
|
// with various length chains.
|
|
func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) }
|
|
func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) }
|
|
|
|
func testExtendCanonical(t *testing.T, full bool) {
|
|
length := 5
|
|
|
|
// Make first chain starting from genesis
|
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
}
|
|
defer processor.Stop()
|
|
|
|
// Define the difficulty comparator
|
|
better := func(td1, td2 *big.Int) {
|
|
if td2.Cmp(td1) <= 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
|
|
}
|
|
}
|
|
// Start fork from current height
|
|
testFork(t, processor, length, 1, full, better)
|
|
testFork(t, processor, length, 2, full, better)
|
|
testFork(t, processor, length, 5, full, better)
|
|
testFork(t, processor, length, 10, full, better)
|
|
}
|
|
|
|
// Tests that given a starting canonical chain of a given size, creating shorter
|
|
// forks do not take canonical ownership.
|
|
func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) }
|
|
func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) }
|
|
|
|
func testShorterFork(t *testing.T, full bool) {
|
|
length := 10
|
|
|
|
// Make first chain starting from genesis
|
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
}
|
|
defer processor.Stop()
|
|
|
|
// Define the difficulty comparator
|
|
worse := func(td1, td2 *big.Int) {
|
|
if td2.Cmp(td1) >= 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1)
|
|
}
|
|
}
|
|
// Sum of numbers must be less than `length` for this to be a shorter fork
|
|
testFork(t, processor, 0, 3, full, worse)
|
|
testFork(t, processor, 0, 7, full, worse)
|
|
testFork(t, processor, 1, 1, full, worse)
|
|
testFork(t, processor, 1, 7, full, worse)
|
|
testFork(t, processor, 5, 3, full, worse)
|
|
testFork(t, processor, 5, 4, full, worse)
|
|
}
|
|
|
|
// Tests that given a starting canonical chain of a given size, creating longer
|
|
// forks do take canonical ownership.
|
|
func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) }
|
|
func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) }
|
|
|
|
func testLongerFork(t *testing.T, full bool) {
|
|
length := 10
|
|
|
|
// Make first chain starting from genesis
|
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
}
|
|
defer processor.Stop()
|
|
|
|
// Define the difficulty comparator
|
|
better := func(td1, td2 *big.Int) {
|
|
if td2.Cmp(td1) <= 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1)
|
|
}
|
|
}
|
|
// Sum of numbers must be greater than `length` for this to be a longer fork
|
|
testFork(t, processor, 0, 11, full, better)
|
|
testFork(t, processor, 0, 15, full, better)
|
|
testFork(t, processor, 1, 10, full, better)
|
|
testFork(t, processor, 1, 12, full, better)
|
|
testFork(t, processor, 5, 6, full, better)
|
|
testFork(t, processor, 5, 8, full, better)
|
|
}
|
|
|
|
// Tests that given a starting canonical chain of a given size, creating equal
|
|
// forks do take canonical ownership.
|
|
func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) }
|
|
func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) }
|
|
|
|
func testEqualFork(t *testing.T, full bool) {
|
|
length := 10
|
|
|
|
// Make first chain starting from genesis
|
|
_, processor, err := newCanonical(ethash.NewFaker(), length, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
}
|
|
defer processor.Stop()
|
|
|
|
// Define the difficulty comparator
|
|
equal := func(td1, td2 *big.Int) {
|
|
if td2.Cmp(td1) != 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1)
|
|
}
|
|
}
|
|
// Sum of numbers must be equal to `length` for this to be an equal fork
|
|
testFork(t, processor, 0, 10, full, equal)
|
|
testFork(t, processor, 1, 9, full, equal)
|
|
testFork(t, processor, 2, 8, full, equal)
|
|
testFork(t, processor, 5, 5, full, equal)
|
|
testFork(t, processor, 6, 4, full, equal)
|
|
testFork(t, processor, 9, 1, full, equal)
|
|
}
|
|
|
|
// Tests that chains missing links do not get accepted by the processor.
|
|
func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) }
|
|
func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) }
|
|
|
|
func testBrokenChain(t *testing.T, full bool) {
|
|
// Make chain starting from genesis
|
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 10, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to make new canonical chain: %v", err)
|
|
}
|
|
defer blockchain.Stop()
|
|
|
|
// Create a forked chain, and try to insert with a missing link
|
|
if full {
|
|
chain := makeBlockChain(blockchain.CurrentBlock(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
|
if err := testBlockChainImport(chain, blockchain); err == nil {
|
|
t.Errorf("broken block chain not reported")
|
|
}
|
|
} else {
|
|
chain := makeHeaderChain(blockchain.CurrentHeader(), 5, ethash.NewFaker(), db, forkSeed)[1:]
|
|
if err := testHeaderChainImport(chain, blockchain); err == nil {
|
|
t.Errorf("broken header chain not reported")
|
|
}
|
|
}
|
|
}
|
|
|
|
type bproc struct{}
|
|
|
|
func (bproc) ValidateBody(*types.Block) error { return nil }
|
|
func (bproc) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas *big.Int) error {
|
|
return nil
|
|
}
|
|
func (bproc) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, *big.Int, error) {
|
|
return nil, nil, new(big.Int), nil
|
|
}
|
|
|
|
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
|
|
for i, difficulty := range d {
|
|
header := &types.Header{
|
|
Coinbase: common.Address{seed},
|
|
Number: big.NewInt(int64(i + 1)),
|
|
Difficulty: big.NewInt(int64(difficulty)),
|
|
UncleHash: types.EmptyUncleHash,
|
|
TxHash: types.EmptyRootHash,
|
|
ReceiptHash: types.EmptyRootHash,
|
|
Time: big.NewInt(int64(i) + 1),
|
|
}
|
|
if i == 0 {
|
|
header.ParentHash = genesis.Hash()
|
|
} else {
|
|
header.ParentHash = chain[i-1].Hash()
|
|
}
|
|
block := types.NewBlockWithHeader(header)
|
|
chain = append(chain, block)
|
|
}
|
|
return chain
|
|
}
|
|
|
|
// Tests that reorganising a long difficult chain after a short easy one
|
|
// 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) }
|
|
|
|
func testReorgLong(t *testing.T, full bool) {
|
|
testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10, full)
|
|
}
|
|
|
|
// Tests that reorganising a short difficult chain after a long easy one
|
|
// 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) }
|
|
|
|
func testReorgShort(t *testing.T, full bool) {
|
|
testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11, full)
|
|
}
|
|
|
|
func testReorg(t *testing.T, first, second []int, td int64, full bool) {
|
|
bc := newTestBlockChain(true)
|
|
defer bc.Stop()
|
|
|
|
// Insert an easy and a difficult chain afterwards
|
|
if full {
|
|
bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, first, 11))
|
|
bc.InsertChain(makeBlockChainWithDiff(bc.genesisBlock, second, 22))
|
|
} else {
|
|
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11), 1)
|
|
bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22), 1)
|
|
}
|
|
// 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(bc.genesisBlock.Difficulty(), big.NewInt(td))
|
|
if full {
|
|
if have := bc.GetTdByHash(bc.CurrentBlock().Hash()); have.Cmp(want) != 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
|
|
}
|
|
} else {
|
|
if have := bc.GetTdByHash(bc.CurrentHeader().Hash()); have.Cmp(want) != 0 {
|
|
t.Errorf("total difficulty mismatch: have %v, want %v", have, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
bc := newTestBlockChain(true)
|
|
defer bc.Stop()
|
|
|
|
// Create a chain, ban a hash and try to import
|
|
var err error
|
|
if full {
|
|
blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
|
|
BadHashes[blocks[2].Header().Hash()] = true
|
|
_, err = bc.InsertChain(blocks)
|
|
} else {
|
|
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10)
|
|
BadHashes[headers[2].Hash()] = true
|
|
_, err = bc.InsertHeaderChain(headers, 1)
|
|
}
|
|
if err != ErrBlacklistedHash {
|
|
t.Errorf("error mismatch: have: %v, want: %v", err, ErrBlacklistedHash)
|
|
}
|
|
}
|
|
|
|
// Tests that bad hashes are detected on boot, and the chain 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) {
|
|
bc := newTestBlockChain(true)
|
|
defer bc.Stop()
|
|
|
|
// Create a chain, import and ban afterwards
|
|
headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
|
|
blocks := makeBlockChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10)
|
|
|
|
if full {
|
|
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, 1); 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 BlockChain and check that it rolled back the state.
|
|
ncm, err := NewBlockChain(bc.chainDb, bc.config, ethash.NewFaker(), vm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("failed to create new chain manager: %v", err)
|
|
}
|
|
defer ncm.Stop()
|
|
|
|
if full {
|
|
if ncm.CurrentBlock().Hash() != blocks[2].Header().Hash() {
|
|
t.Errorf("last block hash mismatch: have: %x, want %x", ncm.CurrentBlock().Hash(), blocks[2].Header().Hash())
|
|
}
|
|
if blocks[2].Header().GasLimit.Cmp(ncm.GasLimit()) != 0 {
|
|
t.Errorf("last block gasLimit mismatch: have: %x, want %x", ncm.GasLimit(), blocks[2].Header().GasLimit)
|
|
}
|
|
} else {
|
|
if ncm.CurrentHeader().Hash() != headers[2].Hash() {
|
|
t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash())
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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++ {
|
|
// Create a pristine chain and database
|
|
db, blockchain, err := newCanonical(ethash.NewFaker(), 0, full)
|
|
if err != nil {
|
|
t.Fatalf("failed to create pristine chain: %v", err)
|
|
}
|
|
defer blockchain.Stop()
|
|
|
|
// Create and insert a chain with a failing nonce
|
|
var (
|
|
failAt int
|
|
failRes int
|
|
failNum uint64
|
|
)
|
|
if full {
|
|
blocks := makeBlockChain(blockchain.CurrentBlock(), i, ethash.NewFaker(), db, 0)
|
|
|
|
failAt = rand.Int() % len(blocks)
|
|
failNum = blocks[failAt].NumberU64()
|
|
|
|
blockchain.engine = ethash.NewFakeFailer(failNum)
|
|
failRes, err = blockchain.InsertChain(blocks)
|
|
} else {
|
|
headers := makeHeaderChain(blockchain.CurrentHeader(), i, ethash.NewFaker(), db, 0)
|
|
|
|
failAt = rand.Int() % len(headers)
|
|
failNum = headers[failAt].Number.Uint64()
|
|
|
|
blockchain.engine = ethash.NewFakeFailer(failNum)
|
|
blockchain.hc.engine = blockchain.engine
|
|
failRes, err = blockchain.InsertHeaderChain(headers, 1)
|
|
}
|
|
// Check that the returned error indicates the failure.
|
|
if failRes != failAt {
|
|
t.Errorf("test %d: failure index mismatch: have %d, want %d", i, failRes, failAt)
|
|
}
|
|
// Check that all no blocks after the failing block have been inserted.
|
|
for j := 0; j < i-failAt; j++ {
|
|
if full {
|
|
if block := blockchain.GetBlockByNumber(failNum + uint64(j)); block != nil {
|
|
t.Errorf("test %d: invalid block in chain: %v", i, block)
|
|
}
|
|
} else {
|
|
if header := blockchain.GetHeaderByNumber(failNum + uint64(j)); header != nil {
|
|
t.Errorf("test %d: invalid header in chain: %v", i, header)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that fast importing a block chain produces the same chain data as the
|
|
// classical full block processing.
|
|
func TestFastVsFullChains(t *testing.T) {
|
|
// Configure and generate a sample block chain
|
|
var (
|
|
gendb, _ = ethdb.NewMemDatabase()
|
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
|
funds = big.NewInt(1000000000)
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: GenesisAlloc{address: {Balance: funds}},
|
|
}
|
|
genesis = gspec.MustCommit(gendb)
|
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
|
)
|
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, 1024, func(i int, block *BlockGen) {
|
|
block.SetCoinbase(common.Address{0x00})
|
|
|
|
// If the block number is multiple of 3, send a few bonus transactions to the miner
|
|
if i%3 == 2 {
|
|
for j := 0; j < i%4+1; j++ {
|
|
tx, err := types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{0x00}, big.NewInt(1000), bigTxGas, nil, nil), signer, key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
block.AddTx(tx)
|
|
}
|
|
}
|
|
// If the block number is a multiple of 5, add a few bonus uncles to the block
|
|
if i%5 == 5 {
|
|
block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 1).Hash(), Number: big.NewInt(int64(i - 1))})
|
|
}
|
|
})
|
|
// Import the chain as an archive node for the comparison baseline
|
|
archiveDb, _ := ethdb.NewMemDatabase()
|
|
gspec.MustCommit(archiveDb)
|
|
archive, _ := NewBlockChain(archiveDb, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer archive.Stop()
|
|
|
|
if n, err := archive.InsertChain(blocks); err != nil {
|
|
t.Fatalf("failed to process block %d: %v", n, err)
|
|
}
|
|
// Fast import the chain as a non-archive node to test
|
|
fastDb, _ := ethdb.NewMemDatabase()
|
|
gspec.MustCommit(fastDb)
|
|
fast, _ := NewBlockChain(fastDb, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer fast.Stop()
|
|
|
|
headers := make([]*types.Header, len(blocks))
|
|
for i, block := range blocks {
|
|
headers[i] = block.Header()
|
|
}
|
|
if n, err := fast.InsertHeaderChain(headers, 1); err != nil {
|
|
t.Fatalf("failed to insert header %d: %v", n, err)
|
|
}
|
|
if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
|
|
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
|
}
|
|
// Iterate over all chain data components, and cross reference
|
|
for i := 0; i < len(blocks); i++ {
|
|
num, hash := blocks[i].NumberU64(), blocks[i].Hash()
|
|
|
|
if ftd, atd := fast.GetTdByHash(hash), archive.GetTdByHash(hash); ftd.Cmp(atd) != 0 {
|
|
t.Errorf("block #%d [%x]: td mismatch: have %v, want %v", num, hash, ftd, atd)
|
|
}
|
|
if fheader, aheader := fast.GetHeaderByHash(hash), archive.GetHeaderByHash(hash); fheader.Hash() != aheader.Hash() {
|
|
t.Errorf("block #%d [%x]: header mismatch: have %v, want %v", num, hash, fheader, aheader)
|
|
}
|
|
if fblock, ablock := fast.GetBlockByHash(hash), archive.GetBlockByHash(hash); fblock.Hash() != ablock.Hash() {
|
|
t.Errorf("block #%d [%x]: block mismatch: have %v, want %v", num, hash, fblock, ablock)
|
|
} else if types.DeriveSha(fblock.Transactions()) != types.DeriveSha(ablock.Transactions()) {
|
|
t.Errorf("block #%d [%x]: transactions mismatch: have %v, want %v", num, hash, fblock.Transactions(), ablock.Transactions())
|
|
} else if types.CalcUncleHash(fblock.Uncles()) != types.CalcUncleHash(ablock.Uncles()) {
|
|
t.Errorf("block #%d [%x]: uncles mismatch: have %v, want %v", num, hash, fblock.Uncles(), ablock.Uncles())
|
|
}
|
|
if freceipts, areceipts := GetBlockReceipts(fastDb, hash, GetBlockNumber(fastDb, hash)), GetBlockReceipts(archiveDb, hash, GetBlockNumber(archiveDb, hash)); types.DeriveSha(freceipts) != types.DeriveSha(areceipts) {
|
|
t.Errorf("block #%d [%x]: receipts mismatch: have %v, want %v", num, hash, freceipts, areceipts)
|
|
}
|
|
}
|
|
// Check that the canonical chains are the same between the databases
|
|
for i := 0; i < len(blocks)+1; i++ {
|
|
if fhash, ahash := GetCanonicalHash(fastDb, uint64(i)), GetCanonicalHash(archiveDb, uint64(i)); fhash != ahash {
|
|
t.Errorf("block #%d: canonical hash mismatch: have %v, want %v", i, fhash, ahash)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests that various import methods move the chain head pointers to the correct
|
|
// positions.
|
|
func TestLightVsFastVsFullChainHeads(t *testing.T) {
|
|
// Configure and generate a sample block chain
|
|
var (
|
|
gendb, _ = ethdb.NewMemDatabase()
|
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
|
funds = big.NewInt(1000000000)
|
|
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{address: {Balance: funds}}}
|
|
genesis = gspec.MustCommit(gendb)
|
|
)
|
|
height := uint64(1024)
|
|
blocks, receipts := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), gendb, int(height), nil)
|
|
|
|
// Configure a subchain to roll back
|
|
remove := []common.Hash{}
|
|
for _, block := range blocks[height/2:] {
|
|
remove = append(remove, block.Hash())
|
|
}
|
|
// Create a small assertion method to check the three heads
|
|
assert := func(t *testing.T, kind string, chain *BlockChain, header uint64, fast uint64, block uint64) {
|
|
if num := chain.CurrentBlock().NumberU64(); num != block {
|
|
t.Errorf("%s head block mismatch: have #%v, want #%v", kind, num, block)
|
|
}
|
|
if num := chain.CurrentFastBlock().NumberU64(); num != fast {
|
|
t.Errorf("%s head fast-block mismatch: have #%v, want #%v", kind, num, fast)
|
|
}
|
|
if num := chain.CurrentHeader().Number.Uint64(); num != header {
|
|
t.Errorf("%s head header mismatch: have #%v, want #%v", kind, num, header)
|
|
}
|
|
}
|
|
// Import the chain as an archive node and ensure all pointers are updated
|
|
archiveDb, _ := ethdb.NewMemDatabase()
|
|
gspec.MustCommit(archiveDb)
|
|
|
|
archive, _ := NewBlockChain(archiveDb, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
if n, err := archive.InsertChain(blocks); err != nil {
|
|
t.Fatalf("failed to process block %d: %v", n, err)
|
|
}
|
|
defer archive.Stop()
|
|
|
|
assert(t, "archive", archive, height, height, height)
|
|
archive.Rollback(remove)
|
|
assert(t, "archive", archive, height/2, height/2, height/2)
|
|
|
|
// Import the chain as a non-archive node and ensure all pointers are updated
|
|
fastDb, _ := ethdb.NewMemDatabase()
|
|
gspec.MustCommit(fastDb)
|
|
fast, _ := NewBlockChain(fastDb, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer fast.Stop()
|
|
|
|
headers := make([]*types.Header, len(blocks))
|
|
for i, block := range blocks {
|
|
headers[i] = block.Header()
|
|
}
|
|
if n, err := fast.InsertHeaderChain(headers, 1); err != nil {
|
|
t.Fatalf("failed to insert header %d: %v", n, err)
|
|
}
|
|
if n, err := fast.InsertReceiptChain(blocks, receipts); err != nil {
|
|
t.Fatalf("failed to insert receipt %d: %v", n, err)
|
|
}
|
|
assert(t, "fast", fast, height, height, 0)
|
|
fast.Rollback(remove)
|
|
assert(t, "fast", fast, height/2, height/2, 0)
|
|
|
|
// Import the chain as a light node and ensure all pointers are updated
|
|
lightDb, _ := ethdb.NewMemDatabase()
|
|
gspec.MustCommit(lightDb)
|
|
|
|
light, _ := NewBlockChain(lightDb, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
if n, err := light.InsertHeaderChain(headers, 1); err != nil {
|
|
t.Fatalf("failed to insert header %d: %v", n, err)
|
|
}
|
|
defer light.Stop()
|
|
|
|
assert(t, "light", light, height, 0, 0)
|
|
light.Rollback(remove)
|
|
assert(t, "light", light, height/2, 0, 0)
|
|
}
|
|
|
|
// Tests that chain reorganisations handle transaction removals and reinsertions.
|
|
func TestChainTxReorgs(t *testing.T) {
|
|
var (
|
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
|
key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
|
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
|
addr3 = crypto.PubkeyToAddress(key3.PublicKey)
|
|
db, _ = ethdb.NewMemDatabase()
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
GasLimit: 3141592,
|
|
Alloc: GenesisAlloc{
|
|
addr1: {Balance: big.NewInt(1000000)},
|
|
addr2: {Balance: big.NewInt(1000000)},
|
|
addr3: {Balance: big.NewInt(1000000)},
|
|
},
|
|
}
|
|
genesis = gspec.MustCommit(db)
|
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
|
)
|
|
|
|
// Create two transactions shared between the chains:
|
|
// - postponed: transaction included at a later block in the forked chain
|
|
// - swapped: transaction included at the same block number in the forked chain
|
|
postponed, _ := types.SignTx(types.NewTransaction(0, addr1, big.NewInt(1000), bigTxGas, nil, nil), signer, key1)
|
|
swapped, _ := types.SignTx(types.NewTransaction(1, addr1, big.NewInt(1000), bigTxGas, nil, nil), signer, key1)
|
|
|
|
// Create two transactions that will be dropped by the forked chain:
|
|
// - pastDrop: transaction dropped retroactively from a past block
|
|
// - freshDrop: transaction dropped exactly at the block where the reorg is detected
|
|
var pastDrop, freshDrop *types.Transaction
|
|
|
|
// Create three transactions that will be added in the forked chain:
|
|
// - pastAdd: transaction added before the reorganization is detected
|
|
// - freshAdd: transaction added at the exact block the reorg is detected
|
|
// - futureAdd: transaction added after the reorg has already finished
|
|
var pastAdd, freshAdd, futureAdd *types.Transaction
|
|
|
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {
|
|
switch i {
|
|
case 0:
|
|
pastDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
|
|
|
gen.AddTx(pastDrop) // This transaction will be dropped in the fork from below the split point
|
|
gen.AddTx(postponed) // This transaction will be postponed till block #3 in the fork
|
|
|
|
case 2:
|
|
freshDrop, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr2, big.NewInt(1000), bigTxGas, nil, nil), signer, key2)
|
|
|
|
gen.AddTx(freshDrop) // This transaction will be dropped in the fork from exactly at the split point
|
|
gen.AddTx(swapped) // This transaction will be swapped out at the exact height
|
|
|
|
gen.OffsetTime(9) // Lower the block difficulty to simulate a weaker chain
|
|
}
|
|
})
|
|
// Import the chain. This runs all block validation rules.
|
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
if i, err := blockchain.InsertChain(chain); err != nil {
|
|
t.Fatalf("failed to insert original chain[%d]: %v", i, err)
|
|
}
|
|
defer blockchain.Stop()
|
|
|
|
// overwrite the old chain
|
|
chain, _ = GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) {
|
|
switch i {
|
|
case 0:
|
|
pastAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
|
gen.AddTx(pastAdd) // This transaction needs to be injected during reorg
|
|
|
|
case 2:
|
|
gen.AddTx(postponed) // This transaction was postponed from block #1 in the original chain
|
|
gen.AddTx(swapped) // This transaction was swapped from the exact current spot in the original chain
|
|
|
|
freshAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
|
gen.AddTx(freshAdd) // This transaction will be added exactly at reorg time
|
|
|
|
case 3:
|
|
futureAdd, _ = types.SignTx(types.NewTransaction(gen.TxNonce(addr3), addr3, big.NewInt(1000), bigTxGas, nil, nil), signer, key3)
|
|
gen.AddTx(futureAdd) // This transaction will be added after a full reorg
|
|
}
|
|
})
|
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
|
t.Fatalf("failed to insert forked chain: %v", err)
|
|
}
|
|
|
|
// removed tx
|
|
for i, tx := range (types.Transactions{pastDrop, freshDrop}) {
|
|
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn != nil {
|
|
t.Errorf("drop %d: tx %v found while shouldn't have been", i, txn)
|
|
}
|
|
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt != nil {
|
|
t.Errorf("drop %d: receipt %v found while shouldn't have been", i, rcpt)
|
|
}
|
|
}
|
|
// added tx
|
|
for i, tx := range (types.Transactions{pastAdd, freshAdd, futureAdd}) {
|
|
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil {
|
|
t.Errorf("add %d: expected tx to be found", i)
|
|
}
|
|
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil {
|
|
t.Errorf("add %d: expected receipt to be found", i)
|
|
}
|
|
}
|
|
// shared tx
|
|
for i, tx := range (types.Transactions{postponed, swapped}) {
|
|
if txn, _, _, _ := GetTransaction(db, tx.Hash()); txn == nil {
|
|
t.Errorf("share %d: expected tx to be found", i)
|
|
}
|
|
if rcpt, _, _, _ := GetReceipt(db, tx.Hash()); rcpt == nil {
|
|
t.Errorf("share %d: expected receipt to be found", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLogReorgs(t *testing.T) {
|
|
|
|
var (
|
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
|
db, _ = ethdb.NewMemDatabase()
|
|
// this code generates a log
|
|
code = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")
|
|
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
|
|
genesis = gspec.MustCommit(db)
|
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
|
)
|
|
|
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer blockchain.Stop()
|
|
|
|
rmLogsCh := make(chan RemovedLogsEvent)
|
|
blockchain.SubscribeRemovedLogsEvent(rmLogsCh)
|
|
chain, _ := GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 2, func(i int, gen *BlockGen) {
|
|
if i == 1 {
|
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), code), signer, key1)
|
|
if err != nil {
|
|
t.Fatalf("failed to create tx: %v", err)
|
|
}
|
|
gen.AddTx(tx)
|
|
}
|
|
})
|
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
|
t.Fatalf("failed to insert chain: %v", err)
|
|
}
|
|
|
|
chain, _ = GenerateChain(params.TestChainConfig, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
|
t.Fatalf("failed to insert forked chain: %v", err)
|
|
}
|
|
|
|
timeout := time.NewTimer(1 * time.Second)
|
|
select {
|
|
case ev := <-rmLogsCh:
|
|
if len(ev.Logs) == 0 {
|
|
t.Error("expected logs")
|
|
}
|
|
case <-timeout.C:
|
|
t.Fatal("Timeout. There is no RemovedLogsEvent has been sent.")
|
|
}
|
|
}
|
|
|
|
func TestReorgSideEvent(t *testing.T) {
|
|
var (
|
|
db, _ = ethdb.NewMemDatabase()
|
|
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
|
gspec = &Genesis{
|
|
Config: params.TestChainConfig,
|
|
Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}},
|
|
}
|
|
genesis = gspec.MustCommit(db)
|
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
|
)
|
|
|
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer blockchain.Stop()
|
|
|
|
chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, gen *BlockGen) {})
|
|
if _, err := blockchain.InsertChain(chain); err != nil {
|
|
t.Fatalf("failed to insert chain: %v", err)
|
|
}
|
|
|
|
replacementBlocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, gen *BlockGen) {
|
|
tx, err := types.SignTx(types.NewContractCreation(gen.TxNonce(addr1), new(big.Int), big.NewInt(1000000), new(big.Int), nil), signer, key1)
|
|
if i == 2 {
|
|
gen.OffsetTime(-9)
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("failed to create tx: %v", err)
|
|
}
|
|
gen.AddTx(tx)
|
|
})
|
|
chainSideCh := make(chan ChainSideEvent, 64)
|
|
blockchain.SubscribeChainSideEvent(chainSideCh)
|
|
if _, err := blockchain.InsertChain(replacementBlocks); err != nil {
|
|
t.Fatalf("failed to insert chain: %v", err)
|
|
}
|
|
|
|
// first two block of the secondary chain are for a brief moment considered
|
|
// side chains because up to that point the first one is considered the
|
|
// heavier chain.
|
|
expectedSideHashes := map[common.Hash]bool{
|
|
replacementBlocks[0].Hash(): true,
|
|
replacementBlocks[1].Hash(): true,
|
|
chain[0].Hash(): true,
|
|
chain[1].Hash(): true,
|
|
chain[2].Hash(): true,
|
|
}
|
|
|
|
i := 0
|
|
|
|
const timeoutDura = 10 * time.Second
|
|
timeout := time.NewTimer(timeoutDura)
|
|
done:
|
|
for {
|
|
select {
|
|
case ev := <-chainSideCh:
|
|
block := ev.Block
|
|
if _, ok := expectedSideHashes[block.Hash()]; !ok {
|
|
t.Errorf("%d: didn't expect %x to be in side chain", i, block.Hash())
|
|
}
|
|
i++
|
|
|
|
if i == len(expectedSideHashes) {
|
|
timeout.Stop()
|
|
|
|
break done
|
|
}
|
|
timeout.Reset(timeoutDura)
|
|
|
|
case <-timeout.C:
|
|
t.Fatal("Timeout. Possibly not all blocks were triggered for sideevent")
|
|
}
|
|
}
|
|
|
|
// make sure no more events are fired
|
|
select {
|
|
case e := <-chainSideCh:
|
|
t.Errorf("unexpected event fired: %v", e)
|
|
case <-time.After(250 * time.Millisecond):
|
|
}
|
|
|
|
}
|
|
|
|
// Tests if the canonical block can be fetched from the database during chain insertion.
|
|
func TestCanonicalBlockRetrieval(t *testing.T) {
|
|
bc := newTestBlockChain(true)
|
|
defer bc.Stop()
|
|
|
|
chain, _ := GenerateChain(bc.config, bc.genesisBlock, ethash.NewFaker(), bc.chainDb, 10, func(i int, gen *BlockGen) {})
|
|
|
|
var pend sync.WaitGroup
|
|
pend.Add(len(chain))
|
|
|
|
for i := range chain {
|
|
go func(block *types.Block) {
|
|
defer pend.Done()
|
|
|
|
// try to retrieve a block by its canonical hash and see if the block data can be retrieved.
|
|
for {
|
|
ch := GetCanonicalHash(bc.chainDb, block.NumberU64())
|
|
if ch == (common.Hash{}) {
|
|
continue // busy wait for canonical hash to be written
|
|
}
|
|
if ch != block.Hash() {
|
|
t.Fatalf("unknown canonical hash, want %s, got %s", block.Hash().Hex(), ch.Hex())
|
|
}
|
|
fb := GetBlock(bc.chainDb, ch, block.NumberU64())
|
|
if fb == nil {
|
|
t.Fatalf("unable to retrieve block %d for canonical hash: %s", block.NumberU64(), ch.Hex())
|
|
}
|
|
if fb.Hash() != block.Hash() {
|
|
t.Fatalf("invalid block hash for block %d, want %s, got %s", block.NumberU64(), block.Hash().Hex(), fb.Hash().Hex())
|
|
}
|
|
return
|
|
}
|
|
}(chain[i])
|
|
|
|
if _, err := bc.InsertChain(types.Blocks{chain[i]}); err != nil {
|
|
t.Fatalf("failed to insert block %d: %v", i, err)
|
|
}
|
|
}
|
|
pend.Wait()
|
|
}
|
|
|
|
func TestEIP155Transition(t *testing.T) {
|
|
// Configure and generate a sample block chain
|
|
var (
|
|
db, _ = ethdb.NewMemDatabase()
|
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
|
funds = big.NewInt(1000000000)
|
|
deleteAddr = common.Address{1}
|
|
gspec = &Genesis{
|
|
Config: ¶ms.ChainConfig{ChainId: big.NewInt(1), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)},
|
|
Alloc: GenesisAlloc{address: {Balance: funds}, deleteAddr: {Balance: new(big.Int)}},
|
|
}
|
|
genesis = gspec.MustCommit(db)
|
|
)
|
|
|
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer blockchain.Stop()
|
|
|
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
|
var (
|
|
tx *types.Transaction
|
|
err error
|
|
basicTx = func(signer types.Signer) (*types.Transaction, error) {
|
|
return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), big.NewInt(21000), new(big.Int), nil), signer, key)
|
|
}
|
|
)
|
|
switch i {
|
|
case 0:
|
|
tx, err = basicTx(types.HomesteadSigner{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
case 2:
|
|
tx, err = basicTx(types.HomesteadSigner{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
|
|
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainId))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
case 3:
|
|
tx, err = basicTx(types.HomesteadSigner{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
|
|
tx, err = basicTx(types.NewEIP155Signer(gspec.Config.ChainId))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
}
|
|
})
|
|
|
|
if _, err := blockchain.InsertChain(blocks); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block := blockchain.GetBlockByNumber(1)
|
|
if block.Transactions()[0].Protected() {
|
|
t.Error("Expected block[0].txs[0] to not be replay protected")
|
|
}
|
|
|
|
block = blockchain.GetBlockByNumber(3)
|
|
if block.Transactions()[0].Protected() {
|
|
t.Error("Expected block[3].txs[0] to not be replay protected")
|
|
}
|
|
if !block.Transactions()[1].Protected() {
|
|
t.Error("Expected block[3].txs[1] to be replay protected")
|
|
}
|
|
if _, err := blockchain.InsertChain(blocks[4:]); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// generate an invalid chain id transaction
|
|
config := ¶ms.ChainConfig{ChainId: big.NewInt(2), EIP155Block: big.NewInt(2), HomesteadBlock: new(big.Int)}
|
|
blocks, _ = GenerateChain(config, blocks[len(blocks)-1], ethash.NewFaker(), db, 4, func(i int, block *BlockGen) {
|
|
var (
|
|
tx *types.Transaction
|
|
err error
|
|
basicTx = func(signer types.Signer) (*types.Transaction, error) {
|
|
return types.SignTx(types.NewTransaction(block.TxNonce(address), common.Address{}, new(big.Int), big.NewInt(21000), new(big.Int), nil), signer, key)
|
|
}
|
|
)
|
|
switch i {
|
|
case 0:
|
|
tx, err = basicTx(types.NewEIP155Signer(big.NewInt(2)))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
}
|
|
})
|
|
_, err := blockchain.InsertChain(blocks)
|
|
if err != types.ErrInvalidChainId {
|
|
t.Error("expected error:", types.ErrInvalidChainId)
|
|
}
|
|
}
|
|
|
|
func TestEIP161AccountRemoval(t *testing.T) {
|
|
// Configure and generate a sample block chain
|
|
var (
|
|
db, _ = ethdb.NewMemDatabase()
|
|
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
address = crypto.PubkeyToAddress(key.PublicKey)
|
|
funds = big.NewInt(1000000000)
|
|
theAddr = common.Address{1}
|
|
gspec = &Genesis{
|
|
Config: ¶ms.ChainConfig{
|
|
ChainId: big.NewInt(1),
|
|
HomesteadBlock: new(big.Int),
|
|
EIP155Block: new(big.Int),
|
|
EIP158Block: big.NewInt(2),
|
|
},
|
|
Alloc: GenesisAlloc{address: {Balance: funds}},
|
|
}
|
|
genesis = gspec.MustCommit(db)
|
|
)
|
|
blockchain, _ := NewBlockChain(db, gspec.Config, ethash.NewFaker(), vm.Config{})
|
|
defer blockchain.Stop()
|
|
|
|
blocks, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 3, func(i int, block *BlockGen) {
|
|
var (
|
|
tx *types.Transaction
|
|
err error
|
|
signer = types.NewEIP155Signer(gspec.Config.ChainId)
|
|
)
|
|
switch i {
|
|
case 0:
|
|
tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), big.NewInt(21000), new(big.Int), nil), signer, key)
|
|
case 1:
|
|
tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), big.NewInt(21000), new(big.Int), nil), signer, key)
|
|
case 2:
|
|
tx, err = types.SignTx(types.NewTransaction(block.TxNonce(address), theAddr, new(big.Int), big.NewInt(21000), new(big.Int), nil), signer, key)
|
|
}
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
block.AddTx(tx)
|
|
})
|
|
// account must exist pre eip 161
|
|
if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st, _ := blockchain.State(); !st.Exist(theAddr) {
|
|
t.Error("expected account to exist")
|
|
}
|
|
|
|
// account needs to be deleted post eip 161
|
|
if _, err := blockchain.InsertChain(types.Blocks{blocks[1]}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st, _ := blockchain.State(); st.Exist(theAddr) {
|
|
t.Error("account should not exist")
|
|
}
|
|
|
|
// account musn't be created post eip 161
|
|
if _, err := blockchain.InsertChain(types.Blocks{blocks[2]}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st, _ := blockchain.State(); st.Exist(theAddr) {
|
|
t.Error("account should not exist")
|
|
}
|
|
}
|