quorum/consensus/istanbul/backend/engine_test.go

569 lines
18 KiB
Go

// Copyright 2017 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 backend
import (
"bytes"
"crypto/ecdsa"
"math/big"
"reflect"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/istanbul"
"github.com/ethereum/go-ethereum/core"
"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"
"github.com/ethereum/go-ethereum/rlp"
)
// in this test, we can set n to 1, and it means we can process Istanbul and commit a
// block by one node. Otherwise, if n is larger than 1, we have to generate
// other fake events to process Istanbul.
func newBlockChain(n int) (*core.BlockChain, *backend) {
genesis, nodeKeys := getGenesisAndKeys(n)
memDB := ethdb.NewMemDatabase()
config := istanbul.DefaultConfig
// Use the first key as private key
b, _ := New(config, nodeKeys[0], memDB).(*backend)
genesis.MustCommit(memDB)
blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, b, vm.Config{}, nil)
if err != nil {
panic(err)
}
b.Start(blockchain, blockchain.CurrentBlock, blockchain.HasBadBlock)
snap, err := b.snapshot(blockchain, 0, common.Hash{}, nil)
if err != nil {
panic(err)
}
if snap == nil {
panic("failed to get snapshot")
}
proposerAddr := snap.ValSet.GetProposer().Address()
// find proposer key
for _, key := range nodeKeys {
addr := crypto.PubkeyToAddress(key.PublicKey)
if addr.String() == proposerAddr.String() {
b.privateKey = key
b.address = addr
}
}
return blockchain, b
}
func getGenesisAndKeys(n int) (*core.Genesis, []*ecdsa.PrivateKey) {
// Setup validators
var nodeKeys = make([]*ecdsa.PrivateKey, n)
var addrs = make([]common.Address, n)
for i := 0; i < n; i++ {
nodeKeys[i], _ = crypto.GenerateKey()
addrs[i] = crypto.PubkeyToAddress(nodeKeys[i].PublicKey)
}
// generate genesis block
genesis := core.DefaultGenesisBlock()
genesis.Config = params.TestChainConfig
// force enable Istanbul engine
genesis.Config.Istanbul = &params.IstanbulConfig{}
genesis.Config.Ethash = nil
genesis.Difficulty = defaultDifficulty
genesis.Nonce = emptyNonce.Uint64()
genesis.Mixhash = types.IstanbulDigest
appendValidators(genesis, addrs)
return genesis, nodeKeys
}
func appendValidators(genesis *core.Genesis, addrs []common.Address) {
if len(genesis.ExtraData) < types.IstanbulExtraVanity {
genesis.ExtraData = append(genesis.ExtraData, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)...)
}
genesis.ExtraData = genesis.ExtraData[:types.IstanbulExtraVanity]
ist := &types.IstanbulExtra{
Validators: addrs,
Seal: []byte{},
CommittedSeal: [][]byte{},
}
istPayload, err := rlp.EncodeToBytes(&ist)
if err != nil {
panic("failed to encode istanbul extra")
}
genesis.ExtraData = append(genesis.ExtraData, istPayload...)
}
func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header {
header := &types.Header{
ParentHash: parent.Hash(),
Number: parent.Number().Add(parent.Number(), common.Big1),
GasLimit: core.CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()),
GasUsed: 0,
Extra: parent.Extra(),
Time: new(big.Int).Add(parent.Time(), new(big.Int).SetUint64(config.BlockPeriod)),
Difficulty: defaultDifficulty,
}
return header
}
func makeBlock(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block {
block := makeBlockWithoutSeal(chain, engine, parent)
stopCh := make(chan struct{})
resultCh := make(chan *types.Block, 10)
go engine.Seal(chain, block, resultCh, stopCh)
blk := <-resultCh
return blk
}
func makeBlockWithoutSeal(chain *core.BlockChain, engine *backend, parent *types.Block) *types.Block {
header := makeHeader(parent, engine.config)
engine.Prepare(chain, header)
state, _, _ := chain.StateAt(parent.Root())
block, _ := engine.Finalize(chain, header, state, nil, nil, nil)
return block
}
func TestPrepare(t *testing.T) {
chain, engine := newBlockChain(1)
header := makeHeader(chain.Genesis(), engine.config)
err := engine.Prepare(chain, header)
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
header.ParentHash = common.StringToHash("1234567890")
err = engine.Prepare(chain, header)
if err != consensus.ErrUnknownAncestor {
t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor)
}
}
func TestSealStopChannel(t *testing.T) {
chain, engine := newBlockChain(4)
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
stop := make(chan struct{}, 1)
eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{})
eventLoop := func() {
select {
case ev := <-eventSub.Chan():
_, ok := ev.Data.(istanbul.RequestEvent)
if !ok {
t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
}
stop <- struct{}{}
}
eventSub.Unsubscribe()
}
go eventLoop()
resultCh := make(chan *types.Block, 10)
go func() {
err := engine.Seal(chain, block, resultCh, stop)
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
}()
finalBlock := <-resultCh
if finalBlock != nil {
t.Errorf("block mismatch: have %v, want nil", finalBlock)
}
}
func TestSealCommittedOtherHash(t *testing.T) {
chain, engine := newBlockChain(4)
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
otherBlock := makeBlockWithoutSeal(chain, engine, block)
eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{})
eventLoop := func() {
select {
case ev := <-eventSub.Chan():
_, ok := ev.Data.(istanbul.RequestEvent)
if !ok {
t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data))
}
engine.Commit(otherBlock, [][]byte{})
}
eventSub.Unsubscribe()
}
go eventLoop()
seal := func() {
engine.Seal(chain, block, nil, make(chan struct{}))
t.Error("seal should not be completed")
}
go seal()
const timeoutDura = 2 * time.Second
timeout := time.NewTimer(timeoutDura)
select {
case <-timeout.C:
// wait 2 seconds to ensure we cannot get any blocks from Istanbul
}
}
func TestSealCommitted(t *testing.T) {
chain, engine := newBlockChain(1)
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block)
resultCh := make(chan *types.Block, 10)
go func() {
err := engine.Seal(chain, block, resultCh, make(chan struct{}))
if err != nil {
t.Errorf("error mismatch: have %v, want %v", err, expectedBlock)
}
}()
finalBlock := <-resultCh
if finalBlock.Hash() != expectedBlock.Hash() {
t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash())
}
}
func TestVerifyHeader(t *testing.T) {
chain, engine := newBlockChain(1)
// errEmptyCommittedSeals case
block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
block, _ = engine.updateBlock(chain.Genesis().Header(), block)
err := engine.VerifyHeader(chain, block.Header(), false)
if err != errEmptyCommittedSeals {
t.Errorf("error mismatch: have %v, want %v", err, errEmptyCommittedSeals)
}
// short extra data
header := block.Header()
header.Extra = []byte{}
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidExtraDataFormat {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat)
}
// incorrect extra format
header.Extra = []byte("0000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000000")
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidExtraDataFormat {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidExtraDataFormat)
}
// non zero MixDigest
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
header.MixDigest = common.StringToHash("123456789")
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidMixDigest {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidMixDigest)
}
// invalid uncles hash
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
header.UncleHash = common.StringToHash("123456789")
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidUncleHash {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidUncleHash)
}
// invalid difficulty
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
header.Difficulty = big.NewInt(2)
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidDifficulty {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidDifficulty)
}
// invalid timestamp
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
header.Time = new(big.Int).Add(chain.Genesis().Time(), new(big.Int).SetUint64(engine.config.BlockPeriod-1))
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidTimestamp {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidTimestamp)
}
// future block
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
header.Time = new(big.Int).Add(big.NewInt(now().Unix()), new(big.Int).SetUint64(10))
err = engine.VerifyHeader(chain, header, false)
if err != consensus.ErrFutureBlock {
t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrFutureBlock)
}
// invalid nonce
block = makeBlockWithoutSeal(chain, engine, chain.Genesis())
header = block.Header()
copy(header.Nonce[:], hexutil.MustDecode("0x111111111111"))
header.Number = big.NewInt(int64(engine.config.Epoch))
err = engine.VerifyHeader(chain, header, false)
if err != errInvalidNonce {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidNonce)
}
}
func TestVerifySeal(t *testing.T) {
chain, engine := newBlockChain(1)
genesis := chain.Genesis()
// cannot verify genesis
err := engine.VerifySeal(chain, genesis.Header())
if err != errUnknownBlock {
t.Errorf("error mismatch: have %v, want %v", err, errUnknownBlock)
}
block := makeBlock(chain, engine, genesis)
// change block content
header := block.Header()
header.Number = big.NewInt(4)
block1 := block.WithSeal(header)
err = engine.VerifySeal(chain, block1.Header())
if err != errUnauthorized {
t.Errorf("error mismatch: have %v, want %v", err, errUnauthorized)
}
// unauthorized users but still can get correct signer address
engine.privateKey, _ = crypto.GenerateKey()
err = engine.VerifySeal(chain, block.Header())
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
}
func TestVerifyHeaders(t *testing.T) {
chain, engine := newBlockChain(1)
genesis := chain.Genesis()
// success case
headers := []*types.Header{}
blocks := []*types.Block{}
size := 100
for i := 0; i < size; i++ {
var b *types.Block
if i == 0 {
b = makeBlockWithoutSeal(chain, engine, genesis)
b, _ = engine.updateBlock(genesis.Header(), b)
} else {
b = makeBlockWithoutSeal(chain, engine, blocks[i-1])
b, _ = engine.updateBlock(blocks[i-1].Header(), b)
}
blocks = append(blocks, b)
headers = append(headers, blocks[i].Header())
}
now = func() time.Time {
return time.Unix(headers[size-1].Time.Int64(), 0)
}
_, results := engine.VerifyHeaders(chain, headers, nil)
const timeoutDura = 2 * time.Second
timeout := time.NewTimer(timeoutDura)
index := 0
OUT1:
for {
select {
case err := <-results:
if err != nil {
if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals {
t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals", err)
break OUT1
}
}
index++
if index == size {
break OUT1
}
case <-timeout.C:
break OUT1
}
}
// abort cases
abort, results := engine.VerifyHeaders(chain, headers, nil)
timeout = time.NewTimer(timeoutDura)
index = 0
OUT2:
for {
select {
case err := <-results:
if err != nil {
if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals {
t.Errorf("error mismatch: have %v, want errEmptyCommittedSeals|errInvalidCommittedSeals", err)
break OUT2
}
}
index++
if index == 5 {
abort <- struct{}{}
}
if index >= size {
t.Errorf("verifyheaders should be aborted")
break OUT2
}
case <-timeout.C:
break OUT2
}
}
// error header cases
headers[2].Number = big.NewInt(100)
abort, results = engine.VerifyHeaders(chain, headers, nil)
timeout = time.NewTimer(timeoutDura)
index = 0
errors := 0
expectedErrors := 2
OUT3:
for {
select {
case err := <-results:
if err != nil {
if err != errEmptyCommittedSeals && err != errInvalidCommittedSeals {
errors++
}
}
index++
if index == size {
if errors != expectedErrors {
t.Errorf("error mismatch: have %v, want %v", err, expectedErrors)
}
break OUT3
}
case <-timeout.C:
break OUT3
}
}
}
func TestPrepareExtra(t *testing.T) {
validators := make([]common.Address, 4)
validators[0] = common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a"))
validators[1] = common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212"))
validators[2] = common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6"))
validators[3] = common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440"))
vanity := make([]byte, types.IstanbulExtraVanity)
expectedResult := append(vanity, hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0")...)
h := &types.Header{
Extra: vanity,
}
payload, err := prepareExtra(h, validators)
if err != nil {
t.Errorf("error mismatch: have %v, want: nil", err)
}
if !reflect.DeepEqual(payload, expectedResult) {
t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult)
}
// append useless information to extra-data
h.Extra = append(vanity, make([]byte, 15)...)
payload, err = prepareExtra(h, validators)
if !reflect.DeepEqual(payload, expectedResult) {
t.Errorf("payload mismatch: have %v, want %v", payload, expectedResult)
}
}
func TestWriteSeal(t *testing.T) {
vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)
istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0")
expectedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...)
expectedIstExtra := &types.IstanbulExtra{
Validators: []common.Address{
common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")),
common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")),
common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")),
common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")),
},
Seal: expectedSeal,
CommittedSeal: [][]byte{},
}
var expectedErr error
h := &types.Header{
Extra: append(vanity, istRawData...),
}
// normal case
err := writeSeal(h, expectedSeal)
if err != expectedErr {
t.Errorf("error mismatch: have %v, want %v", err, expectedErr)
}
// verify istanbul extra-data
istExtra, err := types.ExtractIstanbulExtra(h)
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
if !reflect.DeepEqual(istExtra, expectedIstExtra) {
t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra)
}
// invalid seal
unexpectedSeal := append(expectedSeal, make([]byte, 1)...)
err = writeSeal(h, unexpectedSeal)
if err != errInvalidSignature {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature)
}
}
func TestWriteCommittedSeals(t *testing.T) {
vanity := bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)
istRawData := hexutil.MustDecode("0xf858f8549444add0ec310f115a0e603b2d7db9f067778eaf8a94294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212946beaaed781d2d2ab6350f5c4566a2c6eaac407a6948be76812f765c24641ec63dc2852b378aba2b44080c0")
expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...)
expectedIstExtra := &types.IstanbulExtra{
Validators: []common.Address{
common.BytesToAddress(hexutil.MustDecode("0x44add0ec310f115a0e603b2d7db9f067778eaf8a")),
common.BytesToAddress(hexutil.MustDecode("0x294fc7e8f22b3bcdcf955dd7ff3ba2ed833f8212")),
common.BytesToAddress(hexutil.MustDecode("0x6beaaed781d2d2ab6350f5c4566a2c6eaac407a6")),
common.BytesToAddress(hexutil.MustDecode("0x8be76812f765c24641ec63dc2852b378aba2b440")),
},
Seal: []byte{},
CommittedSeal: [][]byte{expectedCommittedSeal},
}
var expectedErr error
h := &types.Header{
Extra: append(vanity, istRawData...),
}
// normal case
err := writeCommittedSeals(h, [][]byte{expectedCommittedSeal})
if err != expectedErr {
t.Errorf("error mismatch: have %v, want %v", err, expectedErr)
}
// verify istanbul extra-data
istExtra, err := types.ExtractIstanbulExtra(h)
if err != nil {
t.Errorf("error mismatch: have %v, want nil", err)
}
if !reflect.DeepEqual(istExtra, expectedIstExtra) {
t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra)
}
// invalid seal
unexpectedCommittedSeal := append(expectedCommittedSeal, make([]byte, 1)...)
err = writeCommittedSeals(h, [][]byte{unexpectedCommittedSeal})
if err != errInvalidCommittedSeals {
t.Errorf("error mismatch: have %v, want %v", err, errInvalidCommittedSeals)
}
}