github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/consensus/clique/snapshot_test.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of the Spectrum library. 3 // 4 // The Spectrum 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 Spectrum 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 Spectrum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package clique 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "math/big" 23 "testing" 24 25 "github.com/SmartMeshFoundation/Spectrum/common" 26 "github.com/SmartMeshFoundation/Spectrum/core" 27 "github.com/SmartMeshFoundation/Spectrum/core/types" 28 "github.com/SmartMeshFoundation/Spectrum/crypto" 29 "github.com/SmartMeshFoundation/Spectrum/ethdb" 30 "github.com/SmartMeshFoundation/Spectrum/params" 31 ) 32 33 type testerVote struct { 34 signer string 35 voted string 36 auth bool 37 } 38 39 // testerAccountPool is a pool to maintain currently active tester accounts, 40 // mapped from textual names used in the tests below to actual Ethereum private 41 // keys capable of signing transactions. 42 type testerAccountPool struct { 43 accounts map[string]*ecdsa.PrivateKey 44 } 45 46 func newTesterAccountPool() *testerAccountPool { 47 return &testerAccountPool{ 48 accounts: make(map[string]*ecdsa.PrivateKey), 49 } 50 } 51 52 func (ap *testerAccountPool) sign(header *types.Header, signer string) { 53 // Ensure we have a persistent key for the signer 54 if ap.accounts[signer] == nil { 55 ap.accounts[signer], _ = crypto.GenerateKey() 56 } 57 // Sign the header and embed the signature in extra data 58 sig, _ := crypto.Sign(sigHash(header).Bytes(), ap.accounts[signer]) 59 copy(header.Extra[len(header.Extra)-65:], sig) 60 } 61 62 func (ap *testerAccountPool) address(account string) common.Address { 63 // Ensure we have a persistent key for the account 64 if ap.accounts[account] == nil { 65 ap.accounts[account], _ = crypto.GenerateKey() 66 } 67 // Resolve and return the Ethereum address 68 return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) 69 } 70 71 // testerChainReader implements consensus.ChainReader to access the genesis 72 // block. All other methods and requests will panic. 73 type testerChainReader struct { 74 db ethdb.Database 75 } 76 77 func (r *testerChainReader) Config() *params.ChainConfig { return params.AllCliqueProtocolChanges } 78 func (r *testerChainReader) CurrentHeader() *types.Header { panic("not supported") } 79 func (r *testerChainReader) GetHeader(common.Hash, uint64) *types.Header { panic("not supported") } 80 func (r *testerChainReader) GetBlock(common.Hash, uint64) *types.Block { panic("not supported") } 81 func (r *testerChainReader) GetHeaderByHash(common.Hash) *types.Header { panic("not supported") } 82 func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header { 83 if number == 0 { 84 return core.GetHeader(r.db, core.GetCanonicalHash(r.db, 0), 0) 85 } 86 panic("not supported") 87 } 88 89 // Tests that voting is evaluated correctly for various simple and complex scenarios. 90 func TestVoting(t *testing.T) { 91 // Define the various voting scenarios to test 92 tests := []struct { 93 epoch uint64 94 signers []string 95 votes []testerVote 96 results []string 97 }{ 98 { 99 // Single signer, no votes cast 100 signers: []string{"A"}, 101 votes: []testerVote{{signer: "A"}}, 102 results: []string{"A"}, 103 }, { 104 // Single signer, voting to add two others (only accept first, second needs 2 votes) 105 signers: []string{"A"}, 106 votes: []testerVote{ 107 {signer: "A", voted: "B", auth: true}, 108 {signer: "B"}, 109 {signer: "A", voted: "C", auth: true}, 110 }, 111 results: []string{"A", "B"}, 112 }, { 113 // Two signers, voting to add three others (only accept first two, third needs 3 votes already) 114 signers: []string{"A", "B"}, 115 votes: []testerVote{ 116 {signer: "A", voted: "C", auth: true}, 117 {signer: "B", voted: "C", auth: true}, 118 {signer: "A", voted: "D", auth: true}, 119 {signer: "B", voted: "D", auth: true}, 120 {signer: "C"}, 121 {signer: "A", voted: "E", auth: true}, 122 {signer: "B", voted: "E", auth: true}, 123 }, 124 results: []string{"A", "B", "C", "D"}, 125 }, { 126 // Single signer, dropping itself (weird, but one less cornercase by explicitly allowing this) 127 signers: []string{"A"}, 128 votes: []testerVote{ 129 {signer: "A", voted: "A", auth: false}, 130 }, 131 results: []string{}, 132 }, { 133 // Two signers, actually needing mutual consent to drop either of them (not fulfilled) 134 signers: []string{"A", "B"}, 135 votes: []testerVote{ 136 {signer: "A", voted: "B", auth: false}, 137 }, 138 results: []string{"A", "B"}, 139 }, { 140 // Two signers, actually needing mutual consent to drop either of them (fulfilled) 141 signers: []string{"A", "B"}, 142 votes: []testerVote{ 143 {signer: "A", voted: "B", auth: false}, 144 {signer: "B", voted: "B", auth: false}, 145 }, 146 results: []string{"A"}, 147 }, { 148 // Three signers, two of them deciding to drop the third 149 signers: []string{"A", "B", "C"}, 150 votes: []testerVote{ 151 {signer: "A", voted: "C", auth: false}, 152 {signer: "B", voted: "C", auth: false}, 153 }, 154 results: []string{"A", "B"}, 155 }, { 156 // Four signers, consensus of two not being enough to drop anyone 157 signers: []string{"A", "B", "C", "D"}, 158 votes: []testerVote{ 159 {signer: "A", voted: "C", auth: false}, 160 {signer: "B", voted: "C", auth: false}, 161 }, 162 results: []string{"A", "B", "C", "D"}, 163 }, { 164 // Four signers, consensus of three already being enough to drop someone 165 signers: []string{"A", "B", "C", "D"}, 166 votes: []testerVote{ 167 {signer: "A", voted: "D", auth: false}, 168 {signer: "B", voted: "D", auth: false}, 169 {signer: "C", voted: "D", auth: false}, 170 }, 171 results: []string{"A", "B", "C"}, 172 }, { 173 // Authorizations are counted once per signer per target 174 signers: []string{"A", "B"}, 175 votes: []testerVote{ 176 {signer: "A", voted: "C", auth: true}, 177 {signer: "B"}, 178 {signer: "A", voted: "C", auth: true}, 179 {signer: "B"}, 180 {signer: "A", voted: "C", auth: true}, 181 }, 182 results: []string{"A", "B"}, 183 }, { 184 // Authorizing multiple accounts concurrently is permitted 185 signers: []string{"A", "B"}, 186 votes: []testerVote{ 187 {signer: "A", voted: "C", auth: true}, 188 {signer: "B"}, 189 {signer: "A", voted: "D", auth: true}, 190 {signer: "B"}, 191 {signer: "A"}, 192 {signer: "B", voted: "D", auth: true}, 193 {signer: "A"}, 194 {signer: "B", voted: "C", auth: true}, 195 }, 196 results: []string{"A", "B", "C", "D"}, 197 }, { 198 // Deauthorizations are counted once per signer per target 199 signers: []string{"A", "B"}, 200 votes: []testerVote{ 201 {signer: "A", voted: "B", auth: false}, 202 {signer: "B"}, 203 {signer: "A", voted: "B", auth: false}, 204 {signer: "B"}, 205 {signer: "A", voted: "B", auth: false}, 206 }, 207 results: []string{"A", "B"}, 208 }, { 209 // Deauthorizing multiple accounts concurrently is permitted 210 signers: []string{"A", "B", "C", "D"}, 211 votes: []testerVote{ 212 {signer: "A", voted: "C", auth: false}, 213 {signer: "B"}, 214 {signer: "C"}, 215 {signer: "A", voted: "D", auth: false}, 216 {signer: "B"}, 217 {signer: "C"}, 218 {signer: "A"}, 219 {signer: "B", voted: "D", auth: false}, 220 {signer: "C", voted: "D", auth: false}, 221 {signer: "A"}, 222 {signer: "B", voted: "C", auth: false}, 223 }, 224 results: []string{"A", "B"}, 225 }, { 226 // Votes from deauthorized signers are discarded immediately (deauth votes) 227 signers: []string{"A", "B", "C"}, 228 votes: []testerVote{ 229 {signer: "C", voted: "B", auth: false}, 230 {signer: "A", voted: "C", auth: false}, 231 {signer: "B", voted: "C", auth: false}, 232 {signer: "A", voted: "B", auth: false}, 233 }, 234 results: []string{"A", "B"}, 235 }, { 236 // Votes from deauthorized signers are discarded immediately (auth votes) 237 signers: []string{"A", "B", "C"}, 238 votes: []testerVote{ 239 {signer: "C", voted: "B", auth: false}, 240 {signer: "A", voted: "C", auth: false}, 241 {signer: "B", voted: "C", auth: false}, 242 {signer: "A", voted: "B", auth: false}, 243 }, 244 results: []string{"A", "B"}, 245 }, { 246 // Cascading changes are not allowed, only the account being voted on may change 247 signers: []string{"A", "B", "C", "D"}, 248 votes: []testerVote{ 249 {signer: "A", voted: "C", auth: false}, 250 {signer: "B"}, 251 {signer: "C"}, 252 {signer: "A", voted: "D", auth: false}, 253 {signer: "B", voted: "C", auth: false}, 254 {signer: "C"}, 255 {signer: "A"}, 256 {signer: "B", voted: "D", auth: false}, 257 {signer: "C", voted: "D", auth: false}, 258 }, 259 results: []string{"A", "B", "C"}, 260 }, { 261 // Changes reaching consensus out of bounds (via a deauth) execute on touch 262 signers: []string{"A", "B", "C", "D"}, 263 votes: []testerVote{ 264 {signer: "A", voted: "C", auth: false}, 265 {signer: "B"}, 266 {signer: "C"}, 267 {signer: "A", voted: "D", auth: false}, 268 {signer: "B", voted: "C", auth: false}, 269 {signer: "C"}, 270 {signer: "A"}, 271 {signer: "B", voted: "D", auth: false}, 272 {signer: "C", voted: "D", auth: false}, 273 {signer: "A"}, 274 {signer: "C", voted: "C", auth: true}, 275 }, 276 results: []string{"A", "B"}, 277 }, { 278 // Changes reaching consensus out of bounds (via a deauth) may go out of consensus on first touch 279 signers: []string{"A", "B", "C", "D"}, 280 votes: []testerVote{ 281 {signer: "A", voted: "C", auth: false}, 282 {signer: "B"}, 283 {signer: "C"}, 284 {signer: "A", voted: "D", auth: false}, 285 {signer: "B", voted: "C", auth: false}, 286 {signer: "C"}, 287 {signer: "A"}, 288 {signer: "B", voted: "D", auth: false}, 289 {signer: "C", voted: "D", auth: false}, 290 {signer: "A"}, 291 {signer: "B", voted: "C", auth: true}, 292 }, 293 results: []string{"A", "B", "C"}, 294 }, { 295 // Ensure that pending votes don't survive authorization status changes. This 296 // corner case can only appear if a signer is quickly added, removed and then 297 // readded (or the inverse), while one of the original voters dropped. If a 298 // past vote is left cached in the system somewhere, this will interfere with 299 // the final signer outcome. 300 signers: []string{"A", "B", "C", "D", "E"}, 301 votes: []testerVote{ 302 {signer: "A", voted: "F", auth: true}, // Authorize F, 3 votes needed 303 {signer: "B", voted: "F", auth: true}, 304 {signer: "C", voted: "F", auth: true}, 305 {signer: "D", voted: "F", auth: false}, // Deauthorize F, 4 votes needed (leave A's previous vote "unchanged") 306 {signer: "E", voted: "F", auth: false}, 307 {signer: "B", voted: "F", auth: false}, 308 {signer: "C", voted: "F", auth: false}, 309 {signer: "D", voted: "F", auth: true}, // Almost authorize F, 2/3 votes needed 310 {signer: "E", voted: "F", auth: true}, 311 {signer: "B", voted: "A", auth: false}, // Deauthorize A, 3 votes needed 312 {signer: "C", voted: "A", auth: false}, 313 {signer: "D", voted: "A", auth: false}, 314 {signer: "B", voted: "F", auth: true}, // Finish authorizing F, 3/3 votes needed 315 }, 316 results: []string{"B", "C", "D", "E", "F"}, 317 }, { 318 // Epoch transitions reset all votes to allow chain checkpointing 319 epoch: 3, 320 signers: []string{"A", "B"}, 321 votes: []testerVote{ 322 {signer: "A", voted: "C", auth: true}, 323 {signer: "B"}, 324 {signer: "A"}, // Checkpoint block, (don't vote here, it's validated outside of snapshots) 325 {signer: "B", voted: "C", auth: true}, 326 }, 327 results: []string{"A", "B"}, 328 }, 329 } 330 // Run through the scenarios and test them 331 for i, tt := range tests { 332 // Create the account pool and generate the initial set of signers 333 accounts := newTesterAccountPool() 334 335 signers := make([]common.Address, len(tt.signers)) 336 for j, signer := range tt.signers { 337 signers[j] = accounts.address(signer) 338 } 339 for j := 0; j < len(signers); j++ { 340 for k := j + 1; k < len(signers); k++ { 341 if bytes.Compare(signers[j][:], signers[k][:]) > 0 { 342 signers[j], signers[k] = signers[k], signers[j] 343 } 344 } 345 } 346 // Create the genesis block with the initial set of signers 347 genesis := &core.Genesis{ 348 ExtraData: make([]byte, extraVanity+common.AddressLength*len(signers)+extraSeal), 349 } 350 for j, signer := range signers { 351 copy(genesis.ExtraData[extraVanity+j*common.AddressLength:], signer[:]) 352 } 353 // Create a pristine blockchain with the genesis injected 354 db, _ := ethdb.NewMemDatabase() 355 genesis.Commit(db) 356 357 // Assemble a chain of headers from the cast votes 358 headers := make([]*types.Header, len(tt.votes)) 359 for j, vote := range tt.votes { 360 headers[j] = &types.Header{ 361 Number: big.NewInt(int64(j) + 1), 362 Time: big.NewInt(int64(j) * int64(blockPeriod)), 363 Coinbase: accounts.address(vote.voted), 364 Extra: make([]byte, extraVanity+extraSeal), 365 } 366 if j > 0 { 367 headers[j].ParentHash = headers[j-1].Hash() 368 } 369 if vote.auth { 370 copy(headers[j].Nonce[:], nonceAuthVote) 371 } 372 accounts.sign(headers[j], vote.signer) 373 } 374 // Pass all the headers through clique and ensure tallying succeeds 375 head := headers[len(headers)-1] 376 377 snap, err := New(¶ms.CliqueConfig{Epoch: tt.epoch}, db).snapshot(&testerChainReader{db: db}, head.Number.Uint64(), head.Hash(), headers) 378 if err != nil { 379 t.Errorf("test %d: failed to create voting snapshot: %v", i, err) 380 continue 381 } 382 // Verify the final list of signers against the expected ones 383 signers = make([]common.Address, len(tt.results)) 384 for j, signer := range tt.results { 385 signers[j] = accounts.address(signer) 386 } 387 for j := 0; j < len(signers); j++ { 388 for k := j + 1; k < len(signers); k++ { 389 if bytes.Compare(signers[j][:], signers[k][:]) > 0 { 390 signers[j], signers[k] = signers[k], signers[j] 391 } 392 } 393 } 394 result := snap.signers() 395 if len(result) != len(signers) { 396 t.Errorf("test %d: signers mismatch: have %x, want %x", i, result, signers) 397 continue 398 } 399 for j := 0; j < len(result); j++ { 400 if !bytes.Equal(result[j][:], signers[j][:]) { 401 t.Errorf("test %d, signer %d: signer mismatch: have %x, want %x", i, j, result[j], signers[j]) 402 } 403 } 404 } 405 }