github.com/bcskill/bcschain/v3@v3.4.9-beta2/consensus/clique/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 clique
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/ecdsa"
    22  	"math/big"
    23  	"testing"
    24  
    25  	"github.com/bcskill/bcschain/v3/common"
    26  	"github.com/bcskill/bcschain/v3/core"
    27  	"github.com/bcskill/bcschain/v3/core/rawdb"
    28  	"github.com/bcskill/bcschain/v3/core/types"
    29  	"github.com/bcskill/bcschain/v3/crypto"
    30  	"github.com/bcskill/bcschain/v3/ethdb"
    31  	"github.com/bcskill/bcschain/v3/params"
    32  )
    33  
    34  type testerVote struct {
    35  	signer        string
    36  	voted         string
    37  	auth          bool
    38  	voterElection 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, signer string) {
    55  	// Ensure we have a persistent key for the signer
    56  	if ap.accounts[signer] == nil {
    57  		ap.accounts[signer], _ = crypto.GenerateKey()
    58  	}
    59  	// Sign the header and embed the signature in extra data
    60  	sig, _ := crypto.Sign(SealHash(header).Bytes(), ap.accounts[signer])
    61  	header.Signer = sig
    62  }
    63  
    64  func (ap *testerAccountPool) address(account string) common.Address {
    65  	// Ensure we have a persistent key for the account
    66  	if ap.accounts[account] == nil {
    67  		ap.accounts[account], _ = crypto.GenerateKey()
    68  	}
    69  	// Resolve and return the Ethereum address
    70  	return crypto.PubkeyToAddress(ap.accounts[account].PublicKey)
    71  }
    72  
    73  // testerChainReader implements consensus.ChainReader to access the genesis
    74  // block. All other methods and requests will panic.
    75  type testerChainReader struct {
    76  	db common.Database
    77  }
    78  
    79  func (r *testerChainReader) Config() *params.ChainConfig                 { return params.AllCliqueProtocolChanges }
    80  func (r *testerChainReader) CurrentHeader() *types.Header                { panic("not supported") }
    81  func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") }
    82  func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block   { panic("not supported") }
    83  func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header   { panic("not supported") }
    84  func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
    85  	if number == 0 {
    86  		return rawdb.ReadHeader(r.db.HeaderTable(), rawdb.ReadCanonicalHash(r.db, 0), 0)
    87  	}
    88  	panic("not supported")
    89  }
    90  
    91  // Tests that voting is evaluated correctly for various simple and complex scenarios.
    92  func TestVoting(t *testing.T) {
    93  	// Define the various voting scenarios to test
    94  	tests := []votingTest{
    95  		{
    96  			// 0: Single signer, no votes cast
    97  			name:           "1-no-votes",
    98  			signers:        []string{"A"},
    99  			voters:         []string{"A"},
   100  			votes:          []testerVote{{signer: "A"}},
   101  			signersResults: []string{"A"},
   102  			votersResults:  []string{"A"},
   103  		},
   104  		{
   105  			// 1: Single signer, voting to add two others (only accept first)
   106  			name:    "1-vote-2",
   107  			signers: []string{"A"},
   108  			voters:  []string{"A"},
   109  			votes: []testerVote{
   110  				{signer: "A", voted: "B", auth: true},
   111  				{signer: "B"},
   112  				{signer: "A", voted: "C", auth: false},
   113  				{signer: "B", voted: "C", auth: true},
   114  			},
   115  			signersResults: []string{"A", "B"},
   116  			votersResults:  []string{"A"},
   117  		},
   118  		{
   119  			// 2: Two signers, voting to add three others (only accept first two)
   120  			name:    "1-vote-3",
   121  			signers: []string{"A", "B"},
   122  			voters:  []string{"A", "B"},
   123  			votes: []testerVote{
   124  				{signer: "A", voted: "C", auth: true},
   125  				{signer: "B", voted: "C", auth: true},
   126  				{signer: "A", voted: "D", auth: true},
   127  				{signer: "B", voted: "D", auth: true},
   128  				{signer: "C"},
   129  				{signer: "A", voted: "E", auth: false},
   130  				{signer: "B", voted: "E", auth: false},
   131  			},
   132  			signersResults: []string{"A", "B", "C", "D"},
   133  			votersResults:  []string{"A", "B"},
   134  		},
   135  		{
   136  			// 3: Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this)
   137  			name:    "1-drop",
   138  			signers: []string{"A"},
   139  			voters:  []string{"A"},
   140  			votes: []testerVote{
   141  				{signer: "A", voted: "A", auth: false},
   142  			},
   143  			signersResults: []string{"A"},
   144  			votersResults:  []string{},
   145  		},
   146  		{
   147  			// 4: Two signers, actually needing mutual consent to drop either of them (not fulfilled)
   148  			name:    "2-drop-fail",
   149  			signers: []string{"A", "B"},
   150  			voters:  []string{"A", "B"},
   151  			votes: []testerVote{
   152  				{signer: "A", voted: "B", auth: false},
   153  			},
   154  			signersResults: []string{"A", "B"},
   155  			votersResults:  []string{"A", "B"},
   156  		},
   157  		{
   158  			// 5: Two signers, actually needing mutual consent to drop either of them (fulfilled)
   159  			name:    "2-drop",
   160  			signers: []string{"A", "B"},
   161  			voters:  []string{"A", "B"},
   162  			votes: []testerVote{
   163  				{signer: "A", voted: "B", auth: false},
   164  				{signer: "B", voted: "B", auth: false},
   165  			},
   166  			signersResults: []string{"A", "B"},
   167  			votersResults:  []string{"A"},
   168  		},
   169  		{
   170  			// 6: Three signers, two of them deciding to drop the third
   171  			name:    "3-drop",
   172  			signers: []string{"A", "B", "C"},
   173  			voters:  []string{"A", "B", "C"},
   174  			votes: []testerVote{
   175  				{signer: "A", voted: "C", auth: false},
   176  				{signer: "B", voted: "C", auth: false},
   177  			},
   178  			signersResults: []string{"A", "B", "C"},
   179  			votersResults:  []string{"A", "B"},
   180  		},
   181  		{
   182  			// 7: Four signers, consensus of two not being enough to drop anyone
   183  			name:    "4-drop-fail",
   184  			signers: []string{"A", "B", "C", "D"},
   185  			voters:  []string{"A", "B", "C", "D"},
   186  			votes: []testerVote{
   187  				{signer: "A", voted: "C", auth: false},
   188  				{signer: "B", voted: "C", auth: false},
   189  			},
   190  			signersResults: []string{"A", "B", "C", "D"},
   191  			votersResults:  []string{"A", "B", "C", "D"},
   192  		},
   193  		{
   194  			// 8: Four signers, consensus of three already being enough to drop someone
   195  			name:    "4-drop",
   196  			signers: []string{"A", "B", "C", "D"},
   197  			voters:  []string{"A", "B", "C", "D"},
   198  			votes: []testerVote{
   199  				{signer: "A", voted: "D", auth: false},
   200  				{signer: "B", voted: "D", auth: false},
   201  				{signer: "C", voted: "D", auth: false},
   202  			},
   203  			signersResults: []string{"A", "B", "C", "D"},
   204  			votersResults:  []string{"A", "B", "C"},
   205  		},
   206  		{
   207  			// 9: Authorizations are counted once per signer per target
   208  			name:    "auth-count",
   209  			signers: []string{"A", "B"},
   210  			voters:  []string{"A", "B"},
   211  			votes: []testerVote{
   212  				{signer: "A", voted: "C", auth: true},
   213  				{signer: "B"},
   214  				{signer: "A", voted: "C", auth: true},
   215  				{signer: "B"},
   216  				{signer: "A", voted: "C", auth: true},
   217  			},
   218  			signersResults: []string{"A", "B"},
   219  			votersResults:  []string{"A", "B"},
   220  		},
   221  		{
   222  			// 10: Authorizing multiple accounts concurrently is permitted
   223  			name:    "auth-mult",
   224  			signers: []string{"A", "B"},
   225  			voters:  []string{"A", "B"},
   226  			votes: []testerVote{
   227  				{signer: "A", voted: "C", auth: true},
   228  				{signer: "B"},
   229  				{signer: "A", voted: "D", auth: true},
   230  				{signer: "B"},
   231  				{signer: "A"},
   232  				{signer: "B", voted: "D", auth: true},
   233  				{signer: "A"},
   234  				{signer: "B", voted: "C", auth: true},
   235  			},
   236  			signersResults: []string{"A", "B", "C", "D"},
   237  			votersResults:  []string{"A", "B"},
   238  		},
   239  		{
   240  			// 11: Deauthorizations are counted once per signer per target
   241  			name:    "deauth-count",
   242  			signers: []string{"A", "B"},
   243  			voters:  []string{"A", "B"},
   244  			votes: []testerVote{
   245  				{signer: "A", voted: "B", auth: false},
   246  				{signer: "B"},
   247  				{signer: "A", voted: "B", auth: false},
   248  				{signer: "B"},
   249  				{signer: "A", voted: "B", auth: false},
   250  			},
   251  			signersResults: []string{"A", "B"},
   252  			votersResults:  []string{"A", "B"},
   253  		},
   254  		{
   255  			// 12: Deauthorizing multiple accounts concurrently is permitted
   256  			name:    "deauth-mult",
   257  			signers: []string{"A", "B", "C", "D"},
   258  			voters:  []string{"A", "B", "C", "D"},
   259  			votes: []testerVote{
   260  				{signer: "A", voted: "C", auth: false},
   261  				{signer: "B"},
   262  				{signer: "C"},
   263  				{signer: "A", voted: "D", auth: false},
   264  				{signer: "B"},
   265  				{signer: "C"},
   266  				{signer: "A"},
   267  				{signer: "B", voted: "D", auth: false},
   268  				{signer: "C", voted: "D", auth: false},
   269  				{signer: "A"},
   270  				{signer: "B", voted: "C", auth: false},
   271  			},
   272  			signersResults: []string{"A", "B", "C", "D"},
   273  			votersResults:  []string{"A", "B"},
   274  		},
   275  		{
   276  			// 13: Votes from deauthorized signers are discarded immediately (deauth votes)
   277  			name:    "deauth-discard-deauth",
   278  			signers: []string{"A", "B", "C"},
   279  			voters:  []string{"A", "B", "C"},
   280  			votes: []testerVote{
   281  				{signer: "C", voted: "B", auth: false},
   282  				{signer: "A", voted: "C", auth: false},
   283  				{signer: "B", voted: "C", auth: false},
   284  				{signer: "A", voted: "B", auth: false},
   285  			},
   286  			signersResults: []string{"A", "B", "C"},
   287  			votersResults:  []string{"A", "B"},
   288  		},
   289  		{
   290  			// 14: Votes from deauthorized signers are discarded immediately (auth votes)
   291  			name:    "deauth-discard-auth",
   292  			signers: []string{"A", "B", "C"},
   293  			voters:  []string{"A", "B", "C"},
   294  			votes: []testerVote{
   295  				{signer: "C", voted: "D", auth: true},
   296  				{signer: "A", voted: "C", auth: false},
   297  				{signer: "B", voted: "C", auth: false},
   298  				{signer: "A", voted: "D", auth: true},
   299  			},
   300  			signersResults: []string{"A", "B", "C"},
   301  			votersResults:  []string{"A", "B"},
   302  		},
   303  		{
   304  			// 15: Cascading changes are not allowed, only the account being voted on may change
   305  			name:    "no-cascade",
   306  			signers: []string{"A", "B", "C", "D"},
   307  			voters:  []string{"A", "B", "C", "D"},
   308  			votes: []testerVote{
   309  				{signer: "A", voted: "C", auth: false},
   310  				{signer: "B"},
   311  				{signer: "C"},
   312  				{signer: "A", voted: "D", auth: false},
   313  				{signer: "B", voted: "C", auth: false},
   314  				{signer: "C"},
   315  				{signer: "A"},
   316  				{signer: "B", voted: "D", auth: false},
   317  				{signer: "C", voted: "D", auth: false},
   318  			},
   319  			signersResults: []string{"A", "B", "C", "D"},
   320  			votersResults:  []string{"A", "B", "C"},
   321  		},
   322  		{
   323  			// 16: Changes reaching consensus out of bounds (via a deauth) execute on touch
   324  			name:    "out-of-bounds",
   325  			signers: []string{"A", "B", "C", "D"},
   326  			voters:  []string{"A", "B", "C", "D"},
   327  			votes: []testerVote{
   328  				{signer: "A", voted: "C", auth: false},
   329  				{signer: "B"},
   330  				{signer: "C"},
   331  				{signer: "A", voted: "D", auth: false},
   332  				{signer: "B", voted: "C", auth: false},
   333  				{signer: "C"},
   334  				{signer: "A"},
   335  				{signer: "B", voted: "D", auth: false},
   336  				{signer: "C", voted: "D", auth: false},
   337  				{signer: "A"},
   338  				{signer: "B"},
   339  				{signer: "C", voted: "C", auth: true},
   340  			},
   341  			signersResults: []string{"A", "B", "C", "D"},
   342  			votersResults:  []string{"A", "B"},
   343  		},
   344  		{
   345  			// 17: Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch
   346  			name:    "out-of-bounds-out",
   347  			signers: []string{"A", "B", "C", "D"},
   348  			voters:  []string{"A", "B", "C", "D"},
   349  			votes: []testerVote{
   350  				{signer: "A", voted: "C", auth: false, voterElection: true},
   351  				{signer: "B"},
   352  				{signer: "C"},
   353  				{signer: "A", voted: "D", auth: false, voterElection: true},
   354  				{signer: "B", voted: "C", auth: false, voterElection: true},
   355  				{signer: "C"},
   356  				{signer: "A"},
   357  				{signer: "B", voted: "D", auth: false, voterElection: true},
   358  				{signer: "C", voted: "D", auth: false, voterElection: true},
   359  				{signer: "A"},
   360  				{signer: "B", voted: "C", auth: true, voterElection: true},
   361  			},
   362  			signersResults: []string{"A", "B", "C", "D"},
   363  			votersResults:  []string{"A", "B", "C"},
   364  		},
   365  		{
   366  			// 18: Ensure that pending votes don't survive authorization status changes. This
   367  			// corner case can only appear if a signer is quickly added, removed and then
   368  			// readded (or the inverse), while one of the original voters dropped. If a
   369  			// past vote is left cached in the system somewhere, this will interfere with
   370  			// the final signer outcome.
   371  			name:    "discard-pending",
   372  			signers: []string{"A", "B", "C", "D", "E", "F"},
   373  			voters:  []string{"A", "B", "C", "D", "E"},
   374  			votes: []testerVote{
   375  				{signer: "A", voted: "F", auth: true, voterElection: true}, // Authorize F, 3 votes needed
   376  				{signer: "B", voted: "F", auth: true, voterElection: true},
   377  				{signer: "C", voted: "F", auth: true, voterElection: true},
   378  				{signer: "D", voted: "F", auth: false, voterElection: true}, // Deauthorize F, 3 votes needed (leave A's previous vote "unchanged")
   379  				{signer: "E", voted: "F", auth: false, voterElection: true},
   380  				{signer: "B", voted: "F", auth: false, voterElection: true},
   381  				{signer: "C", voted: "F", auth: false, voterElection: true},
   382  				{signer: "D", voted: "F", auth: true, voterElection: true}, // Almost authorize F as a voter, 2/3 votes needed
   383  				{signer: "E", voted: "F", auth: true, voterElection: true},
   384  				{signer: "B", voted: "A", auth: false, voterElection: true}, // Deauthorize A as a voter, 3 votes needed
   385  				{signer: "C", voted: "A", auth: false, voterElection: true},
   386  				{signer: "D", voted: "A", auth: false, voterElection: true},
   387  				{signer: "E"},
   388  				{signer: "B", voted: "F", auth: true, voterElection: true}, // Finish authorizing F as a voter, 3/3 votes needed
   389  			},
   390  			//results: []string{"B", "C", "D", "E", "F"},
   391  			signersResults: []string{"A", "B", "C", "D", "E", "F"},
   392  			votersResults:  []string{"B", "C", "D", "E", "F"},
   393  		},
   394  		{
   395  			// 19: Epoch transitions reset all votes to allow chain checkpointing
   396  			name:    "epoch-reset",
   397  			epoch:   3,
   398  			signers: []string{"A", "B"},
   399  			voters:  []string{"A", "B"},
   400  			votes: []testerVote{
   401  				{signer: "A", voted: "C", auth: true},
   402  				{signer: "B"},
   403  				{signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots)
   404  				{signer: "B", voted: "C", auth: true},
   405  			},
   406  			signersResults: []string{"A", "B"},
   407  			votersResults:  []string{"A", "B"},
   408  		},
   409  	}
   410  	// Run through the scenarios and test them
   411  	for _, tt := range tests {
   412  		t.Run(tt.name, tt.run)
   413  	}
   414  }
   415  
   416  type votingTest struct {
   417  	name           string
   418  	epoch          uint64
   419  	signers        []string
   420  	voters         []string
   421  	votes          []testerVote
   422  	signersResults []string
   423  	votersResults  []string
   424  }
   425  
   426  func (tt *votingTest) run(t *testing.T) {
   427  	// Create the account pool and generate the initial set of signers
   428  	accounts := newTesterAccountPool()
   429  
   430  	signers := make([]common.Address, len(tt.signers))
   431  	voters := make([]common.Address, len(tt.voters))
   432  	for j, signer := range tt.signers {
   433  		signers[j] = accounts.address(signer)
   434  	}
   435  	for j, voter := range tt.voters {
   436  		voters[j] = accounts.address(voter)
   437  	}
   438  	for j := 0; j < len(signers); j++ {
   439  		for k := j + 1; k < len(signers); k++ {
   440  			if bytes.Compare(signers[j][:], signers[k][:]) > 0 {
   441  				signers[j], signers[k] = signers[k], signers[j]
   442  			}
   443  		}
   444  	}
   445  	for j := 0; j < len(voters); j++ {
   446  		for k := j + 1; k < len(voters); k++ {
   447  			if bytes.Compare(voters[j][:], voters[k][:]) > 0 {
   448  				voters[j], voters[k] = voters[k], voters[j]
   449  			}
   450  		}
   451  	}
   452  	// Create the genesis block with the initial set of signers
   453  	genesis := &core.Genesis{
   454  		ExtraData: make([]byte, extraVanity),
   455  		Signers:   signers,
   456  		Voters:    voters,
   457  		Signer:    make([]byte, signatureLength),
   458  	}
   459  	// Create a pristine blockchain with the genesis injected
   460  	db := ethdb.NewMemDatabase()
   461  	genesis.Commit(db)
   462  
   463  	// Assemble a chain of headers from the cast votes
   464  	headers := make([]*types.Header, len(tt.votes))
   465  	for j, vote := range tt.votes {
   466  		headers[j] = &types.Header{
   467  			Number: big.NewInt(int64(j) + 1),
   468  			Time:   big.NewInt(int64(j) * int64(params.DefaultCliquePeriod)),
   469  			Signer: make([]byte, signatureLength),
   470  			Extra:  make([]byte, extraVanity),
   471  		}
   472  		if j > 0 {
   473  			headers[j].ParentHash = headers[j-1].Hash()
   474  		}
   475  		if vote.auth {
   476  			copy(headers[j].Nonce[:], nonceAuthVote)
   477  		}
   478  		headers[j].Extra = ExtraAppendVote(headers[j].Extra, accounts.address(vote.voted), vote.voterElection)
   479  		accounts.sign(headers[j], vote.signer)
   480  	}
   481  	// Pass all the headers through clique and ensure tallying succeeds
   482  	head := headers[len(headers)-1]
   483  
   484  	snap, err := New(&params.CliqueConfig{Epoch: tt.epoch}, db).
   485  		snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers)
   486  	if err != nil {
   487  		t.Errorf("failed to create voting snapshot: %v", err)
   488  		return
   489  	}
   490  	// Verify the final list of signers against the expected ones
   491  	signers = make([]common.Address, len(tt.signersResults))
   492  	for j, signer := range tt.signersResults {
   493  		signers[j] = accounts.address(signer)
   494  	}
   495  	for j := 0; j < len(signers); j++ {
   496  		for k := j + 1; k < len(signers); k++ {
   497  			if bytes.Compare(signers[j][:], signers[k][:]) > 0 {
   498  				signers[j], signers[k] = signers[k], signers[j]
   499  			}
   500  		}
   501  	}
   502  	signersResult := snap.signers()
   503  	if len(signersResult) != len(signers) {
   504  		t.Errorf("signers mismatch: have %x, want %x", signersResult, signers)
   505  		return
   506  	}
   507  	for j := 0; j < len(signersResult); j++ {
   508  		if !bytes.Equal(signersResult[j][:], signers[j][:]) {
   509  			t.Errorf("signer %d: signer mismatch: have %x, want %x", j, signersResult[j], signers[j])
   510  		}
   511  	}
   512  	// Verify the final list of voters against the expected ones
   513  	voters = make([]common.Address, len(tt.votersResults))
   514  	for j, voter := range tt.votersResults {
   515  		voters[j] = accounts.address(voter)
   516  	}
   517  	for j := 0; j < len(voters); j++ {
   518  		for k := j + 1; k < len(voters); k++ {
   519  			if bytes.Compare(voters[j][:], voters[k][:]) > 0 {
   520  				voters[j], voters[k] = voters[k], voters[j]
   521  			}
   522  		}
   523  	}
   524  	votersResult := snap.voters()
   525  	if len(votersResult) != len(voters) {
   526  		t.Errorf("voters mismatch: have %x, want %x", votersResult, voters)
   527  		return
   528  	}
   529  	for j := 0; j < len(votersResult); j++ {
   530  		if !bytes.Equal(votersResult[j][:], voters[j][:]) {
   531  			t.Errorf("voter %d: voter mismatch: have %x, want %x", j, votersResult[j], voters[j])
   532  		}
   533  	}
   534  }