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