github.com/ConsenSys/Quorum@v20.10.0+incompatible/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  	"testing"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/consensus/istanbul"
    28  	"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
    29  	"github.com/ethereum/go-ethereum/core"
    30  	"github.com/ethereum/go-ethereum/core/rawdb"
    31  	"github.com/ethereum/go-ethereum/core/types"
    32  	"github.com/ethereum/go-ethereum/core/vm"
    33  	"github.com/ethereum/go-ethereum/crypto"
    34  )
    35  
    36  type testerVote struct {
    37  	validator string
    38  	voted     string
    39  	auth      bool
    40  }
    41  
    42  // testerAccountPool is a pool to maintain currently active tester accounts,
    43  // mapped from textual names used in the tests below to actual Ethereum private
    44  // keys capable of signing transactions.
    45  type testerAccountPool struct {
    46  	accounts map[string]*ecdsa.PrivateKey
    47  }
    48  
    49  func newTesterAccountPool() *testerAccountPool {
    50  	return &testerAccountPool{
    51  		accounts: make(map[string]*ecdsa.PrivateKey),
    52  	}
    53  }
    54  
    55  func (ap *testerAccountPool) sign(header *types.Header, validator string) {
    56  	// Ensure we have a persistent key for the validator
    57  	if ap.accounts[validator] == nil {
    58  		ap.accounts[validator], _ = crypto.GenerateKey()
    59  	}
    60  	// Sign the header and embed the signature in extra data
    61  	hashData := crypto.Keccak256([]byte(sigHash(header).Bytes()))
    62  	sig, _ := crypto.Sign(hashData, ap.accounts[validator])
    63  
    64  	writeSeal(header, sig)
    65  }
    66  
    67  func (ap *testerAccountPool) address(account string) common.Address {
    68  	// Ensure we have a persistent key for the account
    69  	if ap.accounts[account] == nil {
    70  		ap.accounts[account], _ = crypto.GenerateKey()
    71  	}
    72  	// Resolve and return the Ethereum address
    73  	return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
    74  }
    75  
    76  // Tests that voting is evaluated correctly for various simple and complex scenarios.
    77  func TestVoting(t *testing.T) {
    78  	// Define the various voting scenarios to test
    79  	tests := []struct {
    80  		epoch      uint64
    81  		validators []string
    82  		votes      []testerVote
    83  		results    []string
    84  	}{
    85  		{
    86  			// Single validator, no votes cast
    87  			validators: []string{"A"},
    88  			votes:      []testerVote{{validator: "A"}},
    89  			results:    []string{"A"},
    90  		}, {
    91  			// Single validator, voting to add two others (only accept first, second needs 2 votes)
    92  			validators: []string{"A"},
    93  			votes: []testerVote{
    94  				{validator: "A", voted: "B", auth: true},
    95  				{validator: "B"},
    96  				{validator: "A", voted: "C", auth: true},
    97  			},
    98  			results: []string{"A", "B"},
    99  		}, {
   100  			// Two validators, voting to add three others (only accept first two, third needs 3 votes already)
   101  			validators: []string{"A", "B"},
   102  			votes: []testerVote{
   103  				{validator: "A", voted: "C", auth: true},
   104  				{validator: "B", voted: "C", auth: true},
   105  				{validator: "A", voted: "D", auth: true},
   106  				{validator: "B", voted: "D", auth: true},
   107  				{validator: "C"},
   108  				{validator: "A", voted: "E", auth: true},
   109  				{validator: "B", voted: "E", auth: true},
   110  			},
   111  			results: []string{"A", "B", "C", "D"},
   112  		}, {
   113  			// Single validator, dropping itself (weird, but one less cornercase by explicitly allowing this)
   114  			validators: []string{"A"},
   115  			votes: []testerVote{
   116  				{validator: "A", voted: "A", auth: false},
   117  			},
   118  			results: []string{},
   119  		}, {
   120  			// Two validators, actually needing mutual consent to drop either of them (not fulfilled)
   121  			validators: []string{"A", "B"},
   122  			votes: []testerVote{
   123  				{validator: "A", voted: "B", auth: false},
   124  			},
   125  			results: []string{"A", "B"},
   126  		}, {
   127  			// Two validators, actually needing mutual consent to drop either of them (fulfilled)
   128  			validators: []string{"A", "B"},
   129  			votes: []testerVote{
   130  				{validator: "A", voted: "B", auth: false},
   131  				{validator: "B", voted: "B", auth: false},
   132  			},
   133  			results: []string{"A"},
   134  		}, {
   135  			// Three validators, two of them deciding to drop the third
   136  			validators: []string{"A", "B", "C"},
   137  			votes: []testerVote{
   138  				{validator: "A", voted: "C", auth: false},
   139  				{validator: "B", voted: "C", auth: false},
   140  			},
   141  			results: []string{"A", "B"},
   142  		}, {
   143  			// Four validators, consensus of two not being enough to drop anyone
   144  			validators: []string{"A", "B", "C", "D"},
   145  			votes: []testerVote{
   146  				{validator: "A", voted: "C", auth: false},
   147  				{validator: "B", voted: "C", auth: false},
   148  			},
   149  			results: []string{"A", "B", "C", "D"},
   150  		}, {
   151  			// Four validators, consensus of three already being enough to drop someone
   152  			validators: []string{"A", "B", "C", "D"},
   153  			votes: []testerVote{
   154  				{validator: "A", voted: "D", auth: false},
   155  				{validator: "B", voted: "D", auth: false},
   156  				{validator: "C", voted: "D", auth: false},
   157  			},
   158  			results: []string{"A", "B", "C"},
   159  		}, {
   160  			// Authorizations are counted once per validator per target
   161  			validators: []string{"A", "B"},
   162  			votes: []testerVote{
   163  				{validator: "A", voted: "C", auth: true},
   164  				{validator: "B"},
   165  				{validator: "A", voted: "C", auth: true},
   166  				{validator: "B"},
   167  				{validator: "A", voted: "C", auth: true},
   168  			},
   169  			results: []string{"A", "B"},
   170  		}, {
   171  			// Authorizing multiple accounts concurrently is permitted
   172  			validators: []string{"A", "B"},
   173  			votes: []testerVote{
   174  				{validator: "A", voted: "C", auth: true},
   175  				{validator: "B"},
   176  				{validator: "A", voted: "D", auth: true},
   177  				{validator: "B"},
   178  				{validator: "A"},
   179  				{validator: "B", voted: "D", auth: true},
   180  				{validator: "A"},
   181  				{validator: "B", voted: "C", auth: true},
   182  			},
   183  			results: []string{"A", "B", "C", "D"},
   184  		}, {
   185  			// Deauthorizations are counted once per validator per target
   186  			validators: []string{"A", "B"},
   187  			votes: []testerVote{
   188  				{validator: "A", voted: "B", auth: false},
   189  				{validator: "B"},
   190  				{validator: "A", voted: "B", auth: false},
   191  				{validator: "B"},
   192  				{validator: "A", voted: "B", auth: false},
   193  			},
   194  			results: []string{"A", "B"},
   195  		}, {
   196  			// Deauthorizing multiple accounts concurrently is permitted
   197  			validators: []string{"A", "B", "C", "D"},
   198  			votes: []testerVote{
   199  				{validator: "A", voted: "C", auth: false},
   200  				{validator: "B"},
   201  				{validator: "C"},
   202  				{validator: "A", voted: "D", auth: false},
   203  				{validator: "B"},
   204  				{validator: "C"},
   205  				{validator: "A"},
   206  				{validator: "B", voted: "D", auth: false},
   207  				{validator: "C", voted: "D", auth: false},
   208  				{validator: "A"},
   209  				{validator: "B", voted: "C", auth: false},
   210  			},
   211  			results: []string{"A", "B"},
   212  		}, {
   213  			// Votes from deauthorized validators are discarded immediately (deauth votes)
   214  			validators: []string{"A", "B", "C"},
   215  			votes: []testerVote{
   216  				{validator: "C", voted: "B", auth: false},
   217  				{validator: "A", voted: "C", auth: false},
   218  				{validator: "B", voted: "C", auth: false},
   219  				{validator: "A", voted: "B", auth: false},
   220  			},
   221  			results: []string{"A", "B"},
   222  		}, {
   223  			// Votes from deauthorized validators are discarded immediately (auth votes)
   224  			validators: []string{"A", "B", "C"},
   225  			votes: []testerVote{
   226  				{validator: "C", voted: "B", auth: false},
   227  				{validator: "A", voted: "C", auth: false},
   228  				{validator: "B", voted: "C", auth: false},
   229  				{validator: "A", voted: "B", auth: false},
   230  			},
   231  			results: []string{"A", "B"},
   232  		}, {
   233  			// Cascading changes are not allowed, only the the account being voted on may change
   234  			validators: []string{"A", "B", "C", "D"},
   235  			votes: []testerVote{
   236  				{validator: "A", voted: "C", auth: false},
   237  				{validator: "B"},
   238  				{validator: "C"},
   239  				{validator: "A", voted: "D", auth: false},
   240  				{validator: "B", voted: "C", auth: false},
   241  				{validator: "C"},
   242  				{validator: "A"},
   243  				{validator: "B", voted: "D", auth: false},
   244  				{validator: "C", voted: "D", auth: false},
   245  			},
   246  			results: []string{"A", "B", "C"},
   247  		}, {
   248  			// Changes reaching consensus out of bounds (via a deauth) execute on touch
   249  			validators: []string{"A", "B", "C", "D"},
   250  			votes: []testerVote{
   251  				{validator: "A", voted: "C", auth: false},
   252  				{validator: "B"},
   253  				{validator: "C"},
   254  				{validator: "A", voted: "D", auth: false},
   255  				{validator: "B", voted: "C", auth: false},
   256  				{validator: "C"},
   257  				{validator: "A"},
   258  				{validator: "B", voted: "D", auth: false},
   259  				{validator: "C", voted: "D", auth: false},
   260  				{validator: "A"},
   261  				{validator: "C", voted: "C", auth: true},
   262  			},
   263  			results: []string{"A", "B"},
   264  		}, {
   265  			// Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch
   266  			validators: []string{"A", "B", "C", "D"},
   267  			votes: []testerVote{
   268  				{validator: "A", voted: "C", auth: false},
   269  				{validator: "B"},
   270  				{validator: "C"},
   271  				{validator: "A", voted: "D", auth: false},
   272  				{validator: "B", voted: "C", auth: false},
   273  				{validator: "C"},
   274  				{validator: "A"},
   275  				{validator: "B", voted: "D", auth: false},
   276  				{validator: "C", voted: "D", auth: false},
   277  				{validator: "A"},
   278  				{validator: "B", voted: "C", auth: true},
   279  			},
   280  			results: []string{"A", "B", "C"},
   281  		}, {
   282  			// Ensure that pending votes don't survive authorization status changes. This
   283  			// corner case can only appear if a validator is quickly added, remove and then
   284  			// readded (or the inverse), while one of the original voters dropped. If a
   285  			// past vote is left cached in the system somewhere, this will interfere with
   286  			// the final validator outcome.
   287  			validators: []string{"A", "B", "C", "D", "E"},
   288  			votes: []testerVote{
   289  				{validator: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed
   290  				{validator: "B", voted: "F", auth: true},
   291  				{validator: "C", voted: "F", auth: true},
   292  				{validator: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged")
   293  				{validator: "E", voted: "F", auth: false},
   294  				{validator: "B", voted: "F", auth: false},
   295  				{validator: "C", voted: "F", auth: false},
   296  				{validator: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed
   297  				{validator: "E", voted: "F", auth: true},
   298  				{validator: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed
   299  				{validator: "C", voted: "A", auth: false},
   300  				{validator: "D", voted: "A", auth: false},
   301  				{validator: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed
   302  			},
   303  			results: []string{"B", "C", "D", "E", "F"},
   304  		}, {
   305  			// Epoch transitions reset all votes to allow chain checkpointing
   306  			epoch:      3,
   307  			validators: []string{"A", "B"},
   308  			votes: []testerVote{
   309  				{validator: "A", voted: "C", auth: true},
   310  				{validator: "B"},
   311  				{validator: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots)
   312  				{validator: "B", voted: "C", auth: true},
   313  			},
   314  			results: []string{"A", "B"},
   315  		},
   316  	}
   317  	// Run through the scenarios and test them
   318  	for i, tt := range tests {
   319  		// Create the account pool and generate the initial set of validators
   320  		accounts := newTesterAccountPool()
   321  
   322  		validators := make([]common.Address, len(tt.validators))
   323  		for j, validator := range tt.validators {
   324  			validators[j] = accounts.address(validator)
   325  		}
   326  		for j := 0; j < len(validators); j++ {
   327  			for k := j + 1; k < len(validators); k++ {
   328  				if bytes.Compare(validators[j][:], validators[k][:]) > 0 {
   329  					validators[j], validators[k] = validators[k], validators[j]
   330  				}
   331  			}
   332  		}
   333  		// Create the genesis block with the initial set of validators
   334  		genesis := &core.Genesis{
   335  			Difficulty: defaultDifficulty,
   336  			Mixhash:    types.IstanbulDigest,
   337  		}
   338  		b := genesis.ToBlock(nil)
   339  		extra, _ := prepareExtra(b.Header(), validators)
   340  		genesis.ExtraData = extra
   341  		// Create a pristine blockchain with the genesis injected
   342  		db := rawdb.NewMemoryDatabase()
   343  		genesis.Commit(db)
   344  
   345  		config := istanbul.DefaultConfig
   346  		if tt.epoch != 0 {
   347  			config.Epoch = tt.epoch
   348  		}
   349  		engine := New(config, accounts.accounts[tt.validators[0]], db).(*backend)
   350  		chain, err := core.NewBlockChain(db, nil, genesis.Config, engine, vm.Config{}, nil)
   351  
   352  		// Assemble a chain of headers from the cast votes
   353  		headers := make([]*types.Header, len(tt.votes))
   354  		for j, vote := range tt.votes {
   355  			headers[j] = &types.Header{
   356  				Number:     big.NewInt(int64(j) + 1),
   357  				Time:       uint64(int64(j) * int64(config.BlockPeriod)),
   358  				Coinbase:   accounts.address(vote.voted),
   359  				Difficulty: defaultDifficulty,
   360  				MixDigest:  types.IstanbulDigest,
   361  			}
   362  			extra, _ := prepareExtra(headers[j], validators)
   363  			headers[j].Extra = extra
   364  			if j > 0 {
   365  				headers[j].ParentHash = headers[j-1].Hash()
   366  			}
   367  			if vote.auth {
   368  				copy(headers[j].Nonce[:], nonceAuthVote)
   369  			}
   370  			copy(headers[j].Extra, genesis.ExtraData)
   371  			accounts.sign(headers[j], vote.validator)
   372  		}
   373  		// Pass all the headers through clique and ensure tallying succeeds
   374  		head := headers[len(headers)-1]
   375  
   376  		snap, err := engine.snapshot(chain, head.Number.Uint64(), head.Hash(), headers)
   377  		if err != nil {
   378  			t.Errorf("test %d: failed to create voting snapshot: %v", i, err)
   379  			continue
   380  		}
   381  		// Verify the final list of validators against the expected ones
   382  		validators = make([]common.Address, len(tt.results))
   383  		for j, validator := range tt.results {
   384  			validators[j] = accounts.address(validator)
   385  		}
   386  		for j := 0; j < len(validators); j++ {
   387  			for k := j + 1; k < len(validators); k++ {
   388  				if bytes.Compare(validators[j][:], validators[k][:]) > 0 {
   389  					validators[j], validators[k] = validators[k], validators[j]
   390  				}
   391  			}
   392  		}
   393  		result := snap.validators()
   394  		if len(result) != len(validators) {
   395  			t.Errorf("test %d: validators mismatch: have %x, want %x", i, result, validators)
   396  			continue
   397  		}
   398  		for j := 0; j < len(result); j++ {
   399  			if !bytes.Equal(result[j][:], validators[j][:]) {
   400  				t.Errorf("test %d, validator %d: validator mismatch: have %x, want %x", i, j, result[j], validators[j])
   401  			}
   402  		}
   403  	}
   404  }
   405  
   406  func TestSaveAndLoad(t *testing.T) {
   407  	snap := &Snapshot{
   408  		Epoch:  5,
   409  		Number: 10,
   410  		Hash:   common.HexToHash("1234567890"),
   411  		Votes: []*Vote{
   412  			{
   413  				Validator: common.StringToAddress("1234567891"),
   414  				Block:     15,
   415  				Address:   common.StringToAddress("1234567892"),
   416  				Authorize: false,
   417  			},
   418  		},
   419  		Tally: map[common.Address]Tally{
   420  			common.StringToAddress("1234567893"): {
   421  				Authorize: false,
   422  				Votes:     20,
   423  			},
   424  		},
   425  		ValSet: validator.NewSet([]common.Address{
   426  			common.StringToAddress("1234567894"),
   427  			common.StringToAddress("1234567895"),
   428  		}, istanbul.RoundRobin),
   429  	}
   430  	db := rawdb.NewMemoryDatabase()
   431  	err := snap.store(db)
   432  	if err != nil {
   433  		t.Errorf("store snapshot failed: %v", err)
   434  	}
   435  
   436  	snap1, err := loadSnapshot(snap.Epoch, db, snap.Hash)
   437  	if err != nil {
   438  		t.Errorf("load snapshot failed: %v", err)
   439  	}
   440  	if snap.Epoch != snap1.Epoch {
   441  		t.Errorf("epoch mismatch: have %v, want %v", snap1.Epoch, snap.Epoch)
   442  	}
   443  	if snap.Hash != snap1.Hash {
   444  		t.Errorf("hash mismatch: have %v, want %v", snap1.Number, snap.Number)
   445  	}
   446  	if !reflect.DeepEqual(snap.Votes, snap.Votes) {
   447  		t.Errorf("votes mismatch: have %v, want %v", snap1.Votes, snap.Votes)
   448  	}
   449  	if !reflect.DeepEqual(snap.Tally, snap.Tally) {
   450  		t.Errorf("tally mismatch: have %v, want %v", snap1.Tally, snap.Tally)
   451  	}
   452  	if !reflect.DeepEqual(snap.ValSet, snap.ValSet) {
   453  		t.Errorf("validator set mismatch: have %v, want %v", snap1.ValSet, snap.ValSet)
   454  	}
   455  }