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(¶ms.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 }