github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/consensus/istanbul/backend/snapshot_test.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package backend
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"math/big"
    23  	"reflect"
    24  	"sort"
    25  	"testing"
    26  
    27  	bls "github.com/celo-org/bls-zexe/go"
    28  	ethAccounts "github.com/ethereum/go-ethereum/accounts"
    29  	"github.com/ethereum/go-ethereum/common"
    30  	"github.com/ethereum/go-ethereum/consensus/istanbul"
    31  	"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
    32  	"github.com/ethereum/go-ethereum/core"
    33  	"github.com/ethereum/go-ethereum/core/types"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	blscrypto "github.com/ethereum/go-ethereum/crypto/bls"
    36  	"github.com/ethereum/go-ethereum/ethdb"
    37  	"github.com/ethereum/go-ethereum/params"
    38  	"github.com/ethereum/go-ethereum/rlp"
    39  )
    40  
    41  type testerValSetDiff struct {
    42  	proposer          string
    43  	addedValidators   []string
    44  	removedValidators []string
    45  }
    46  
    47  // testerAccountPool is a pool to maintain currently active tester accounts,
    48  // mapped from textual names used in the tests below to actual Ethereum private
    49  // keys capable of signing transactions.
    50  type testerAccountPool struct {
    51  	accounts map[string]*ecdsa.PrivateKey
    52  }
    53  
    54  func newTesterAccountPool() *testerAccountPool {
    55  	return &testerAccountPool{
    56  		accounts: make(map[string]*ecdsa.PrivateKey),
    57  	}
    58  }
    59  
    60  func (ap *testerAccountPool) sign(header *types.Header, validator string) {
    61  	// Ensure we have a persistent key for the validator
    62  	if ap.accounts[validator] == nil {
    63  		ap.accounts[validator], _ = crypto.GenerateKey()
    64  	}
    65  	// Sign the header and embed the signature in extra data
    66  	hashData := crypto.Keccak256([]byte(sigHash(header).Bytes()))
    67  	sig, _ := crypto.Sign(hashData, ap.accounts[validator])
    68  
    69  	writeSeal(header, sig)
    70  }
    71  
    72  func (ap *testerAccountPool) address(account string) common.Address {
    73  	// Ensure we have a persistent key for the account
    74  	if account == "" {
    75  		return common.Address{}
    76  	}
    77  	if ap.accounts[account] == nil {
    78  		ap.accounts[account], _ = crypto.GenerateKey()
    79  	}
    80  	// Resolve and return the Ethereum address
    81  	return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
    82  }
    83  
    84  func indexOf(element string, data []string) int {
    85  	for k, v := range data {
    86  		if element == v {
    87  			return k
    88  		}
    89  	}
    90  	return -1 //not found.
    91  }
    92  
    93  func convertValNamesToRemovedValidators(accounts *testerAccountPool, oldVals []istanbul.ValidatorData, valNames []string) *big.Int {
    94  	bitmap := big.NewInt(0)
    95  	for _, v := range valNames {
    96  		for j := range oldVals {
    97  			if accounts.address(v) == oldVals[j].Address {
    98  				bitmap = bitmap.SetBit(bitmap, j, 1)
    99  			}
   100  		}
   101  	}
   102  
   103  	return bitmap
   104  }
   105  
   106  func convertValNames(accounts *testerAccountPool, valNames []string) []common.Address {
   107  	returnArray := make([]common.Address, len(valNames))
   108  
   109  	for i, valName := range valNames {
   110  		returnArray[i] = accounts.address(valName)
   111  	}
   112  
   113  	return returnArray
   114  }
   115  
   116  func convertValNamesToValidatorsData(accounts *testerAccountPool, valNames []string) []istanbul.ValidatorData {
   117  	returnArray := make([]istanbul.ValidatorData, len(valNames))
   118  
   119  	for i, valName := range valNames {
   120  		returnArray[i] = istanbul.ValidatorData{
   121  			accounts.address(valName),
   122  			blscrypto.SerializedPublicKey{},
   123  		}
   124  	}
   125  
   126  	return returnArray
   127  }
   128  
   129  // Define a mock blockchain
   130  type mockBlockchain struct {
   131  	headers map[uint64]*types.Header
   132  }
   133  
   134  func (bc *mockBlockchain) AddHeader(number uint64, header *types.Header) {
   135  	bc.headers[number] = header
   136  }
   137  
   138  func (bc *mockBlockchain) GetHeaderByNumber(number uint64) *types.Header {
   139  	return bc.headers[number]
   140  }
   141  
   142  func (bc *mockBlockchain) Config() *params.ChainConfig {
   143  	return &params.ChainConfig{FullHeaderChainAvailable: true}
   144  }
   145  
   146  func (bc *mockBlockchain) CurrentHeader() *types.Header {
   147  	return nil
   148  }
   149  
   150  func (bc *mockBlockchain) GetHeader(hash common.Hash, number uint64) *types.Header {
   151  	return nil
   152  }
   153  
   154  func (bc *mockBlockchain) GetHeaderByHash(hash common.Hash) *types.Header {
   155  	return nil
   156  }
   157  
   158  func (bc *mockBlockchain) GetBlock(hash common.Hash, number uint64) *types.Block {
   159  	return nil
   160  }
   161  
   162  // Tests that validator set changes are evaluated correctly for various simple and complex scenarios.
   163  func TestValSetChange(t *testing.T) {
   164  	// Define the various voting scenarios to test
   165  	tests := []struct {
   166  		epoch       uint64
   167  		validators  []string
   168  		valsetdiffs []testerValSetDiff
   169  		results     []string
   170  		err         error
   171  	}{
   172  		{
   173  			// Single validator, empty val set diff
   174  			epoch:       1,
   175  			validators:  []string{"A"},
   176  			valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{}, removedValidators: []string{}}},
   177  			results:     []string{"A"},
   178  			err:         nil,
   179  		}, {
   180  			// Single validator, add two new validators
   181  			epoch:       1,
   182  			validators:  []string{"A"},
   183  			valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"B", "C"}, removedValidators: []string{}}},
   184  			results:     []string{"A", "B", "C"},
   185  			err:         nil,
   186  		}, {
   187  			// Two validator, remove two validators
   188  			epoch:       1,
   189  			validators:  []string{"A", "B"},
   190  			valsetdiffs: []testerValSetDiff{{proposer: "B", addedValidators: []string{}, removedValidators: []string{"A", "B"}}},
   191  			results:     []string{},
   192  			err:         nil,
   193  		}, {
   194  			// Three validator, add two validators and remove two validators
   195  			epoch:       1,
   196  			validators:  []string{"A", "B", "C"},
   197  			valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}},
   198  			results:     []string{"A", "D", "E"},
   199  			err:         nil,
   200  		}, {
   201  			// Three validator, add two validators and remove two validators with an unauthorized proposer
   202  			epoch:       1,
   203  			validators:  []string{"A", "B", "C"},
   204  			valsetdiffs: []testerValSetDiff{{proposer: "D", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}},
   205  			results:     []string{"A", "D", "E"},
   206  			err:         errUnauthorized,
   207  		},
   208  		{
   209  			// Three validator, add two validators and remove two validators.  Second header will add 1 validators and remove 2 validators.
   210  			epoch:      1,
   211  			validators: []string{"A", "B", "C"},
   212  			valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}},
   213  				{proposer: "E", addedValidators: []string{"F"}, removedValidators: []string{"A", "D"}}},
   214  			results: []string{"F", "E"},
   215  			err:     nil,
   216  		}, {
   217  			// Three validator, add two validators and remove two validators.  Second header will add 1 validators and remove 2 validators.  The first header will
   218  			// be ignored, since it's no the last header of an epoch
   219  			epoch:      2,
   220  			validators: []string{"A", "B", "C"},
   221  			valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}},
   222  				{proposer: "A", addedValidators: []string{"F"}, removedValidators: []string{"A", "B"}}},
   223  			results: []string{"F", "C"},
   224  			err:     nil,
   225  		},
   226  	}
   227  	// Run through the scenarios and test them
   228  	for i, tt := range tests {
   229  		// Create the account pool and generate the initial set of validators
   230  		accounts := newTesterAccountPool()
   231  
   232  		validators := make([]istanbul.ValidatorData, len(tt.validators))
   233  		for j, validator := range tt.validators {
   234  			validators[j] = istanbul.ValidatorData{
   235  				accounts.address(validator),
   236  				blscrypto.SerializedPublicKey{},
   237  			}
   238  		}
   239  
   240  		// Create the genesis block with the initial set of validators
   241  		genesis := &core.Genesis{
   242  			Difficulty: defaultDifficulty,
   243  			Mixhash:    types.IstanbulDigest,
   244  			Config:     params.TestChainConfig,
   245  		}
   246  		extra, _ := rlp.EncodeToBytes(&types.IstanbulExtra{})
   247  		genesis.ExtraData = append(make([]byte, types.IstanbulExtraVanity), extra...)
   248  		b := genesis.ToBlock(nil)
   249  		h := b.Header()
   250  		err := writeValidatorSetDiff(h, []istanbul.ValidatorData{}, validators)
   251  		if err != nil {
   252  			t.Errorf("Could not update genesis validator set, got err: %v", err)
   253  		}
   254  		genesis.ExtraData = h.Extra
   255  		db := ethdb.NewMemDatabase()
   256  
   257  		config := istanbul.DefaultConfig
   258  		if tt.epoch != 0 {
   259  			config.Epoch = tt.epoch
   260  		}
   261  
   262  		chain := &mockBlockchain{
   263  			headers: make(map[uint64]*types.Header),
   264  		}
   265  
   266  		engine := New(config, db).(*Backend)
   267  
   268  		privateKey := accounts.accounts[tt.validators[0]]
   269  		address := crypto.PubkeyToAddress(privateKey.PublicKey)
   270  		signerFn := func(_ ethAccounts.Account, data []byte) ([]byte, error) {
   271  			return crypto.Sign(data, privateKey)
   272  		}
   273  
   274  		signerBLSHashFn := func(_ ethAccounts.Account, data []byte) (blscrypto.SerializedSignature, error) {
   275  			key := privateKey
   276  			privateKeyBytes, err := blscrypto.ECDSAToBLS(key)
   277  			if err != nil {
   278  				return blscrypto.SerializedSignature{}, err
   279  			}
   280  
   281  			privateKey, err := bls.DeserializePrivateKey(privateKeyBytes)
   282  			if err != nil {
   283  				return blscrypto.SerializedSignature{}, err
   284  			}
   285  			defer privateKey.Destroy()
   286  
   287  			signature, err := privateKey.SignMessage(data, []byte{}, false)
   288  			if err != nil {
   289  				return blscrypto.SerializedSignature{}, err
   290  			}
   291  			defer signature.Destroy()
   292  			signatureBytes, err := signature.Serialize()
   293  			if err != nil {
   294  				return blscrypto.SerializedSignature{}, err
   295  			}
   296  
   297  			return blscrypto.SerializedSignatureFromBytes(signatureBytes)
   298  		}
   299  
   300  		signerBLSMessageFn := func(_ ethAccounts.Account, data []byte, extraData []byte) (blscrypto.SerializedSignature, error) {
   301  			key := privateKey
   302  			privateKeyBytes, err := blscrypto.ECDSAToBLS(key)
   303  			if err != nil {
   304  				return blscrypto.SerializedSignature{}, err
   305  			}
   306  
   307  			privateKey, err := bls.DeserializePrivateKey(privateKeyBytes)
   308  			if err != nil {
   309  				return blscrypto.SerializedSignature{}, err
   310  			}
   311  			defer privateKey.Destroy()
   312  
   313  			signature, err := privateKey.SignMessage(data, extraData, true)
   314  			if err != nil {
   315  				return blscrypto.SerializedSignature{}, err
   316  			}
   317  			defer signature.Destroy()
   318  			signatureBytes, err := signature.Serialize()
   319  			if err != nil {
   320  				return blscrypto.SerializedSignature{}, err
   321  			}
   322  
   323  			return blscrypto.SerializedSignatureFromBytes(signatureBytes)
   324  		}
   325  
   326  		engine.Authorize(address, signerFn, signerBLSHashFn, signerBLSMessageFn)
   327  
   328  		chain.AddHeader(0, genesis.ToBlock(nil).Header())
   329  
   330  		// Assemble a chain of headers from header validator set diffs
   331  		var prevHeader *types.Header
   332  		var currentVals []istanbul.ValidatorData
   333  		var snap *Snapshot
   334  		for j, valsetdiff := range tt.valsetdiffs {
   335  			header := &types.Header{
   336  				Number:     big.NewInt(int64(j) + 1),
   337  				Time:       big.NewInt(int64(j) * int64(config.BlockPeriod)),
   338  				Difficulty: defaultDifficulty,
   339  				MixDigest:  types.IstanbulDigest,
   340  			}
   341  
   342  			var buf bytes.Buffer
   343  
   344  			buf.Write(bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity))
   345  
   346  			var oldVals []istanbul.ValidatorData
   347  			if currentVals == nil {
   348  				oldVals = convertValNamesToValidatorsData(accounts, tests[i].validators)
   349  			} else {
   350  				oldVals = currentVals
   351  			}
   352  
   353  			ist := &types.IstanbulExtra{
   354  				AddedValidators:           convertValNames(accounts, valsetdiff.addedValidators),
   355  				AddedValidatorsPublicKeys: make([]blscrypto.SerializedPublicKey, len(valsetdiff.addedValidators)),
   356  				RemovedValidators:         convertValNamesToRemovedValidators(accounts, oldVals, valsetdiff.removedValidators),
   357  				AggregatedSeal:            types.IstanbulAggregatedSeal{},
   358  				ParentAggregatedSeal:      types.IstanbulAggregatedSeal{},
   359  			}
   360  
   361  			payload, err := rlp.EncodeToBytes(&ist)
   362  			if err != nil {
   363  				t.Errorf("test %d, valsetdiff %d: error in encoding extra header info", i, j)
   364  			}
   365  			header.Extra = append(buf.Bytes(), payload...)
   366  
   367  			if j > 0 {
   368  				header.ParentHash = prevHeader.Hash()
   369  			}
   370  
   371  			accounts.sign(header, valsetdiff.proposer)
   372  
   373  			chain.AddHeader(uint64(j+1), header)
   374  
   375  			prevHeader = header
   376  			snap, err = engine.snapshot(chain, prevHeader.Number.Uint64(), prevHeader.Hash(), nil)
   377  			if err != tt.err {
   378  				t.Errorf("test %d: error mismatch:  have %v, want %v", i, err, tt.err)
   379  			}
   380  
   381  			if err != nil {
   382  				continue
   383  			}
   384  
   385  			currentVals = snap.validators()
   386  		}
   387  		if tt.err != nil {
   388  			continue
   389  		}
   390  
   391  		// Verify the final list of validators against the expected ones
   392  		validators = make([]istanbul.ValidatorData, len(tt.results))
   393  		for j, validator := range tt.results {
   394  			validators[j] = istanbul.ValidatorData{
   395  				accounts.address(validator),
   396  				blscrypto.SerializedPublicKey{},
   397  			}
   398  		}
   399  		result := snap.validators()
   400  		if len(result) != len(validators) {
   401  			t.Errorf("test %d: validators mismatch: have %x, want %x", i, result, validators)
   402  			continue
   403  		}
   404  
   405  		sort.Sort(istanbul.ValidatorsDataByAddress(result))
   406  		sort.Sort(istanbul.ValidatorsDataByAddress(validators))
   407  		for j := 0; j < len(result); j++ {
   408  			if !bytes.Equal(result[j].Address[:], validators[j].Address[:]) {
   409  				t.Errorf("test %d, validator %d: validator mismatch: have %x, want %x", i, j, result[j], validators[j])
   410  			}
   411  		}
   412  	}
   413  }
   414  
   415  func TestSaveAndLoad(t *testing.T) {
   416  	snap := &Snapshot{
   417  		Epoch:  5,
   418  		Number: 10,
   419  		Hash:   common.HexToHash("1234567890"),
   420  		ValSet: validator.NewSet([]istanbul.ValidatorData{
   421  			{
   422  				common.BytesToAddress([]byte("1234567894")),
   423  				blscrypto.SerializedPublicKey{},
   424  			},
   425  			{
   426  				common.BytesToAddress([]byte("1234567895")),
   427  				blscrypto.SerializedPublicKey{},
   428  			},
   429  		}),
   430  	}
   431  	db := ethdb.NewMemDatabase()
   432  	err := snap.store(db)
   433  	if err != nil {
   434  		t.Errorf("store snapshot failed: %v", err)
   435  	}
   436  
   437  	snap1, err := loadSnapshot(snap.Epoch, db, snap.Hash)
   438  	if err != nil {
   439  		t.Errorf("load snapshot failed: %v", err)
   440  	}
   441  	if snap.Epoch != snap1.Epoch {
   442  		t.Errorf("epoch mismatch: have %v, want %v", snap1.Epoch, snap.Epoch)
   443  	}
   444  	if snap.Hash != snap1.Hash {
   445  		t.Errorf("hash mismatch: have %v, want %v", snap1.Number, snap.Number)
   446  	}
   447  	if !reflect.DeepEqual(snap.ValSet, snap.ValSet) {
   448  		t.Errorf("validator set mismatch: have %v, want %v", snap1.ValSet, snap.ValSet)
   449  	}
   450  }