diff --git a/consensus/istanbul/backend/api.go b/consensus/istanbul/backend/api.go
new file mode 100644
index 0000000000..ee2a3e9d94
--- /dev/null
+++ b/consensus/istanbul/backend/api.go
@@ -0,0 +1,118 @@
+// 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 .
+
+package backend
+
+import (
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/rpc"
+)
+
+// API is a user facing RPC API to dump Istanbul state
+type API struct {
+ chain consensus.ChainReader
+ istanbul *backend
+}
+
+// GetSnapshot retrieves the state snapshot at a given block.
+func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error) {
+ // Retrieve the requested block number (or current if none requested)
+ var header *types.Header
+ if number == nil || *number == rpc.LatestBlockNumber {
+ header = api.chain.CurrentHeader()
+ } else {
+ header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
+ }
+ // Ensure we have an actually valid block and return its snapshot
+ if header == nil {
+ return nil, errUnknownBlock
+ }
+ return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
+}
+
+// GetSnapshotAtHash retrieves the state snapshot at a given block.
+func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error) {
+ header := api.chain.GetHeaderByHash(hash)
+ if header == nil {
+ return nil, errUnknownBlock
+ }
+ return api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
+}
+
+// GetValidators retrieves the list of authorized validators at the specified block.
+func (api *API) GetValidators(number *rpc.BlockNumber) ([]common.Address, error) {
+ // Retrieve the requested block number (or current if none requested)
+ var header *types.Header
+ if number == nil || *number == rpc.LatestBlockNumber {
+ header = api.chain.CurrentHeader()
+ } else {
+ header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
+ }
+ // Ensure we have an actually valid block and return the validators from its snapshot
+ if header == nil {
+ return nil, errUnknownBlock
+ }
+ snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
+ if err != nil {
+ return nil, err
+ }
+ return snap.validators(), nil
+}
+
+// GetValidatorsAtHash retrieves the state snapshot at a given block.
+func (api *API) GetValidatorsAtHash(hash common.Hash) ([]common.Address, error) {
+ header := api.chain.GetHeaderByHash(hash)
+ if header == nil {
+ return nil, errUnknownBlock
+ }
+ snap, err := api.istanbul.snapshot(api.chain, header.Number.Uint64(), header.Hash(), nil)
+ if err != nil {
+ return nil, err
+ }
+ return snap.validators(), nil
+}
+
+// Candidates returns the current candidates the node tries to uphold and vote on.
+func (api *API) Candidates() map[common.Address]bool {
+ api.istanbul.candidatesLock.RLock()
+ defer api.istanbul.candidatesLock.RUnlock()
+
+ proposals := make(map[common.Address]bool)
+ for address, auth := range api.istanbul.candidates {
+ proposals[address] = auth
+ }
+ return proposals
+}
+
+// Propose injects a new authorization candidate that the validator will attempt to
+// push through.
+func (api *API) Propose(address common.Address, auth bool) {
+ api.istanbul.candidatesLock.Lock()
+ defer api.istanbul.candidatesLock.Unlock()
+
+ api.istanbul.candidates[address] = auth
+}
+
+// Discard drops a currently running candidate, stopping the validator from casting
+// further votes (either for or against).
+func (api *API) Discard(address common.Address) {
+ api.istanbul.candidatesLock.Lock()
+ defer api.istanbul.candidatesLock.Unlock()
+
+ delete(api.istanbul.candidates, address)
+}
diff --git a/consensus/istanbul/backend/backend.go b/consensus/istanbul/backend/backend.go
new file mode 100644
index 0000000000..03542ee94d
--- /dev/null
+++ b/consensus/istanbul/backend/backend.go
@@ -0,0 +1,312 @@
+// 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 .
+
+package backend
+
+import (
+ "crypto/ecdsa"
+ "math/big"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core"
+ "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/event"
+ "github.com/ethereum/go-ethereum/log"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+const (
+ // fetcherID is the ID indicates the block is from Istanbul engine
+ fetcherID = "istanbul"
+)
+
+// New creates an Ethereum backend for Istanbul core engine.
+func New(config *istanbul.Config, privateKey *ecdsa.PrivateKey, db ethdb.Database) consensus.Istanbul {
+ // Allocate the snapshot caches and create the engine
+ recents, _ := lru.NewARC(inmemorySnapshots)
+ recentMessages, _ := lru.NewARC(inmemoryPeers)
+ knownMessages, _ := lru.NewARC(inmemoryMessages)
+ backend := &backend{
+ config: config,
+ istanbulEventMux: new(event.TypeMux),
+ privateKey: privateKey,
+ address: crypto.PubkeyToAddress(privateKey.PublicKey),
+ logger: log.New(),
+ db: db,
+ commitCh: make(chan *types.Block, 1),
+ recents: recents,
+ candidates: make(map[common.Address]bool),
+ coreStarted: false,
+ recentMessages: recentMessages,
+ knownMessages: knownMessages,
+ }
+ backend.core = istanbulCore.New(backend, backend.config)
+ return backend
+}
+
+// ----------------------------------------------------------------------------
+
+type backend struct {
+ config *istanbul.Config
+ istanbulEventMux *event.TypeMux
+ privateKey *ecdsa.PrivateKey
+ address common.Address
+ core istanbulCore.Engine
+ logger log.Logger
+ db ethdb.Database
+ chain consensus.ChainReader
+ currentBlock func() *types.Block
+ hasBadBlock func(hash common.Hash) bool
+
+ // the channels for istanbul engine notifications
+ commitCh chan *types.Block
+ proposedBlockHash common.Hash
+ sealMu sync.Mutex
+ coreStarted bool
+ coreMu sync.RWMutex
+
+ // Current list of candidates we are pushing
+ candidates map[common.Address]bool
+ // Protects the signer fields
+ candidatesLock sync.RWMutex
+ // Snapshots for recent block to speed up reorgs
+ recents *lru.ARCCache
+
+ // event subscription for ChainHeadEvent event
+ broadcaster consensus.Broadcaster
+
+ recentMessages *lru.ARCCache // the cache of peer's messages
+ knownMessages *lru.ARCCache // the cache of self messages
+}
+
+// Address implements istanbul.Backend.Address
+func (sb *backend) Address() common.Address {
+ return sb.address
+}
+
+// Validators implements istanbul.Backend.Validators
+func (sb *backend) Validators(proposal istanbul.Proposal) istanbul.ValidatorSet {
+ return sb.getValidators(proposal.Number().Uint64(), proposal.Hash())
+}
+
+// Broadcast implements istanbul.Backend.Broadcast
+func (sb *backend) Broadcast(valSet istanbul.ValidatorSet, payload []byte) error {
+ // send to others
+ sb.Gossip(valSet, payload)
+ // send to self
+ msg := istanbul.MessageEvent{
+ Payload: payload,
+ }
+ go sb.istanbulEventMux.Post(msg)
+ return nil
+}
+
+// Broadcast implements istanbul.Backend.Gossip
+func (sb *backend) Gossip(valSet istanbul.ValidatorSet, payload []byte) error {
+ hash := istanbul.RLPHash(payload)
+ sb.knownMessages.Add(hash, true)
+
+ targets := make(map[common.Address]bool)
+ for _, val := range valSet.List() {
+ if val.Address() != sb.Address() {
+ targets[val.Address()] = true
+ }
+ }
+
+ if sb.broadcaster != nil && len(targets) > 0 {
+ ps := sb.broadcaster.FindPeers(targets)
+ for addr, p := range ps {
+ ms, ok := sb.recentMessages.Get(addr)
+ var m *lru.ARCCache
+ if ok {
+ m, _ = ms.(*lru.ARCCache)
+ if _, k := m.Get(hash); k {
+ // This peer had this event, skip it
+ continue
+ }
+ } else {
+ m, _ = lru.NewARC(inmemoryMessages)
+ }
+
+ m.Add(hash, true)
+ sb.recentMessages.Add(addr, m)
+
+ go p.Send(istanbulMsg, payload)
+ }
+ }
+ return nil
+}
+
+// Commit implements istanbul.Backend.Commit
+func (sb *backend) Commit(proposal istanbul.Proposal, seals [][]byte) error {
+ // Check if the proposal is a valid block
+ block := &types.Block{}
+ block, ok := proposal.(*types.Block)
+ if !ok {
+ sb.logger.Error("Invalid proposal, %v", proposal)
+ return errInvalidProposal
+ }
+
+ h := block.Header()
+ // Append seals into extra-data
+ err := writeCommittedSeals(h, seals)
+ if err != nil {
+ return err
+ }
+ // update block's header
+ block = block.WithSeal(h)
+
+ sb.logger.Info("Committed", "address", sb.Address(), "hash", proposal.Hash(), "number", proposal.Number().Uint64())
+ // - if the proposed and committed blocks are the same, send the proposed hash
+ // to commit channel, which is being watched inside the engine.Seal() function.
+ // - otherwise, we try to insert the block.
+ // -- if success, the ChainHeadEvent event will be broadcasted, try to build
+ // the next block and the previous Seal() will be stopped.
+ // -- otherwise, a error will be returned and a round change event will be fired.
+ if sb.proposedBlockHash == block.Hash() {
+ // feed block hash to Seal() and wait the Seal() result
+ sb.commitCh <- block
+ return nil
+ }
+
+ if sb.broadcaster != nil {
+ sb.broadcaster.Enqueue(fetcherID, block)
+ }
+ return nil
+}
+
+// EventMux implements istanbul.Backend.EventMux
+func (sb *backend) EventMux() *event.TypeMux {
+ return sb.istanbulEventMux
+}
+
+// Verify implements istanbul.Backend.Verify
+func (sb *backend) Verify(proposal istanbul.Proposal) (time.Duration, error) {
+ // Check if the proposal is a valid block
+ block := &types.Block{}
+ block, ok := proposal.(*types.Block)
+ if !ok {
+ sb.logger.Error("Invalid proposal, %v", proposal)
+ return 0, errInvalidProposal
+ }
+
+ // check bad block
+ if sb.HasBadProposal(block.Hash()) {
+ return 0, core.ErrBlacklistedHash
+ }
+
+ // check block body
+ txnHash := types.DeriveSha(block.Transactions())
+ uncleHash := types.CalcUncleHash(block.Uncles())
+ if txnHash != block.Header().TxHash {
+ return 0, errMismatchTxhashes
+ }
+ if uncleHash != nilUncleHash {
+ return 0, errInvalidUncleHash
+ }
+
+ // verify the header of proposed block
+ err := sb.VerifyHeader(sb.chain, block.Header(), false)
+ // ignore errEmptyCommittedSeals error because we don't have the committed seals yet
+ if err == nil || err == errEmptyCommittedSeals {
+ return 0, nil
+ } else if err == consensus.ErrFutureBlock {
+ return time.Unix(block.Header().Time.Int64(), 0).Sub(now()), consensus.ErrFutureBlock
+ }
+ return 0, err
+}
+
+// Sign implements istanbul.Backend.Sign
+func (sb *backend) Sign(data []byte) ([]byte, error) {
+ hashData := crypto.Keccak256(data)
+ return crypto.Sign(hashData, sb.privateKey)
+}
+
+// CheckSignature implements istanbul.Backend.CheckSignature
+func (sb *backend) CheckSignature(data []byte, address common.Address, sig []byte) error {
+ signer, err := istanbul.GetSignatureAddress(data, sig)
+ if err != nil {
+ log.Error("Failed to get signer address", "err", err)
+ return err
+ }
+ // Compare derived addresses
+ if signer != address {
+ return errInvalidSignature
+ }
+ return nil
+}
+
+// HasPropsal implements istanbul.Backend.HashBlock
+func (sb *backend) HasPropsal(hash common.Hash, number *big.Int) bool {
+ return sb.chain.GetHeader(hash, number.Uint64()) != nil
+}
+
+// GetProposer implements istanbul.Backend.GetProposer
+func (sb *backend) GetProposer(number uint64) common.Address {
+ if h := sb.chain.GetHeaderByNumber(number); h != nil {
+ a, _ := sb.Author(h)
+ return a
+ }
+ return common.Address{}
+}
+
+// ParentValidators implements istanbul.Backend.GetParentValidators
+func (sb *backend) ParentValidators(proposal istanbul.Proposal) istanbul.ValidatorSet {
+ if block, ok := proposal.(*types.Block); ok {
+ return sb.getValidators(block.Number().Uint64()-1, block.ParentHash())
+ }
+ return validator.NewSet(nil, sb.config.ProposerPolicy)
+}
+
+func (sb *backend) getValidators(number uint64, hash common.Hash) istanbul.ValidatorSet {
+ snap, err := sb.snapshot(sb.chain, number, hash, nil)
+ if err != nil {
+ return validator.NewSet(nil, sb.config.ProposerPolicy)
+ }
+ return snap.ValSet
+}
+
+func (sb *backend) LastProposal() (istanbul.Proposal, common.Address) {
+ block := sb.currentBlock()
+
+ var proposer common.Address
+ if block.Number().Cmp(common.Big0) > 0 {
+ var err error
+ proposer, err = sb.Author(block.Header())
+ if err != nil {
+ sb.logger.Error("Failed to get block proposer", "err", err)
+ return nil, common.Address{}
+ }
+ }
+
+ // Return header only block here since we don't need block body
+ return block, proposer
+}
+
+func (sb *backend) HasBadProposal(hash common.Hash) bool {
+ if sb.hasBadBlock == nil {
+ return false
+ }
+ return sb.hasBadBlock(hash)
+}
diff --git a/consensus/istanbul/backend/backend_test.go b/consensus/istanbul/backend/backend_test.go
new file mode 100644
index 0000000000..822c0f5ec9
--- /dev/null
+++ b/consensus/istanbul/backend/backend_test.go
@@ -0,0 +1,239 @@
+// 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 .
+
+package backend
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "sort"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+)
+
+func TestSign(t *testing.T) {
+ b := newBackend()
+ data := []byte("Here is a string....")
+ sig, err := b.Sign(data)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ //Check signature recover
+ hashData := crypto.Keccak256([]byte(data))
+ pubkey, _ := crypto.Ecrecover(hashData, sig)
+ var signer common.Address
+ copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
+ if signer != getAddress() {
+ t.Errorf("address mismatch: have %v, want %s", signer.Hex(), getAddress().Hex())
+ }
+}
+
+func TestCheckSignature(t *testing.T) {
+ key, _ := generatePrivateKey()
+ data := []byte("Here is a string....")
+ hashData := crypto.Keccak256([]byte(data))
+ sig, _ := crypto.Sign(hashData, key)
+ b := newBackend()
+ a := getAddress()
+ err := b.CheckSignature(data, a, sig)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ a = getInvalidAddress()
+ err = b.CheckSignature(data, a, sig)
+ if err != errInvalidSignature {
+ t.Errorf("error mismatch: have %v, want %v", err, errInvalidSignature)
+ }
+}
+
+func TestCheckValidatorSignature(t *testing.T) {
+ vset, keys := newTestValidatorSet(5)
+
+ // 1. Positive test: sign with validator's key should succeed
+ data := []byte("dummy data")
+ hashData := crypto.Keccak256([]byte(data))
+ for i, k := range keys {
+ // Sign
+ sig, err := crypto.Sign(hashData, k)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ // CheckValidatorSignature should succeed
+ addr, err := istanbul.CheckValidatorSignature(vset, data, sig)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ validator := vset.GetByIndex(uint64(i))
+ if addr != validator.Address() {
+ t.Errorf("validator address mismatch: have %v, want %v", addr, validator.Address())
+ }
+ }
+
+ // 2. Negative test: sign with any key other than validator's key should return error
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ // Sign
+ sig, err := crypto.Sign(hashData, key)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+
+ // CheckValidatorSignature should return ErrUnauthorizedAddress
+ addr, err := istanbul.CheckValidatorSignature(vset, data, sig)
+ if err != istanbul.ErrUnauthorizedAddress {
+ t.Errorf("error mismatch: have %v, want %v", err, istanbul.ErrUnauthorizedAddress)
+ }
+ emptyAddr := common.Address{}
+ if addr != emptyAddr {
+ t.Errorf("address mismatch: have %v, want %v", addr, emptyAddr)
+ }
+}
+
+func TestCommit(t *testing.T) {
+ backend := newBackend()
+
+ commitCh := make(chan *types.Block)
+ // Case: it's a proposer, so the backend.commit will receive channel result from backend.Commit function
+ testCases := []struct {
+ expectedErr error
+ expectedSignature [][]byte
+ expectedBlock func() *types.Block
+ }{
+ {
+ // normal case
+ nil,
+ [][]byte{append([]byte{1}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-1)...)},
+ func() *types.Block {
+ chain, engine := newBlockChain(1)
+ block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
+ expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block)
+ return expectedBlock
+ },
+ },
+ {
+ // invalid signature
+ errInvalidCommittedSeals,
+ nil,
+ func() *types.Block {
+ chain, engine := newBlockChain(1)
+ block := makeBlockWithoutSeal(chain, engine, chain.Genesis())
+ expectedBlock, _ := engine.updateBlock(engine.chain.GetHeader(block.ParentHash(), block.NumberU64()-1), block)
+ return expectedBlock
+ },
+ },
+ }
+
+ for _, test := range testCases {
+ expBlock := test.expectedBlock()
+ go func() {
+ result := <-backend.commitCh
+ commitCh <- result
+ }()
+
+ backend.proposedBlockHash = expBlock.Hash()
+ if err := backend.Commit(expBlock, test.expectedSignature); err != nil {
+ if err != test.expectedErr {
+ t.Errorf("error mismatch: have %v, want %v", err, test.expectedErr)
+ }
+ }
+
+ if test.expectedErr == nil {
+ // to avoid race condition is occurred by goroutine
+ select {
+ case result := <-commitCh:
+ if result.Hash() != expBlock.Hash() {
+ t.Errorf("hash mismatch: have %v, want %v", result.Hash(), expBlock.Hash())
+ }
+ case <-time.After(10 * time.Second):
+ t.Fatal("timeout")
+ }
+ }
+ }
+}
+
+func TestGetProposer(t *testing.T) {
+ chain, engine := newBlockChain(1)
+ block := makeBlock(chain, engine, chain.Genesis())
+ chain.InsertChain(types.Blocks{block})
+ expected := engine.GetProposer(1)
+ actual := engine.Address()
+ if actual != expected {
+ t.Errorf("proposer mismatch: have %v, want %v", actual.Hex(), expected.Hex())
+ }
+}
+
+/**
+ * SimpleBackend
+ * Private key: bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1
+ * Public key: 04a2bfb0f7da9e1b9c0c64e14f87e8fb82eb0144e97c25fe3a977a921041a50976984d18257d2495e7bfd3d4b280220217f429287d25ecdf2b0d7c0f7aae9aa624
+ * Address: 0x70524d664ffe731100208a0154e556f9bb679ae6
+ */
+func getAddress() common.Address {
+ return common.HexToAddress("0x70524d664ffe731100208a0154e556f9bb679ae6")
+}
+
+func getInvalidAddress() common.Address {
+ return common.HexToAddress("0x9535b2e7faaba5288511d89341d94a38063a349b")
+}
+
+func generatePrivateKey() (*ecdsa.PrivateKey, error) {
+ key := "bb047e5940b6d83354d9432db7c449ac8fca2248008aaa7271369880f9f11cc1"
+ return crypto.HexToECDSA(key)
+}
+
+func newTestValidatorSet(n int) (istanbul.ValidatorSet, []*ecdsa.PrivateKey) {
+ // generate validators
+ keys := make(Keys, n)
+ addrs := make([]common.Address, n)
+ for i := 0; i < n; i++ {
+ privateKey, _ := crypto.GenerateKey()
+ keys[i] = privateKey
+ addrs[i] = crypto.PubkeyToAddress(privateKey.PublicKey)
+ }
+ vset := validator.NewSet(addrs, istanbul.RoundRobin)
+ sort.Sort(keys) //Keys need to be sorted by its public key address
+ return vset, keys
+}
+
+type Keys []*ecdsa.PrivateKey
+
+func (slice Keys) Len() int {
+ return len(slice)
+}
+
+func (slice Keys) Less(i, j int) bool {
+ return strings.Compare(crypto.PubkeyToAddress(slice[i].PublicKey).String(), crypto.PubkeyToAddress(slice[j].PublicKey).String()) < 0
+}
+
+func (slice Keys) Swap(i, j int) {
+ slice[i], slice[j] = slice[j], slice[i]
+}
+
+func newBackend() (b *backend) {
+ _, b = newBlockChain(4)
+ key, _ := generatePrivateKey()
+ b.privateKey = key
+ return
+}
diff --git a/consensus/istanbul/backend/engine.go b/consensus/istanbul/backend/engine.go
new file mode 100644
index 0000000000..a365abf561
--- /dev/null
+++ b/consensus/istanbul/backend/engine.go
@@ -0,0 +1,717 @@
+// 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 .
+
+package backend
+
+import (
+ "bytes"
+ "errors"
+ "math/big"
+ "math/rand"
+ "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"
+ istanbulCore "github.com/ethereum/go-ethereum/consensus/istanbul/core"
+ "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
+ "github.com/ethereum/go-ethereum/core/state"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto/sha3"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/rpc"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+const (
+ checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
+ inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory
+ inmemoryPeers = 40
+ inmemoryMessages = 1024
+)
+
+var (
+ // errInvalidProposal is returned when a prposal is malformed.
+ errInvalidProposal = errors.New("invalid proposal")
+ // errInvalidSignature is returned when given signature is not signed by given
+ // address.
+ errInvalidSignature = errors.New("invalid signature")
+ // errUnknownBlock is returned when the list of validators is requested for a block
+ // that is not part of the local blockchain.
+ errUnknownBlock = errors.New("unknown block")
+ // errUnauthorized is returned if a header is signed by a non authorized entity.
+ errUnauthorized = errors.New("unauthorized")
+ // errInvalidDifficulty is returned if the difficulty of a block is not 1
+ errInvalidDifficulty = errors.New("invalid difficulty")
+ // errInvalidExtraDataFormat is returned when the extra data format is incorrect
+ errInvalidExtraDataFormat = errors.New("invalid extra data format")
+ // errInvalidMixDigest is returned if a block's mix digest is not Istanbul digest.
+ errInvalidMixDigest = errors.New("invalid Istanbul mix digest")
+ // errInvalidNonce is returned if a block's nonce is invalid
+ errInvalidNonce = errors.New("invalid nonce")
+ // errInvalidUncleHash is returned if a block contains an non-empty uncle list.
+ errInvalidUncleHash = errors.New("non empty uncle hash")
+ // errInconsistentValidatorSet is returned if the validator set is inconsistent
+ errInconsistentValidatorSet = errors.New("non empty uncle hash")
+ // errInvalidTimestamp is returned if the timestamp of a block is lower than the previous block's timestamp + the minimum block period.
+ errInvalidTimestamp = errors.New("invalid timestamp")
+ // errInvalidVotingChain is returned if an authorization list is attempted to
+ // be modified via out-of-range or non-contiguous headers.
+ errInvalidVotingChain = errors.New("invalid voting chain")
+ // errInvalidVote is returned if a nonce value is something else that the two
+ // allowed constants of 0x00..0 or 0xff..f.
+ errInvalidVote = errors.New("vote nonce not 0x00..0 or 0xff..f")
+ // errInvalidCommittedSeals is returned if the committed seal is not signed by any of parent validators.
+ errInvalidCommittedSeals = errors.New("invalid committed seals")
+ // errEmptyCommittedSeals is returned if the field of committed seals is zero.
+ errEmptyCommittedSeals = errors.New("zero committed seals")
+ // errMismatchTxhashes is returned if the TxHash in header is mismatch.
+ errMismatchTxhashes = errors.New("mismatch transactions hashes")
+)
+var (
+ defaultDifficulty = big.NewInt(1)
+ nilUncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
+ emptyNonce = types.BlockNonce{}
+ now = time.Now
+
+ nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new validator
+ nonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a validator.
+
+ inmemoryAddresses = 20 // Number of recent addresses from ecrecover
+ recentAddresses, _ = lru.NewARC(inmemoryAddresses)
+)
+
+// Author retrieves the Ethereum address of the account that minted the given
+// block, which may be different from the header's coinbase if a consensus
+// engine is based on signatures.
+func (sb *backend) Author(header *types.Header) (common.Address, error) {
+ return ecrecover(header)
+}
+
+// VerifyHeader checks whether a header conforms to the consensus rules of a
+// given engine. Verifying the seal may be done optionally here, or explicitly
+// via the VerifySeal method.
+func (sb *backend) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
+ return sb.verifyHeader(chain, header, nil)
+}
+
+// verifyHeader checks whether a header conforms to the consensus rules.The
+// caller may optionally pass in a batch of parents (ascending order) to avoid
+// looking those up from the database. This is useful for concurrently verifying
+// a batch of new headers.
+func (sb *backend) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+ if header.Number == nil {
+ return errUnknownBlock
+ }
+
+ // Don't waste time checking blocks from the future
+ if header.Time.Cmp(big.NewInt(now().Unix())) > 0 {
+ return consensus.ErrFutureBlock
+ }
+
+ // Ensure that the extra data format is satisfied
+ if _, err := types.ExtractIstanbulExtra(header); err != nil {
+ return errInvalidExtraDataFormat
+ }
+
+ // Ensure that the coinbase is valid
+ if header.Nonce != (emptyNonce) && !bytes.Equal(header.Nonce[:], nonceAuthVote) && !bytes.Equal(header.Nonce[:], nonceDropVote) {
+ return errInvalidNonce
+ }
+ // Ensure that the mix digest is zero as we don't have fork protection currently
+ if header.MixDigest != types.IstanbulDigest {
+ return errInvalidMixDigest
+ }
+ // Ensure that the block doesn't contain any uncles which are meaningless in Istanbul
+ if header.UncleHash != nilUncleHash {
+ return errInvalidUncleHash
+ }
+ // Ensure that the block's difficulty is meaningful (may not be correct at this point)
+ if header.Difficulty == nil || header.Difficulty.Cmp(defaultDifficulty) != 0 {
+ return errInvalidDifficulty
+ }
+
+ return sb.verifyCascadingFields(chain, header, parents)
+}
+
+// verifyCascadingFields verifies all the header fields that are not standalone,
+// rather depend on a batch of previous headers. The caller may optionally pass
+// in a batch of parents (ascending order) to avoid looking those up from the
+// database. This is useful for concurrently verifying a batch of new headers.
+func (sb *backend) verifyCascadingFields(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+ // The genesis block is the always valid dead-end
+ number := header.Number.Uint64()
+ if number == 0 {
+ return nil
+ }
+ // Ensure that the block's timestamp isn't too close to it's parent
+ var parent *types.Header
+ if len(parents) > 0 {
+ parent = parents[len(parents)-1]
+ } else {
+ parent = chain.GetHeader(header.ParentHash, number-1)
+ }
+ if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
+ return consensus.ErrUnknownAncestor
+ }
+ if parent.Time.Uint64()+sb.config.BlockPeriod > header.Time.Uint64() {
+ return errInvalidTimestamp
+ }
+ // Verify validators in extraData. Validators in snapshot and extraData should be the same.
+ snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents)
+ if err != nil {
+ return err
+ }
+ validators := make([]byte, len(snap.validators())*common.AddressLength)
+ for i, validator := range snap.validators() {
+ copy(validators[i*common.AddressLength:], validator[:])
+ }
+ if err := sb.verifySigner(chain, header, parents); err != nil {
+ return err
+ }
+
+ return sb.verifyCommittedSeals(chain, header, parents)
+}
+
+// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
+// concurrently. The method returns a quit channel to abort the operations and
+// a results channel to retrieve the async verifications (the order is that of
+// the input slice).
+func (sb *backend) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
+ abort := make(chan struct{})
+ results := make(chan error, len(headers))
+ go func() {
+ for i, header := range headers {
+ err := sb.verifyHeader(chain, header, headers[:i])
+
+ select {
+ case <-abort:
+ return
+ case results <- err:
+ }
+ }
+ }()
+ return abort, results
+}
+
+// VerifyUncles verifies that the given block's uncles conform to the consensus
+// rules of a given engine.
+func (sb *backend) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
+ if len(block.Uncles()) > 0 {
+ return errInvalidUncleHash
+ }
+ return nil
+}
+
+// verifySigner checks whether the signer is in parent's validator set
+func (sb *backend) verifySigner(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+ // Verifying the genesis block is not supported
+ number := header.Number.Uint64()
+ if number == 0 {
+ return errUnknownBlock
+ }
+
+ // Retrieve the snapshot needed to verify this header and cache it
+ snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents)
+ if err != nil {
+ return err
+ }
+
+ // resolve the authorization key and check against signers
+ signer, err := ecrecover(header)
+ if err != nil {
+ return err
+ }
+
+ // Signer should be in the validator set of previous block's extraData.
+ if _, v := snap.ValSet.GetByAddress(signer); v == nil {
+ return errUnauthorized
+ }
+ return nil
+}
+
+// verifyCommittedSeals checks whether every committed seal is signed by one of the parent's validators
+func (sb *backend) verifyCommittedSeals(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
+ number := header.Number.Uint64()
+ // We don't need to verify committed seals in the genesis block
+ if number == 0 {
+ return nil
+ }
+
+ // Retrieve the snapshot needed to verify this header and cache it
+ snap, err := sb.snapshot(chain, number-1, header.ParentHash, parents)
+ if err != nil {
+ return err
+ }
+
+ extra, err := types.ExtractIstanbulExtra(header)
+ if err != nil {
+ return err
+ }
+ // The length of Committed seals should be larger than 0
+ if len(extra.CommittedSeal) == 0 {
+ return errEmptyCommittedSeals
+ }
+
+ validators := snap.ValSet.Copy()
+ // Check whether the committed seals are generated by parent's validators
+ validSeal := 0
+ proposalSeal := istanbulCore.PrepareCommittedSeal(header.Hash())
+ // 1. Get committed seals from current header
+ for _, seal := range extra.CommittedSeal {
+ // 2. Get the original address by seal and parent block hash
+ addr, err := istanbul.GetSignatureAddress(proposalSeal, seal)
+ if err != nil {
+ sb.logger.Error("not a valid address", "err", err)
+ return errInvalidSignature
+ }
+ // Every validator can have only one seal. If more than one seals are signed by a
+ // validator, the validator cannot be found and errInvalidCommittedSeals is returned.
+ if validators.RemoveValidator(addr) {
+ validSeal += 1
+ } else {
+ return errInvalidCommittedSeals
+ }
+ }
+
+ // The length of validSeal should be larger than number of faulty node + 1
+ if validSeal <= 2*snap.ValSet.F() {
+ return errInvalidCommittedSeals
+ }
+
+ return nil
+}
+
+// VerifySeal checks whether the crypto seal on a header is valid according to
+// the consensus rules of the given engine.
+func (sb *backend) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
+ // get parent header and ensure the signer is in parent's validator set
+ number := header.Number.Uint64()
+ if number == 0 {
+ return errUnknownBlock
+ }
+
+ // ensure that the difficulty equals to defaultDifficulty
+ if header.Difficulty.Cmp(defaultDifficulty) != 0 {
+ return errInvalidDifficulty
+ }
+ return sb.verifySigner(chain, header, nil)
+}
+
+// Prepare initializes the consensus fields of a block header according to the
+// rules of a particular engine. The changes are executed inline.
+func (sb *backend) Prepare(chain consensus.ChainReader, header *types.Header) error {
+ // unused fields, force to set to empty
+ header.Coinbase = common.Address{}
+ header.Nonce = emptyNonce
+ header.MixDigest = types.IstanbulDigest
+
+ // copy the parent extra data as the header extra data
+ number := header.Number.Uint64()
+ parent := chain.GetHeader(header.ParentHash, number-1)
+ if parent == nil {
+ return consensus.ErrUnknownAncestor
+ }
+ // use the same difficulty for all blocks
+ header.Difficulty = defaultDifficulty
+
+ // Assemble the voting snapshot
+ snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil)
+ if err != nil {
+ return err
+ }
+
+ // get valid candidate list
+ sb.candidatesLock.RLock()
+ var addresses []common.Address
+ var authorizes []bool
+ for address, authorize := range sb.candidates {
+ if snap.checkVote(address, authorize) {
+ addresses = append(addresses, address)
+ authorizes = append(authorizes, authorize)
+ }
+ }
+ sb.candidatesLock.RUnlock()
+
+ // pick one of the candidates randomly
+ if len(addresses) > 0 {
+ index := rand.Intn(len(addresses))
+ // add validator voting in coinbase
+ header.Coinbase = addresses[index]
+ if authorizes[index] {
+ copy(header.Nonce[:], nonceAuthVote)
+ } else {
+ copy(header.Nonce[:], nonceDropVote)
+ }
+ }
+
+ // add validators in snapshot to extraData's validators section
+ extra, err := prepareExtra(header, snap.validators())
+ if err != nil {
+ return err
+ }
+ header.Extra = extra
+
+ // set header's timestamp
+ header.Time = new(big.Int).Add(parent.Time, new(big.Int).SetUint64(sb.config.BlockPeriod))
+ if header.Time.Int64() < time.Now().Unix() {
+ header.Time = big.NewInt(time.Now().Unix())
+ }
+ return nil
+}
+
+// Finalize runs any post-transaction state modifications (e.g. block rewards)
+// and assembles the final block.
+//
+// Note, the block header and state database might be updated to reflect any
+// consensus rules that happen at finalization (e.g. block rewards).
+func (sb *backend) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction,
+ uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
+ // No block rewards in Istanbul, so the state remains as is and uncles are dropped
+ header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
+ header.UncleHash = nilUncleHash
+
+ // Assemble and return the final block for sealing
+ return types.NewBlock(header, txs, nil, receipts), nil
+}
+
+// Seal generates a new block for the given input block with the local miner's
+// seal place on top.
+func (sb *backend) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
+ // update the block header timestamp and signature and propose the block to core engine
+ header := block.Header()
+ number := header.Number.Uint64()
+
+ // Bail out if we're unauthorized to sign a block
+ snap, err := sb.snapshot(chain, number-1, header.ParentHash, nil)
+ if err != nil {
+ return nil, err
+ }
+ if _, v := snap.ValSet.GetByAddress(sb.address); v == nil {
+ return nil, errUnauthorized
+ }
+
+ parent := chain.GetHeader(header.ParentHash, number-1)
+ if parent == nil {
+ return nil, consensus.ErrUnknownAncestor
+ }
+ block, err = sb.updateBlock(parent, block)
+ if err != nil {
+ return nil, err
+ }
+
+ // wait for the timestamp of header, use this to adjust the block period
+ delay := time.Unix(block.Header().Time.Int64(), 0).Sub(now())
+ select {
+ case <-time.After(delay):
+ case <-stop:
+ return nil, nil
+ }
+
+ // get the proposed block hash and clear it if the seal() is completed.
+ sb.sealMu.Lock()
+ sb.proposedBlockHash = block.Hash()
+ clear := func() {
+ sb.proposedBlockHash = common.Hash{}
+ sb.sealMu.Unlock()
+ }
+ defer clear()
+
+ // post block into Istanbul engine
+ go sb.EventMux().Post(istanbul.RequestEvent{
+ Proposal: block,
+ })
+
+ for {
+ select {
+ case result := <-sb.commitCh:
+ // if the block hash and the hash from channel are the same,
+ // return the result. Otherwise, keep waiting the next hash.
+ if block.Hash() == result.Hash() {
+ return result, nil
+ }
+ case <-stop:
+ return nil, nil
+ }
+ }
+}
+
+// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
+// that a new block should have based on the previous blocks in the chain and the
+// current signer.
+func (sb *backend) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
+ return defaultDifficulty
+}
+
+// update timestamp and signature of the block based on its number of transactions
+func (sb *backend) updateBlock(parent *types.Header, block *types.Block) (*types.Block, error) {
+ header := block.Header()
+ // sign the hash
+ seal, err := sb.Sign(sigHash(header).Bytes())
+ if err != nil {
+ return nil, err
+ }
+
+ err = writeSeal(header, seal)
+ if err != nil {
+ return nil, err
+ }
+
+ return block.WithSeal(header), nil
+}
+
+// APIs returns the RPC APIs this consensus engine provides.
+func (sb *backend) APIs(chain consensus.ChainReader) []rpc.API {
+ return []rpc.API{{
+ Namespace: "istanbul",
+ Version: "1.0",
+ Service: &API{chain: chain, istanbul: sb},
+ Public: true,
+ }}
+}
+
+// Start implements consensus.Istanbul.Start
+func (sb *backend) Start(chain consensus.ChainReader, currentBlock func() *types.Block, hasBadBlock func(hash common.Hash) bool) error {
+ sb.coreMu.Lock()
+ defer sb.coreMu.Unlock()
+ if sb.coreStarted {
+ return istanbul.ErrStartedEngine
+ }
+
+ // clear previous data
+ sb.proposedBlockHash = common.Hash{}
+ if sb.commitCh != nil {
+ close(sb.commitCh)
+ }
+ sb.commitCh = make(chan *types.Block, 1)
+
+ sb.chain = chain
+ sb.currentBlock = currentBlock
+ sb.hasBadBlock = hasBadBlock
+
+ if err := sb.core.Start(); err != nil {
+ return err
+ }
+
+ sb.coreStarted = true
+ return nil
+}
+
+// Stop implements consensus.Istanbul.Stop
+func (sb *backend) Stop() error {
+ sb.coreMu.Lock()
+ defer sb.coreMu.Unlock()
+ if !sb.coreStarted {
+ return istanbul.ErrStoppedEngine
+ }
+ if err := sb.core.Stop(); err != nil {
+ return err
+ }
+ sb.coreStarted = false
+ return nil
+}
+
+// snapshot retrieves the authorization snapshot at a given point in time.
+func (sb *backend) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
+ // Search for a snapshot in memory or on disk for checkpoints
+ var (
+ headers []*types.Header
+ snap *Snapshot
+ )
+ for snap == nil {
+ // If an in-memory snapshot was found, use that
+ if s, ok := sb.recents.Get(hash); ok {
+ snap = s.(*Snapshot)
+ break
+ }
+ // If an on-disk checkpoint snapshot can be found, use that
+ if number%checkpointInterval == 0 {
+ if s, err := loadSnapshot(sb.config.Epoch, sb.db, hash); err == nil {
+ log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
+ snap = s
+ break
+ }
+ }
+ // If we're at block zero, make a snapshot
+ if number == 0 {
+ genesis := chain.GetHeaderByNumber(0)
+ if err := sb.VerifyHeader(chain, genesis, false); err != nil {
+ return nil, err
+ }
+ istanbulExtra, err := types.ExtractIstanbulExtra(genesis)
+ if err != nil {
+ return nil, err
+ }
+ snap = newSnapshot(sb.config.Epoch, 0, genesis.Hash(), validator.NewSet(istanbulExtra.Validators, sb.config.ProposerPolicy))
+ if err := snap.store(sb.db); err != nil {
+ return nil, err
+ }
+ log.Trace("Stored genesis voting snapshot to disk")
+ break
+ }
+ // No snapshot for this header, gather the header and move backward
+ var header *types.Header
+ if len(parents) > 0 {
+ // If we have explicit parents, pick from there (enforced)
+ header = parents[len(parents)-1]
+ if header.Hash() != hash || header.Number.Uint64() != number {
+ return nil, consensus.ErrUnknownAncestor
+ }
+ parents = parents[:len(parents)-1]
+ } else {
+ // No explicit parents (or no more left), reach out to the database
+ header = chain.GetHeader(hash, number)
+ if header == nil {
+ return nil, consensus.ErrUnknownAncestor
+ }
+ }
+ headers = append(headers, header)
+ number, hash = number-1, header.ParentHash
+ }
+ // Previous snapshot found, apply any pending headers on top of it
+ for i := 0; i < len(headers)/2; i++ {
+ headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i]
+ }
+ snap, err := snap.apply(headers)
+ if err != nil {
+ return nil, err
+ }
+ sb.recents.Add(snap.Hash, snap)
+
+ // If we've generated a new checkpoint snapshot, save to disk
+ if snap.Number%checkpointInterval == 0 && len(headers) > 0 {
+ if err = snap.store(sb.db); err != nil {
+ return nil, err
+ }
+ log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
+ }
+ return snap, err
+}
+
+// FIXME: Need to update this for Istanbul
+// sigHash returns the hash which is used as input for the Istanbul
+// signing. It is the hash of the entire header apart from the 65 byte signature
+// contained at the end of the extra data.
+//
+// Note, the method requires the extra data to be at least 65 bytes, otherwise it
+// panics. This is done to avoid accidentally using both forms (signature present
+// or not), which could be abused to produce different hashes for the same header.
+func sigHash(header *types.Header) (hash common.Hash) {
+ hasher := sha3.NewKeccak256()
+
+ // Clean seal is required for calculating proposer seal.
+ rlp.Encode(hasher, types.IstanbulFilteredHeader(header, false))
+ hasher.Sum(hash[:0])
+ return hash
+}
+
+// ecrecover extracts the Ethereum account address from a signed header.
+func ecrecover(header *types.Header) (common.Address, error) {
+ hash := header.Hash()
+ if addr, ok := recentAddresses.Get(hash); ok {
+ return addr.(common.Address), nil
+ }
+
+ // Retrieve the signature from the header extra-data
+ istanbulExtra, err := types.ExtractIstanbulExtra(header)
+ if err != nil {
+ return common.Address{}, err
+ }
+
+ addr, err := istanbul.GetSignatureAddress(sigHash(header).Bytes(), istanbulExtra.Seal)
+ if err != nil {
+ return addr, err
+ }
+ recentAddresses.Add(hash, addr)
+ return addr, nil
+}
+
+// prepareExtra returns a extra-data of the given header and validators
+func prepareExtra(header *types.Header, vals []common.Address) ([]byte, error) {
+ var buf bytes.Buffer
+
+ // compensate the lack bytes if header.Extra is not enough IstanbulExtraVanity bytes.
+ if len(header.Extra) < types.IstanbulExtraVanity {
+ header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity-len(header.Extra))...)
+ }
+ buf.Write(header.Extra[:types.IstanbulExtraVanity])
+
+ ist := &types.IstanbulExtra{
+ Validators: vals,
+ Seal: []byte{},
+ CommittedSeal: [][]byte{},
+ }
+
+ payload, err := rlp.EncodeToBytes(&ist)
+ if err != nil {
+ return nil, err
+ }
+
+ return append(buf.Bytes(), payload...), nil
+}
+
+// writeSeal writes the extra-data field of the given header with the given seals.
+// suggest to rename to writeSeal.
+func writeSeal(h *types.Header, seal []byte) error {
+ if len(seal)%types.IstanbulExtraSeal != 0 {
+ return errInvalidSignature
+ }
+
+ istanbulExtra, err := types.ExtractIstanbulExtra(h)
+ if err != nil {
+ return err
+ }
+
+ istanbulExtra.Seal = seal
+ payload, err := rlp.EncodeToBytes(&istanbulExtra)
+ if err != nil {
+ return err
+ }
+
+ h.Extra = append(h.Extra[:types.IstanbulExtraVanity], payload...)
+ return nil
+}
+
+// writeCommittedSeals writes the extra-data field of a block header with given committed seals.
+func writeCommittedSeals(h *types.Header, committedSeals [][]byte) error {
+ if len(committedSeals) == 0 {
+ return errInvalidCommittedSeals
+ }
+
+ for _, seal := range committedSeals {
+ if len(seal) != types.IstanbulExtraSeal {
+ return errInvalidCommittedSeals
+ }
+ }
+
+ istanbulExtra, err := types.ExtractIstanbulExtra(h)
+ if err != nil {
+ return err
+ }
+
+ istanbulExtra.CommittedSeal = make([][]byte, len(committedSeals))
+ copy(istanbulExtra.CommittedSeal, committedSeals)
+
+ payload, err := rlp.EncodeToBytes(&istanbulExtra)
+ if err != nil {
+ return err
+ }
+
+ h.Extra = append(h.Extra[:types.IstanbulExtraVanity], payload...)
+ return nil
+}
diff --git a/consensus/istanbul/backend/engine_test.go b/consensus/istanbul/backend/engine_test.go
new file mode 100644
index 0000000000..da37e248f6
--- /dev/null
+++ b/consensus/istanbul/backend/engine_test.go
@@ -0,0 +1,549 @@
+// 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 .
+
+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{})
+ 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 = ¶ms.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),
+ 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)
+ block, _ = engine.Seal(chain, block, nil)
+ return block
+}
+
+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() {
+ 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()
+ finalBlock, err := engine.Seal(chain, block, stop)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ 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() {
+ 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)
+ t.Error("seal should not be completed")
+ }
+ go seal()
+
+ const timeoutDura = 2 * time.Second
+ timeout := time.NewTimer(timeoutDura)
+ <-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)
+
+ finalBlock, err := engine.Seal(chain, block, nil)
+ if err != nil {
+ t.Errorf("error mismatch: have %v, want nil", err)
+ }
+ 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)
+ }
+}
diff --git a/consensus/istanbul/backend/handler.go b/consensus/istanbul/backend/handler.go
new file mode 100644
index 0000000000..a338009451
--- /dev/null
+++ b/consensus/istanbul/backend/handler.go
@@ -0,0 +1,103 @@
+// 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 .
+
+package backend
+
+import (
+ "errors"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ "github.com/ethereum/go-ethereum/p2p"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+const (
+ istanbulMsg = 0x11
+)
+
+var (
+ // errDecodeFailed is returned when decode message fails
+ errDecodeFailed = errors.New("fail to decode istanbul message")
+)
+
+// Protocol implements consensus.Engine.Protocol
+func (sb *backend) Protocol() consensus.Protocol {
+ return consensus.Protocol{
+ Name: "istanbul",
+ Versions: []uint{64},
+ Lengths: []uint64{18},
+ }
+}
+
+// HandleMsg implements consensus.Handler.HandleMsg
+func (sb *backend) HandleMsg(addr common.Address, msg p2p.Msg) (bool, error) {
+ sb.coreMu.Lock()
+ defer sb.coreMu.Unlock()
+
+ if msg.Code == istanbulMsg {
+ if !sb.coreStarted {
+ return true, istanbul.ErrStoppedEngine
+ }
+
+ var data []byte
+ if err := msg.Decode(&data); err != nil {
+ return true, errDecodeFailed
+ }
+
+ hash := istanbul.RLPHash(data)
+
+ // Mark peer's message
+ ms, ok := sb.recentMessages.Get(addr)
+ var m *lru.ARCCache
+ if ok {
+ m, _ = ms.(*lru.ARCCache)
+ } else {
+ m, _ = lru.NewARC(inmemoryMessages)
+ sb.recentMessages.Add(addr, m)
+ }
+ m.Add(hash, true)
+
+ // Mark self known message
+ if _, ok := sb.knownMessages.Get(hash); ok {
+ return true, nil
+ }
+ sb.knownMessages.Add(hash, true)
+
+ go sb.istanbulEventMux.Post(istanbul.MessageEvent{
+ Payload: data,
+ })
+
+ return true, nil
+ }
+ return false, nil
+}
+
+// SetBroadcaster implements consensus.Handler.SetBroadcaster
+func (sb *backend) SetBroadcaster(broadcaster consensus.Broadcaster) {
+ sb.broadcaster = broadcaster
+}
+
+func (sb *backend) NewChainHead() error {
+ sb.coreMu.RLock()
+ defer sb.coreMu.RUnlock()
+ if !sb.coreStarted {
+ return istanbul.ErrStoppedEngine
+ }
+ go sb.istanbulEventMux.Post(istanbul.FinalCommittedEvent{})
+ return nil
+}
diff --git a/consensus/istanbul/backend/handler_test.go b/consensus/istanbul/backend/handler_test.go
new file mode 100644
index 0000000000..690a586be8
--- /dev/null
+++ b/consensus/istanbul/backend/handler_test.go
@@ -0,0 +1,72 @@
+// Copyright 2015 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 .
+
+package backend
+
+import (
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ "github.com/ethereum/go-ethereum/p2p"
+ "github.com/ethereum/go-ethereum/rlp"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+func TestIstanbulMessage(t *testing.T) {
+ _, backend := newBlockChain(1)
+
+ // generate one msg
+ data := []byte("data1")
+ hash := istanbul.RLPHash(data)
+ msg := makeMsg(istanbulMsg, data)
+ addr := common.StringToAddress("address")
+
+ // 1. this message should not be in cache
+ // for peers
+ if _, ok := backend.recentMessages.Get(addr); ok {
+ t.Fatalf("the cache of messages for this peer should be nil")
+ }
+
+ // for self
+ if _, ok := backend.knownMessages.Get(hash); ok {
+ t.Fatalf("the cache of messages should be nil")
+ }
+
+ // 2. this message should be in cache after we handle it
+ _, err := backend.HandleMsg(addr, msg)
+ if err != nil {
+ t.Fatalf("handle message failed: %v", err)
+ }
+ // for peers
+ if ms, ok := backend.recentMessages.Get(addr); ms == nil || !ok {
+ t.Fatalf("the cache of messages for this peer cannot be nil")
+ } else if m, ok := ms.(*lru.ARCCache); !ok {
+ t.Fatalf("the cache of messages for this peer cannot be casted")
+ } else if _, ok := m.Get(hash); !ok {
+ t.Fatalf("the cache of messages for this peer cannot be found")
+ }
+
+ // for self
+ if _, ok := backend.knownMessages.Get(hash); !ok {
+ t.Fatalf("the cache of messages cannot be found")
+ }
+}
+
+func makeMsg(msgcode uint64, data interface{}) p2p.Msg {
+ size, r, _ := rlp.EncodeToReader(data)
+ return p2p.Msg{Code: msgcode, Size: uint32(size), Payload: r}
+}
diff --git a/consensus/istanbul/backend/snapshot.go b/consensus/istanbul/backend/snapshot.go
new file mode 100644
index 0000000000..e369147f81
--- /dev/null
+++ b/consensus/istanbul/backend/snapshot.go
@@ -0,0 +1,321 @@
+// 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 .
+
+package backend
+
+import (
+ "bytes"
+ "encoding/json"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+)
+
+const (
+ dbKeySnapshotPrefix = "istanbul-snapshot"
+)
+
+// Vote represents a single vote that an authorized validator made to modify the
+// list of authorizations.
+type Vote struct {
+ Validator common.Address `json:"validator"` // Authorized validator that cast this vote
+ Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes)
+ Address common.Address `json:"address"` // Account being voted on to change its authorization
+ Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account
+}
+
+// Tally is a simple vote tally to keep the current score of votes. Votes that
+// go against the proposal aren't counted since it's equivalent to not voting.
+type Tally struct {
+ Authorize bool `json:"authorize"` // Whether the vote it about authorizing or kicking someone
+ Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal
+}
+
+// Snapshot is the state of the authorization voting at a given point in time.
+type Snapshot struct {
+ Epoch uint64 // The number of blocks after which to checkpoint and reset the pending votes
+
+ Number uint64 // Block number where the snapshot was created
+ Hash common.Hash // Block hash where the snapshot was created
+ Votes []*Vote // List of votes cast in chronological order
+ Tally map[common.Address]Tally // Current vote tally to avoid recalculating
+ ValSet istanbul.ValidatorSet // Set of authorized validators at this moment
+}
+
+// newSnapshot create a new snapshot with the specified startup parameters. This
+// method does not initialize the set of recent validators, so only ever use if for
+// the genesis block.
+func newSnapshot(epoch uint64, number uint64, hash common.Hash, valSet istanbul.ValidatorSet) *Snapshot {
+ snap := &Snapshot{
+ Epoch: epoch,
+ Number: number,
+ Hash: hash,
+ ValSet: valSet,
+ Tally: make(map[common.Address]Tally),
+ }
+ return snap
+}
+
+// loadSnapshot loads an existing snapshot from the database.
+func loadSnapshot(epoch uint64, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
+ blob, err := db.Get(append([]byte(dbKeySnapshotPrefix), hash[:]...))
+ if err != nil {
+ return nil, err
+ }
+ snap := new(Snapshot)
+ if err := json.Unmarshal(blob, snap); err != nil {
+ return nil, err
+ }
+ snap.Epoch = epoch
+
+ return snap, nil
+}
+
+// store inserts the snapshot into the database.
+func (s *Snapshot) store(db ethdb.Database) error {
+ blob, err := json.Marshal(s)
+ if err != nil {
+ return err
+ }
+ return db.Put(append([]byte(dbKeySnapshotPrefix), s.Hash[:]...), blob)
+}
+
+// copy creates a deep copy of the snapshot, though not the individual votes.
+func (s *Snapshot) copy() *Snapshot {
+ cpy := &Snapshot{
+ Epoch: s.Epoch,
+ Number: s.Number,
+ Hash: s.Hash,
+ ValSet: s.ValSet.Copy(),
+ Votes: make([]*Vote, len(s.Votes)),
+ Tally: make(map[common.Address]Tally),
+ }
+
+ for address, tally := range s.Tally {
+ cpy.Tally[address] = tally
+ }
+ copy(cpy.Votes, s.Votes)
+
+ return cpy
+}
+
+// checkVote return whether it's a valid vote
+func (s *Snapshot) checkVote(address common.Address, authorize bool) bool {
+ _, validator := s.ValSet.GetByAddress(address)
+ return (validator != nil && !authorize) || (validator == nil && authorize)
+}
+
+// cast adds a new vote into the tally.
+func (s *Snapshot) cast(address common.Address, authorize bool) bool {
+ // Ensure the vote is meaningful
+ if !s.checkVote(address, authorize) {
+ return false
+ }
+ // Cast the vote into an existing or new tally
+ if old, ok := s.Tally[address]; ok {
+ old.Votes++
+ s.Tally[address] = old
+ } else {
+ s.Tally[address] = Tally{Authorize: authorize, Votes: 1}
+ }
+ return true
+}
+
+// uncast removes a previously cast vote from the tally.
+func (s *Snapshot) uncast(address common.Address, authorize bool) bool {
+ // If there's no tally, it's a dangling vote, just drop
+ tally, ok := s.Tally[address]
+ if !ok {
+ return false
+ }
+ // Ensure we only revert counted votes
+ if tally.Authorize != authorize {
+ return false
+ }
+ // Otherwise revert the vote
+ if tally.Votes > 1 {
+ tally.Votes--
+ s.Tally[address] = tally
+ } else {
+ delete(s.Tally, address)
+ }
+ return true
+}
+
+// apply creates a new authorization snapshot by applying the given headers to
+// the original one.
+func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
+ // Allow passing in no headers for cleaner code
+ if len(headers) == 0 {
+ return s, nil
+ }
+ // Sanity check that the headers can be applied
+ for i := 0; i < len(headers)-1; i++ {
+ if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 {
+ return nil, errInvalidVotingChain
+ }
+ }
+ if headers[0].Number.Uint64() != s.Number+1 {
+ return nil, errInvalidVotingChain
+ }
+ // Iterate through the headers and create a new snapshot
+ snap := s.copy()
+
+ for _, header := range headers {
+ // Remove any votes on checkpoint blocks
+ number := header.Number.Uint64()
+ if number%s.Epoch == 0 {
+ snap.Votes = nil
+ snap.Tally = make(map[common.Address]Tally)
+ }
+ // Resolve the authorization key and check against validators
+ validator, err := ecrecover(header)
+ if err != nil {
+ return nil, err
+ }
+ if _, v := snap.ValSet.GetByAddress(validator); v == nil {
+ return nil, errUnauthorized
+ }
+
+ // Header authorized, discard any previous votes from the validator
+ for i, vote := range snap.Votes {
+ if vote.Validator == validator && vote.Address == header.Coinbase {
+ // Uncast the vote from the cached tally
+ snap.uncast(vote.Address, vote.Authorize)
+
+ // Uncast the vote from the chronological list
+ snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
+ break // only one vote allowed
+ }
+ }
+ // Tally up the new vote from the validator
+ var authorize bool
+ switch {
+ case bytes.Equal(header.Nonce[:], nonceAuthVote):
+ authorize = true
+ case bytes.Equal(header.Nonce[:], nonceDropVote):
+ authorize = false
+ default:
+ return nil, errInvalidVote
+ }
+ if snap.cast(header.Coinbase, authorize) {
+ snap.Votes = append(snap.Votes, &Vote{
+ Validator: validator,
+ Block: number,
+ Address: header.Coinbase,
+ Authorize: authorize,
+ })
+ }
+ // If the vote passed, update the list of validators
+ if tally := snap.Tally[header.Coinbase]; tally.Votes > snap.ValSet.Size()/2 {
+ if tally.Authorize {
+ snap.ValSet.AddValidator(header.Coinbase)
+ } else {
+ snap.ValSet.RemoveValidator(header.Coinbase)
+
+ // Discard any previous votes the deauthorized validator cast
+ for i := 0; i < len(snap.Votes); i++ {
+ if snap.Votes[i].Validator == header.Coinbase {
+ // Uncast the vote from the cached tally
+ snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize)
+
+ // Uncast the vote from the chronological list
+ snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
+
+ i--
+ }
+ }
+ }
+ // Discard any previous votes around the just changed account
+ for i := 0; i < len(snap.Votes); i++ {
+ if snap.Votes[i].Address == header.Coinbase {
+ snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
+ i--
+ }
+ }
+ delete(snap.Tally, header.Coinbase)
+ }
+ }
+ snap.Number += uint64(len(headers))
+ snap.Hash = headers[len(headers)-1].Hash()
+
+ return snap, nil
+}
+
+// validators retrieves the list of authorized validators in ascending order.
+func (s *Snapshot) validators() []common.Address {
+ validators := make([]common.Address, 0, s.ValSet.Size())
+ for _, validator := range s.ValSet.List() {
+ validators = append(validators, validator.Address())
+ }
+ for i := 0; i < len(validators); i++ {
+ for j := i + 1; j < len(validators); j++ {
+ if bytes.Compare(validators[i][:], validators[j][:]) > 0 {
+ validators[i], validators[j] = validators[j], validators[i]
+ }
+ }
+ }
+ return validators
+}
+
+type snapshotJSON struct {
+ Epoch uint64 `json:"epoch"`
+ Number uint64 `json:"number"`
+ Hash common.Hash `json:"hash"`
+ Votes []*Vote `json:"votes"`
+ Tally map[common.Address]Tally `json:"tally"`
+
+ // for validator set
+ Validators []common.Address `json:"validators"`
+ Policy istanbul.ProposerPolicy `json:"policy"`
+}
+
+func (s *Snapshot) toJSONStruct() *snapshotJSON {
+ return &snapshotJSON{
+ Epoch: s.Epoch,
+ Number: s.Number,
+ Hash: s.Hash,
+ Votes: s.Votes,
+ Tally: s.Tally,
+ Validators: s.validators(),
+ Policy: s.ValSet.Policy(),
+ }
+}
+
+// Unmarshal from a json byte array
+func (s *Snapshot) UnmarshalJSON(b []byte) error {
+ var j snapshotJSON
+ if err := json.Unmarshal(b, &j); err != nil {
+ return err
+ }
+
+ s.Epoch = j.Epoch
+ s.Number = j.Number
+ s.Hash = j.Hash
+ s.Votes = j.Votes
+ s.Tally = j.Tally
+ s.ValSet = validator.NewSet(j.Validators, j.Policy)
+ return nil
+}
+
+// Marshal to a json byte array
+func (s *Snapshot) MarshalJSON() ([]byte, error) {
+ j := s.toJSONStruct()
+ return json.Marshal(j)
+}
diff --git a/consensus/istanbul/backend/snapshot_test.go b/consensus/istanbul/backend/snapshot_test.go
new file mode 100644
index 0000000000..8dbaf808d6
--- /dev/null
+++ b/consensus/istanbul/backend/snapshot_test.go
@@ -0,0 +1,455 @@
+// 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 .
+
+package backend
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "math/big"
+ "reflect"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/consensus/istanbul"
+ "github.com/ethereum/go-ethereum/consensus/istanbul/validator"
+ "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"
+)
+
+type testerVote struct {
+ validator string
+ voted string
+ auth bool
+}
+
+// testerAccountPool is a pool to maintain currently active tester accounts,
+// mapped from textual names used in the tests below to actual Ethereum private
+// keys capable of signing transactions.
+type testerAccountPool struct {
+ accounts map[string]*ecdsa.PrivateKey
+}
+
+func newTesterAccountPool() *testerAccountPool {
+ return &testerAccountPool{
+ accounts: make(map[string]*ecdsa.PrivateKey),
+ }
+}
+
+func (ap *testerAccountPool) sign(header *types.Header, validator string) {
+ // Ensure we have a persistent key for the validator
+ if ap.accounts[validator] == nil {
+ ap.accounts[validator], _ = crypto.GenerateKey()
+ }
+ // Sign the header and embed the signature in extra data
+ hashData := crypto.Keccak256([]byte(sigHash(header).Bytes()))
+ sig, _ := crypto.Sign(hashData, ap.accounts[validator])
+
+ writeSeal(header, sig)
+}
+
+func (ap *testerAccountPool) address(account string) common.Address {
+ // Ensure we have a persistent key for the account
+ if ap.accounts[account] == nil {
+ ap.accounts[account], _ = crypto.GenerateKey()
+ }
+ // Resolve and return the Ethereum address
+ return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
+}
+
+// Tests that voting is evaluated correctly for various simple and complex scenarios.
+func TestVoting(t *testing.T) {
+ // Define the various voting scenarios to test
+ tests := []struct {
+ epoch uint64
+ validators []string
+ votes []testerVote
+ results []string
+ }{
+ {
+ // Single validator, no votes cast
+ validators: []string{"A"},
+ votes: []testerVote{{validator: "A"}},
+ results: []string{"A"},
+ }, {
+ // Single validator, voting to add two others (only accept first, second needs 2 votes)
+ validators: []string{"A"},
+ votes: []testerVote{
+ {validator: "A", voted: "B", auth: true},
+ {validator: "B"},
+ {validator: "A", voted: "C", auth: true},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Two validators, voting to add three others (only accept first two, third needs 3 votes already)
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: true},
+ {validator: "B", voted: "C", auth: true},
+ {validator: "A", voted: "D", auth: true},
+ {validator: "B", voted: "D", auth: true},
+ {validator: "C"},
+ {validator: "A", voted: "E", auth: true},
+ {validator: "B", voted: "E", auth: true},
+ },
+ results: []string{"A", "B", "C", "D"},
+ }, {
+ // Single validator, dropping itself (weird, but one less cornercase by explicitly allowing this)
+ validators: []string{"A"},
+ votes: []testerVote{
+ {validator: "A", voted: "A", auth: false},
+ },
+ results: []string{},
+ }, {
+ // Two validators, actually needing mutual consent to drop either of them (not fulfilled)
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "B", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Two validators, actually needing mutual consent to drop either of them (fulfilled)
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "B", auth: false},
+ {validator: "B", voted: "B", auth: false},
+ },
+ results: []string{"A"},
+ }, {
+ // Three validators, two of them deciding to drop the third
+ validators: []string{"A", "B", "C"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Four validators, consensus of two not being enough to drop anyone
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ },
+ results: []string{"A", "B", "C", "D"},
+ }, {
+ // Four validators, consensus of three already being enough to drop someone
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "D", auth: false},
+ {validator: "B", voted: "D", auth: false},
+ {validator: "C", voted: "D", auth: false},
+ },
+ results: []string{"A", "B", "C"},
+ }, {
+ // Authorizations are counted once per validator per target
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: true},
+ {validator: "B"},
+ {validator: "A", voted: "C", auth: true},
+ {validator: "B"},
+ {validator: "A", voted: "C", auth: true},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Authorizing multiple accounts concurrently is permitted
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: true},
+ {validator: "B"},
+ {validator: "A", voted: "D", auth: true},
+ {validator: "B"},
+ {validator: "A"},
+ {validator: "B", voted: "D", auth: true},
+ {validator: "A"},
+ {validator: "B", voted: "C", auth: true},
+ },
+ results: []string{"A", "B", "C", "D"},
+ }, {
+ // Deauthorizations are counted once per validator per target
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "B", auth: false},
+ {validator: "B"},
+ {validator: "A", voted: "B", auth: false},
+ {validator: "B"},
+ {validator: "A", voted: "B", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Deauthorizing multiple accounts concurrently is permitted
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B"},
+ {validator: "C"},
+ {validator: "A", voted: "D", auth: false},
+ {validator: "B"},
+ {validator: "C"},
+ {validator: "A"},
+ {validator: "B", voted: "D", auth: false},
+ {validator: "C", voted: "D", auth: false},
+ {validator: "A"},
+ {validator: "B", voted: "C", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Votes from deauthorized validators are discarded immediately (deauth votes)
+ validators: []string{"A", "B", "C"},
+ votes: []testerVote{
+ {validator: "C", voted: "B", auth: false},
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ {validator: "A", voted: "B", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Votes from deauthorized validators are discarded immediately (auth votes)
+ validators: []string{"A", "B", "C"},
+ votes: []testerVote{
+ {validator: "C", voted: "B", auth: false},
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ {validator: "A", voted: "B", auth: false},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Cascading changes are not allowed, only the the account being voted on may change
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B"},
+ {validator: "C"},
+ {validator: "A", voted: "D", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ {validator: "C"},
+ {validator: "A"},
+ {validator: "B", voted: "D", auth: false},
+ {validator: "C", voted: "D", auth: false},
+ },
+ results: []string{"A", "B", "C"},
+ }, {
+ // Changes reaching consensus out of bounds (via a deauth) execute on touch
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B"},
+ {validator: "C"},
+ {validator: "A", voted: "D", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ {validator: "C"},
+ {validator: "A"},
+ {validator: "B", voted: "D", auth: false},
+ {validator: "C", voted: "D", auth: false},
+ {validator: "A"},
+ {validator: "C", voted: "C", auth: true},
+ },
+ results: []string{"A", "B"},
+ }, {
+ // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch
+ validators: []string{"A", "B", "C", "D"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: false},
+ {validator: "B"},
+ {validator: "C"},
+ {validator: "A", voted: "D", auth: false},
+ {validator: "B", voted: "C", auth: false},
+ {validator: "C"},
+ {validator: "A"},
+ {validator: "B", voted: "D", auth: false},
+ {validator: "C", voted: "D", auth: false},
+ {validator: "A"},
+ {validator: "B", voted: "C", auth: true},
+ },
+ results: []string{"A", "B", "C"},
+ }, {
+ // Ensure that pending votes don't survive authorization status changes. This
+ // corner case can only appear if a validator is quickly added, remove and then
+ // readded (or the inverse), while one of the original voters dropped. If a
+ // past vote is left cached in the system somewhere, this will interfere with
+ // the final validator outcome.
+ validators: []string{"A", "B", "C", "D", "E"},
+ votes: []testerVote{
+ {validator: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed
+ {validator: "B", voted: "F", auth: true},
+ {validator: "C", voted: "F", auth: true},
+ {validator: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged")
+ {validator: "E", voted: "F", auth: false},
+ {validator: "B", voted: "F", auth: false},
+ {validator: "C", voted: "F", auth: false},
+ {validator: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed
+ {validator: "E", voted: "F", auth: true},
+ {validator: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed
+ {validator: "C", voted: "A", auth: false},
+ {validator: "D", voted: "A", auth: false},
+ {validator: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed
+ },
+ results: []string{"B", "C", "D", "E", "F"},
+ }, {
+ // Epoch transitions reset all votes to allow chain checkpointing
+ epoch: 3,
+ validators: []string{"A", "B"},
+ votes: []testerVote{
+ {validator: "A", voted: "C", auth: true},
+ {validator: "B"},
+ {validator: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots)
+ {validator: "B", voted: "C", auth: true},
+ },
+ results: []string{"A", "B"},
+ },
+ }
+ // Run through the scenarios and test them
+ for i, tt := range tests {
+ // Create the account pool and generate the initial set of validators
+ accounts := newTesterAccountPool()
+
+ validators := make([]common.Address, len(tt.validators))
+ for j, validator := range tt.validators {
+ validators[j] = accounts.address(validator)
+ }
+ for j := 0; j < len(validators); j++ {
+ for k := j + 1; k < len(validators); k++ {
+ if bytes.Compare(validators[j][:], validators[k][:]) > 0 {
+ validators[j], validators[k] = validators[k], validators[j]
+ }
+ }
+ }
+ // Create the genesis block with the initial set of validators
+ genesis := &core.Genesis{
+ Difficulty: defaultDifficulty,
+ Mixhash: types.IstanbulDigest,
+ }
+ b := genesis.ToBlock(nil)
+ extra, _ := prepareExtra(b.Header(), validators)
+ genesis.ExtraData = extra
+ // Create a pristine blockchain with the genesis injected
+ db, _ := ethdb.NewMemDatabase()
+ genesis.Commit(db)
+
+ config := istanbul.DefaultConfig
+ if tt.epoch != 0 {
+ config.Epoch = tt.epoch
+ }
+ engine := New(config, accounts.accounts[tt.validators[0]], db).(*backend)
+ chain, err := core.NewBlockChain(db, nil, genesis.Config, engine, vm.Config{})
+
+ // Assemble a chain of headers from the cast votes
+ headers := make([]*types.Header, len(tt.votes))
+ for j, vote := range tt.votes {
+ headers[j] = &types.Header{
+ Number: big.NewInt(int64(j) + 1),
+ Time: big.NewInt(int64(j) * int64(config.BlockPeriod)),
+ Coinbase: accounts.address(vote.voted),
+ Difficulty: defaultDifficulty,
+ MixDigest: types.IstanbulDigest,
+ }
+ extra, _ := prepareExtra(headers[j], validators)
+ headers[j].Extra = extra
+ if j > 0 {
+ headers[j].ParentHash = headers[j-1].Hash()
+ }
+ if vote.auth {
+ copy(headers[j].Nonce[:], nonceAuthVote)
+ }
+ copy(headers[j].Extra, genesis.ExtraData)
+ accounts.sign(headers[j], vote.validator)
+ }
+ // Pass all the headers through clique and ensure tallying succeeds
+ head := headers[len(headers)-1]
+
+ snap, err := engine.snapshot(chain, head.Number.Uint64(), head.Hash(), headers)
+ if err != nil {
+ t.Errorf("test %d: failed to create voting snapshot: %v", i, err)
+ continue
+ }
+ // Verify the final list of validators against the expected ones
+ validators = make([]common.Address, len(tt.results))
+ for j, validator := range tt.results {
+ validators[j] = accounts.address(validator)
+ }
+ for j := 0; j < len(validators); j++ {
+ for k := j + 1; k < len(validators); k++ {
+ if bytes.Compare(validators[j][:], validators[k][:]) > 0 {
+ validators[j], validators[k] = validators[k], validators[j]
+ }
+ }
+ }
+ result := snap.validators()
+ if len(result) != len(validators) {
+ t.Errorf("test %d: validators mismatch: have %x, want %x", i, result, validators)
+ continue
+ }
+ for j := 0; j < len(result); j++ {
+ if !bytes.Equal(result[j][:], validators[j][:]) {
+ t.Errorf("test %d, validator %d: validator mismatch: have %x, want %x", i, j, result[j], validators[j])
+ }
+ }
+ }
+}
+
+func TestSaveAndLoad(t *testing.T) {
+ snap := &Snapshot{
+ Epoch: 5,
+ Number: 10,
+ Hash: common.HexToHash("1234567890"),
+ Votes: []*Vote{
+ {
+ Validator: common.StringToAddress("1234567891"),
+ Block: 15,
+ Address: common.StringToAddress("1234567892"),
+ Authorize: false,
+ },
+ },
+ Tally: map[common.Address]Tally{
+ common.StringToAddress("1234567893"): {
+ Authorize: false,
+ Votes: 20,
+ },
+ },
+ ValSet: validator.NewSet([]common.Address{
+ common.StringToAddress("1234567894"),
+ common.StringToAddress("1234567895"),
+ }, istanbul.RoundRobin),
+ }
+ db, _ := ethdb.NewMemDatabase()
+ err := snap.store(db)
+ if err != nil {
+ t.Errorf("store snapshot failed: %v", err)
+ }
+
+ snap1, err := loadSnapshot(snap.Epoch, db, snap.Hash)
+ if err != nil {
+ t.Errorf("load snapshot failed: %v", err)
+ }
+ if snap.Epoch != snap1.Epoch {
+ t.Errorf("epoch mismatch: have %v, want %v", snap1.Epoch, snap.Epoch)
+ }
+ if snap.Hash != snap1.Hash {
+ t.Errorf("hash mismatch: have %v, want %v", snap1.Number, snap.Number)
+ }
+ if !reflect.DeepEqual(snap.Votes, snap.Votes) {
+ t.Errorf("votes mismatch: have %v, want %v", snap1.Votes, snap.Votes)
+ }
+ if !reflect.DeepEqual(snap.Tally, snap.Tally) {
+ t.Errorf("tally mismatch: have %v, want %v", snap1.Tally, snap.Tally)
+ }
+ if !reflect.DeepEqual(snap.ValSet, snap.ValSet) {
+ t.Errorf("validator set mismatch: have %v, want %v", snap1.ValSet, snap.ValSet)
+ }
+}