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

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