github.com/reapchain/go-reapchain@v0.2.15-0.20210609012950-9735c110c705/consensus/podc/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  	"testing"
    24  
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/consensus/podc"
    27  	"github.com/ethereum/go-ethereum/core"
    28  	"github.com/ethereum/go-ethereum/core/types"
    29  	"github.com/ethereum/go-ethereum/core/vm"
    30  	"github.com/ethereum/go-ethereum/crypto"
    31  	"github.com/ethereum/go-ethereum/ethdb"
    32  	"github.com/ethereum/go-ethereum/event"
    33  )
    34  
    35  type testerVote struct {
    36  	validator string
    37  	voted     string
    38  	auth      bool
    39  }
    40  
    41  // testerAccountPool is a pool to maintain currently active tester accounts,
    42  // mapped from textual names used in the tests below to actual Ethereum private
    43  // keys capable of signing transactions.
    44  type testerAccountPool struct {
    45  	accounts map[string]*ecdsa.PrivateKey
    46  }
    47  
    48  func newTesterAccountPool() *testerAccountPool {
    49  	return &testerAccountPool{
    50  		accounts: make(map[string]*ecdsa.PrivateKey),
    51  	}
    52  }
    53  
    54  func (ap *testerAccountPool) sign(header *types.Header, validator string) {
    55  	// Ensure we have a persistent key for the validator
    56  	if ap.accounts[validator] == nil {
    57  		ap.accounts[validator], _ = crypto.GenerateKey()
    58  	}
    59  	// Sign the header and embed the signature in extra data
    60  	hashData := crypto.Keccak256([]byte(sigHash(header).Bytes()))
    61  	sig, _ := crypto.Sign(hashData, ap.accounts[validator])
    62  
    63  	writeSeal(header, sig)
    64  }
    65  
    66  func (ap *testerAccountPool) address(account string) common.Address {
    67  	// Ensure we have a persistent key for the account
    68  	if ap.accounts[account] == nil {
    69  		ap.accounts[account], _ = crypto.GenerateKey()
    70  	}
    71  	// Resolve and return the Ethereum address
    72  	return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
    73  }
    74  
    75  // Tests that voting is evaluated correctly for various simple and complex scenarios.
    76  func TestVoting(t *testing.T) {
    77  	// Define the various voting scenarios to test
    78  	tests := []struct {
    79  		epoch      uint64
    80  		validators []string
    81  		votes      []testerVote
    82  		results    []string
    83  	}{
    84  		{
    85  			// Single validator, no votes cast
    86  			validators: []string{"A"},
    87  			votes:      []testerVote{{validator: "A"}},
    88  			results:    []string{"A"},
    89  		}, {
    90  			// Single validator, voting to add two others (only accept first, second needs 2 votes)
    91  			validators: []string{"A"},
    92  			votes: []testerVote{
    93  				{validator: "A", voted: "B", auth: true},
    94  				{validator: "B"},
    95  				{validator: "A", voted: "C", auth: true},
    96  			},
    97  			results: []string{"A", "B"},
    98  		}, {
    99  			// Two validators, voting to add three others (only accept first two, third needs 3 votes already)
   100  			validators: []string{"A", "B"},
   101  			votes: []testerVote{
   102  				{validator: "A", voted: "C", auth: true},
   103  				{validator: "B", voted: "C", auth: true},
   104  				{validator: "A", voted: "D", auth: true},
   105  				{validator: "B", voted: "D", auth: true},
   106  				{validator: "C"},
   107  				{validator: "A", voted: "E", auth: true},
   108  				{validator: "B", voted: "E", auth: true},
   109  			},
   110  			results: []string{"A", "B", "C", "D"},
   111  		}, {
   112  			// Single validator, dropping itself (weird, but one less cornercase by explicitly allowing this)
   113  			validators: []string{"A"},
   114  			votes: []testerVote{
   115  				{validator: "A", voted: "A", auth: false},
   116  			},
   117  			results: []string{},
   118  		}, {
   119  			// Two validators, actually needing mutual consent to drop either of them (not fulfilled)
   120  			validators: []string{"A", "B"},
   121  			votes: []testerVote{
   122  				{validator: "A", voted: "B", auth: false},
   123  			},
   124  			results: []string{"A", "B"},
   125  		}, {
   126  			// Two validators, actually needing mutual consent to drop either of them (fulfilled)
   127  			validators: []string{"A", "B"},
   128  			votes: []testerVote{
   129  				{validator: "A", voted: "B", auth: false},
   130  				{validator: "B", voted: "B", auth: false},
   131  			},
   132  			results: []string{"A"},
   133  		}, {
   134  			// Three validators, two of them deciding to drop the third
   135  			validators: []string{"A", "B", "C"},
   136  			votes: []testerVote{
   137  				{validator: "A", voted: "C", auth: false},
   138  				{validator: "B", voted: "C", auth: false},
   139  			},
   140  			results: []string{"A", "B"},
   141  		}, {
   142  			// Four validators, consensus of two not being enough to drop anyone
   143  			validators: []string{"A", "B", "C", "D"},
   144  			votes: []testerVote{
   145  				{validator: "A", voted: "C", auth: false},
   146  				{validator: "B", voted: "C", auth: false},
   147  			},
   148  			results: []string{"A", "B", "C", "D"},
   149  		}, {
   150  			// Four validators, consensus of three already being enough to drop someone
   151  			validators: []string{"A", "B", "C", "D"},
   152  			votes: []testerVote{
   153  				{validator: "A", voted: "D", auth: false},
   154  				{validator: "B", voted: "D", auth: false},
   155  				{validator: "C", voted: "D", auth: false},
   156  			},
   157  			results: []string{"A", "B", "C"},
   158  		}, {
   159  			// Authorizations are counted once per validator per target
   160  			validators: []string{"A", "B"},
   161  			votes: []testerVote{
   162  				{validator: "A", voted: "C", auth: true},
   163  				{validator: "B"},
   164  				{validator: "A", voted: "C", auth: true},
   165  				{validator: "B"},
   166  				{validator: "A", voted: "C", auth: true},
   167  			},
   168  			results: []string{"A", "B"},
   169  		}, {
   170  			// Authorizing multiple accounts concurrently is permitted
   171  			validators: []string{"A", "B"},
   172  			votes: []testerVote{
   173  				{validator: "A", voted: "C", auth: true},
   174  				{validator: "B"},
   175  				{validator: "A", voted: "D", auth: true},
   176  				{validator: "B"},
   177  				{validator: "A"},
   178  				{validator: "B", voted: "D", auth: true},
   179  				{validator: "A"},
   180  				{validator: "B", voted: "C", auth: true},
   181  			},
   182  			results: []string{"A", "B", "C", "D"},
   183  		}, {
   184  			// Deauthorizations are counted once per validator per target
   185  			validators: []string{"A", "B"},
   186  			votes: []testerVote{
   187  				{validator: "A", voted: "B", auth: false},
   188  				{validator: "B"},
   189  				{validator: "A", voted: "B", auth: false},
   190  				{validator: "B"},
   191  				{validator: "A", voted: "B", auth: false},
   192  			},
   193  			results: []string{"A", "B"},
   194  		}, {
   195  			// Deauthorizing multiple accounts concurrently is permitted
   196  			validators: []string{"A", "B", "C", "D"},
   197  			votes: []testerVote{
   198  				{validator: "A", voted: "C", auth: false},
   199  				{validator: "B"},
   200  				{validator: "C"},
   201  				{validator: "A", voted: "D", auth: false},
   202  				{validator: "B"},
   203  				{validator: "C"},
   204  				{validator: "A"},
   205  				{validator: "B", voted: "D", auth: false},
   206  				{validator: "C", voted: "D", auth: false},
   207  				{validator: "A"},
   208  				{validator: "B", voted: "C", auth: false},
   209  			},
   210  			results: []string{"A", "B"},
   211  		}, {
   212  			// Votes from deauthorized validators are discarded immediately (deauth votes)
   213  			validators: []string{"A", "B", "C"},
   214  			votes: []testerVote{
   215  				{validator: "C", voted: "B", auth: false},
   216  				{validator: "A", voted: "C", auth: false},
   217  				{validator: "B", voted: "C", auth: false},
   218  				{validator: "A", voted: "B", auth: false},
   219  			},
   220  			results: []string{"A", "B"},
   221  		}, {
   222  			// Votes from deauthorized validators are discarded immediately (auth votes)
   223  			validators: []string{"A", "B", "C"},
   224  			votes: []testerVote{
   225  				{validator: "C", voted: "B", auth: false},
   226  				{validator: "A", voted: "C", auth: false},
   227  				{validator: "B", voted: "C", auth: false},
   228  				{validator: "A", voted: "B", auth: false},
   229  			},
   230  			results: []string{"A", "B"},
   231  		}, {
   232  			// Cascading changes are not allowed, only the the account being voted on may change
   233  			validators: []string{"A", "B", "C", "D"},
   234  			votes: []testerVote{
   235  				{validator: "A", voted: "C", auth: false},
   236  				{validator: "B"},
   237  				{validator: "C"},
   238  				{validator: "A", voted: "D", auth: false},
   239  				{validator: "B", voted: "C", auth: false},
   240  				{validator: "C"},
   241  				{validator: "A"},
   242  				{validator: "B", voted: "D", auth: false},
   243  				{validator: "C", voted: "D", auth: false},
   244  			},
   245  			results: []string{"A", "B", "C"},
   246  		}, {
   247  			// Changes reaching consensus out of bounds (via a deauth) execute on touch
   248  			validators: []string{"A", "B", "C", "D"},
   249  			votes: []testerVote{
   250  				{validator: "A", voted: "C", auth: false},
   251  				{validator: "B"},
   252  				{validator: "C"},
   253  				{validator: "A", voted: "D", auth: false},
   254  				{validator: "B", voted: "C", auth: false},
   255  				{validator: "C"},
   256  				{validator: "A"},
   257  				{validator: "B", voted: "D", auth: false},
   258  				{validator: "C", voted: "D", auth: false},
   259  				{validator: "A"},
   260  				{validator: "C", voted: "C", auth: true},
   261  			},
   262  			results: []string{"A", "B"},
   263  		}, {
   264  			// Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch
   265  			validators: []string{"A", "B", "C", "D"},
   266  			votes: []testerVote{
   267  				{validator: "A", voted: "C", auth: false},
   268  				{validator: "B"},
   269  				{validator: "C"},
   270  				{validator: "A", voted: "D", auth: false},
   271  				{validator: "B", voted: "C", auth: false},
   272  				{validator: "C"},
   273  				{validator: "A"},
   274  				{validator: "B", voted: "D", auth: false},
   275  				{validator: "C", voted: "D", auth: false},
   276  				{validator: "A"},
   277  				{validator: "B", voted: "C", auth: true},
   278  			},
   279  			results: []string{"A", "B", "C"},
   280  		}, {
   281  			// Ensure that pending votes don't survive authorization status changes. This
   282  			// corner case can only appear if a validator is quickly added, remove and then
   283  			// readded (or the inverse), while one of the original voters dropped. If a
   284  			// past vote is left cached in the system somewhere, this will interfere with
   285  			// the final validator outcome.
   286  			validators: []string{"A", "B", "C", "D", "E"},
   287  			votes: []testerVote{
   288  				{validator: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed
   289  				{validator: "B", voted: "F", auth: true},
   290  				{validator: "C", voted: "F", auth: true},
   291  				{validator: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged")
   292  				{validator: "E", voted: "F", auth: false},
   293  				{validator: "B", voted: "F", auth: false},
   294  				{validator: "C", voted: "F", auth: false},
   295  				{validator: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed
   296  				{validator: "E", voted: "F", auth: true},
   297  				{validator: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed
   298  				{validator: "C", voted: "A", auth: false},
   299  				{validator: "D", voted: "A", auth: false},
   300  				{validator: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed
   301  			},
   302  			results: []string{"B", "C", "D", "E", "F"},
   303  		}, {
   304  			// Epoch transitions reset all votes to allow chain checkpointing
   305  			epoch:      3,
   306  			validators: []string{"A", "B"},
   307  			votes: []testerVote{
   308  				{validator: "A", voted: "C", auth: true},
   309  				{validator: "B"},
   310  				{validator: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots)
   311  				{validator: "B", voted: "C", auth: true},
   312  			},
   313  			results: []string{"A", "B"},
   314  		},
   315  	}
   316  	// Run through the scenarios and test them
   317  	for i, tt := range tests {
   318  		// Create the account pool and generate the initial set of validators
   319  		accounts := newTesterAccountPool()
   320  
   321  		validators := make([]common.Address, len(tt.validators))
   322  		for j, validator := range tt.validators {
   323  			validators[j] = accounts.address(validator)
   324  		}
   325  		for j := 0; j < len(validators); j++ {
   326  			for k := j + 1; k < len(validators); k++ {
   327  				if bytes.Compare(validators[j][:], validators[k][:]) > 0 {
   328  					validators[j], validators[k] = validators[k], validators[j]
   329  				}
   330  			}
   331  		}
   332  		// Create the genesis block with the initial set of validators
   333  		genesis := &core.Genesis{
   334  			Difficulty: defaultDifficulty,
   335  			Mixhash:    types.PoDCDigest,
   336  		}
   337  		b, _ := genesis.ToBlock()
   338  		extra, _ := prepareExtra(b.Header(), validators)
   339  		genesis.ExtraData = extra
   340  		// Create a pristine blockchain with the genesis injected
   341  		db, _ := ethdb.NewMemDatabase()
   342  		genesis.Commit(db)
   343  
   344  		eventMux := new(event.TypeMux)
   345  		config := podc.DefaultConfig
   346  		if tt.epoch != 0 {
   347  			config.Epoch = tt.epoch
   348  		}
   349  		engine := New(config, eventMux, accounts.accounts[tt.validators[0]], db).(*simpleBackend)
   350  		chain, err := core.NewBlockChain(db, genesis.Config, engine, eventMux, vm.Config{})
   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:       big.NewInt(int64(j) * int64(config.BlockPauseTime)),
   358  				Coinbase:   accounts.address(vote.voted),
   359  				Difficulty: defaultDifficulty,
   360  				MixDigest:  types.PoDCDigest,
   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  }