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