github.com/reapchain/go-reapchain@v0.2.15-0.20210609012950-9735c110c705/consensus/podc/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 "testing" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/consensus/podc" 27 "github.com/ethereum/go-ethereum/core" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/core/vm" 30 "github.com/ethereum/go-ethereum/crypto" 31 "github.com/ethereum/go-ethereum/ethdb" 32 "github.com/ethereum/go-ethereum/event" 33 ) 34 35 type testerVote struct { 36 validator string 37 voted string 38 auth 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, validator string) { 55 // Ensure we have a persistent key for the validator 56 if ap.accounts[validator] == nil { 57 ap.accounts[validator], _ = crypto.GenerateKey() 58 } 59 // Sign the header and embed the signature in extra data 60 hashData := crypto.Keccak256([]byte(sigHash(header).Bytes())) 61 sig, _ := crypto.Sign(hashData, ap.accounts[validator]) 62 63 writeSeal(header, sig) 64 } 65 66 func (ap *testerAccountPool) address(account string) common.Address { 67 // Ensure we have a persistent key for the account 68 if ap.accounts[account] == nil { 69 ap.accounts[account], _ = crypto.GenerateKey() 70 } 71 // Resolve and return the Ethereum address 72 return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) 73 } 74 75 // Tests that voting is evaluated correctly for various simple and complex scenarios. 76 func TestVoting(t *testing.T) { 77 // Define the various voting scenarios to test 78 tests := []struct { 79 epoch uint64 80 validators []string 81 votes []testerVote 82 results []string 83 }{ 84 { 85 // Single validator, no votes cast 86 validators: []string{"A"}, 87 votes: []testerVote{{validator: "A"}}, 88 results: []string{"A"}, 89 }, { 90 // Single validator, voting to add two others (only accept first, second needs 2 votes) 91 validators: []string{"A"}, 92 votes: []testerVote{ 93 {validator: "A", voted: "B", auth: true}, 94 {validator: "B"}, 95 {validator: "A", voted: "C", auth: true}, 96 }, 97 results: []string{"A", "B"}, 98 }, { 99 // Two validators, voting to add three others (only accept first two, third needs 3 votes already) 100 validators: []string{"A", "B"}, 101 votes: []testerVote{ 102 {validator: "A", voted: "C", auth: true}, 103 {validator: "B", voted: "C", auth: true}, 104 {validator: "A", voted: "D", auth: true}, 105 {validator: "B", voted: "D", auth: true}, 106 {validator: "C"}, 107 {validator: "A", voted: "E", auth: true}, 108 {validator: "B", voted: "E", auth: true}, 109 }, 110 results: []string{"A", "B", "C", "D"}, 111 }, { 112 // Single validator, dropping itself (weird, but one less cornercase by explicitly allowing this) 113 validators: []string{"A"}, 114 votes: []testerVote{ 115 {validator: "A", voted: "A", auth: false}, 116 }, 117 results: []string{}, 118 }, { 119 // Two validators, actually needing mutual consent to drop either of them (not fulfilled) 120 validators: []string{"A", "B"}, 121 votes: []testerVote{ 122 {validator: "A", voted: "B", auth: false}, 123 }, 124 results: []string{"A", "B"}, 125 }, { 126 // Two validators, actually needing mutual consent to drop either of them (fulfilled) 127 validators: []string{"A", "B"}, 128 votes: []testerVote{ 129 {validator: "A", voted: "B", auth: false}, 130 {validator: "B", voted: "B", auth: false}, 131 }, 132 results: []string{"A"}, 133 }, { 134 // Three validators, two of them deciding to drop the third 135 validators: []string{"A", "B", "C"}, 136 votes: []testerVote{ 137 {validator: "A", voted: "C", auth: false}, 138 {validator: "B", voted: "C", auth: false}, 139 }, 140 results: []string{"A", "B"}, 141 }, { 142 // Four validators, consensus of two not being enough to drop anyone 143 validators: []string{"A", "B", "C", "D"}, 144 votes: []testerVote{ 145 {validator: "A", voted: "C", auth: false}, 146 {validator: "B", voted: "C", auth: false}, 147 }, 148 results: []string{"A", "B", "C", "D"}, 149 }, { 150 // Four validators, consensus of three already being enough to drop someone 151 validators: []string{"A", "B", "C", "D"}, 152 votes: []testerVote{ 153 {validator: "A", voted: "D", auth: false}, 154 {validator: "B", voted: "D", auth: false}, 155 {validator: "C", voted: "D", auth: false}, 156 }, 157 results: []string{"A", "B", "C"}, 158 }, { 159 // Authorizations are counted once per validator per target 160 validators: []string{"A", "B"}, 161 votes: []testerVote{ 162 {validator: "A", voted: "C", auth: true}, 163 {validator: "B"}, 164 {validator: "A", voted: "C", auth: true}, 165 {validator: "B"}, 166 {validator: "A", voted: "C", auth: true}, 167 }, 168 results: []string{"A", "B"}, 169 }, { 170 // Authorizing multiple accounts concurrently is permitted 171 validators: []string{"A", "B"}, 172 votes: []testerVote{ 173 {validator: "A", voted: "C", auth: true}, 174 {validator: "B"}, 175 {validator: "A", voted: "D", auth: true}, 176 {validator: "B"}, 177 {validator: "A"}, 178 {validator: "B", voted: "D", auth: true}, 179 {validator: "A"}, 180 {validator: "B", voted: "C", auth: true}, 181 }, 182 results: []string{"A", "B", "C", "D"}, 183 }, { 184 // Deauthorizations are counted once per validator per target 185 validators: []string{"A", "B"}, 186 votes: []testerVote{ 187 {validator: "A", voted: "B", auth: false}, 188 {validator: "B"}, 189 {validator: "A", voted: "B", auth: false}, 190 {validator: "B"}, 191 {validator: "A", voted: "B", auth: false}, 192 }, 193 results: []string{"A", "B"}, 194 }, { 195 // Deauthorizing multiple accounts concurrently is permitted 196 validators: []string{"A", "B", "C", "D"}, 197 votes: []testerVote{ 198 {validator: "A", voted: "C", auth: false}, 199 {validator: "B"}, 200 {validator: "C"}, 201 {validator: "A", voted: "D", auth: false}, 202 {validator: "B"}, 203 {validator: "C"}, 204 {validator: "A"}, 205 {validator: "B", voted: "D", auth: false}, 206 {validator: "C", voted: "D", auth: false}, 207 {validator: "A"}, 208 {validator: "B", voted: "C", auth: false}, 209 }, 210 results: []string{"A", "B"}, 211 }, { 212 // Votes from deauthorized validators are discarded immediately (deauth votes) 213 validators: []string{"A", "B", "C"}, 214 votes: []testerVote{ 215 {validator: "C", voted: "B", auth: false}, 216 {validator: "A", voted: "C", auth: false}, 217 {validator: "B", voted: "C", auth: false}, 218 {validator: "A", voted: "B", auth: false}, 219 }, 220 results: []string{"A", "B"}, 221 }, { 222 // Votes from deauthorized validators are discarded immediately (auth votes) 223 validators: []string{"A", "B", "C"}, 224 votes: []testerVote{ 225 {validator: "C", voted: "B", auth: false}, 226 {validator: "A", voted: "C", auth: false}, 227 {validator: "B", voted: "C", auth: false}, 228 {validator: "A", voted: "B", auth: false}, 229 }, 230 results: []string{"A", "B"}, 231 }, { 232 // Cascading changes are not allowed, only the the account being voted on may change 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 }, 245 results: []string{"A", "B", "C"}, 246 }, { 247 // Changes reaching consensus out of bounds (via a deauth) execute on touch 248 validators: []string{"A", "B", "C", "D"}, 249 votes: []testerVote{ 250 {validator: "A", voted: "C", auth: false}, 251 {validator: "B"}, 252 {validator: "C"}, 253 {validator: "A", voted: "D", auth: false}, 254 {validator: "B", voted: "C", auth: false}, 255 {validator: "C"}, 256 {validator: "A"}, 257 {validator: "B", voted: "D", auth: false}, 258 {validator: "C", voted: "D", auth: false}, 259 {validator: "A"}, 260 {validator: "C", voted: "C", auth: true}, 261 }, 262 results: []string{"A", "B"}, 263 }, { 264 // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch 265 validators: []string{"A", "B", "C", "D"}, 266 votes: []testerVote{ 267 {validator: "A", voted: "C", auth: false}, 268 {validator: "B"}, 269 {validator: "C"}, 270 {validator: "A", voted: "D", auth: false}, 271 {validator: "B", voted: "C", auth: false}, 272 {validator: "C"}, 273 {validator: "A"}, 274 {validator: "B", voted: "D", auth: false}, 275 {validator: "C", voted: "D", auth: false}, 276 {validator: "A"}, 277 {validator: "B", voted: "C", auth: true}, 278 }, 279 results: []string{"A", "B", "C"}, 280 }, { 281 // Ensure that pending votes don't survive authorization status changes. This 282 // corner case can only appear if a validator is quickly added, remove and then 283 // readded (or the inverse), while one of the original voters dropped. If a 284 // past vote is left cached in the system somewhere, this will interfere with 285 // the final validator outcome. 286 validators: []string{"A", "B", "C", "D", "E"}, 287 votes: []testerVote{ 288 {validator: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed 289 {validator: "B", voted: "F", auth: true}, 290 {validator: "C", voted: "F", auth: true}, 291 {validator: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") 292 {validator: "E", voted: "F", auth: false}, 293 {validator: "B", voted: "F", auth: false}, 294 {validator: "C", voted: "F", auth: false}, 295 {validator: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed 296 {validator: "E", voted: "F", auth: true}, 297 {validator: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed 298 {validator: "C", voted: "A", auth: false}, 299 {validator: "D", voted: "A", auth: false}, 300 {validator: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed 301 }, 302 results: []string{"B", "C", "D", "E", "F"}, 303 }, { 304 // Epoch transitions reset all votes to allow chain checkpointing 305 epoch: 3, 306 validators: []string{"A", "B"}, 307 votes: []testerVote{ 308 {validator: "A", voted: "C", auth: true}, 309 {validator: "B"}, 310 {validator: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) 311 {validator: "B", voted: "C", auth: true}, 312 }, 313 results: []string{"A", "B"}, 314 }, 315 } 316 // Run through the scenarios and test them 317 for i, tt := range tests { 318 // Create the account pool and generate the initial set of validators 319 accounts := newTesterAccountPool() 320 321 validators := make([]common.Address, len(tt.validators)) 322 for j, validator := range tt.validators { 323 validators[j] = accounts.address(validator) 324 } 325 for j := 0; j < len(validators); j++ { 326 for k := j + 1; k < len(validators); k++ { 327 if bytes.Compare(validators[j][:], validators[k][:]) > 0 { 328 validators[j], validators[k] = validators[k], validators[j] 329 } 330 } 331 } 332 // Create the genesis block with the initial set of validators 333 genesis := &core.Genesis{ 334 Difficulty: defaultDifficulty, 335 Mixhash: types.PoDCDigest, 336 } 337 b, _ := genesis.ToBlock() 338 extra, _ := prepareExtra(b.Header(), validators) 339 genesis.ExtraData = extra 340 // Create a pristine blockchain with the genesis injected 341 db, _ := ethdb.NewMemDatabase() 342 genesis.Commit(db) 343 344 eventMux := new(event.TypeMux) 345 config := podc.DefaultConfig 346 if tt.epoch != 0 { 347 config.Epoch = tt.epoch 348 } 349 engine := New(config, eventMux, accounts.accounts[tt.validators[0]], db).(*simpleBackend) 350 chain, err := core.NewBlockChain(db, genesis.Config, engine, eventMux, vm.Config{}) 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: big.NewInt(int64(j) * int64(config.BlockPauseTime)), 358 Coinbase: accounts.address(vote.voted), 359 Difficulty: defaultDifficulty, 360 MixDigest: types.PoDCDigest, 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 }