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 */