github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/consensus/bft/backend/snapshot_test.go (about)

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