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