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 }