github.com/Blockdaemon/celo-blockchain@v0.0.0-20200129231733-e667f6b08419/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 "sort" 25 "testing" 26 27 bls "github.com/celo-org/bls-zexe/go" 28 ethAccounts "github.com/ethereum/go-ethereum/accounts" 29 "github.com/ethereum/go-ethereum/common" 30 "github.com/ethereum/go-ethereum/consensus/istanbul" 31 "github.com/ethereum/go-ethereum/consensus/istanbul/validator" 32 "github.com/ethereum/go-ethereum/core" 33 "github.com/ethereum/go-ethereum/core/types" 34 "github.com/ethereum/go-ethereum/crypto" 35 blscrypto "github.com/ethereum/go-ethereum/crypto/bls" 36 "github.com/ethereum/go-ethereum/ethdb" 37 "github.com/ethereum/go-ethereum/params" 38 "github.com/ethereum/go-ethereum/rlp" 39 ) 40 41 type testerValSetDiff struct { 42 proposer string 43 addedValidators []string 44 removedValidators []string 45 } 46 47 // testerAccountPool is a pool to maintain currently active tester accounts, 48 // mapped from textual names used in the tests below to actual Ethereum private 49 // keys capable of signing transactions. 50 type testerAccountPool struct { 51 accounts map[string]*ecdsa.PrivateKey 52 } 53 54 func newTesterAccountPool() *testerAccountPool { 55 return &testerAccountPool{ 56 accounts: make(map[string]*ecdsa.PrivateKey), 57 } 58 } 59 60 func (ap *testerAccountPool) sign(header *types.Header, validator string) { 61 // Ensure we have a persistent key for the validator 62 if ap.accounts[validator] == nil { 63 ap.accounts[validator], _ = crypto.GenerateKey() 64 } 65 // Sign the header and embed the signature in extra data 66 hashData := crypto.Keccak256([]byte(sigHash(header).Bytes())) 67 sig, _ := crypto.Sign(hashData, ap.accounts[validator]) 68 69 writeSeal(header, sig) 70 } 71 72 func (ap *testerAccountPool) address(account string) common.Address { 73 // Ensure we have a persistent key for the account 74 if account == "" { 75 return common.Address{} 76 } 77 if ap.accounts[account] == nil { 78 ap.accounts[account], _ = crypto.GenerateKey() 79 } 80 // Resolve and return the Ethereum address 81 return crypto.PubkeyToAddress(ap.accounts[account].PublicKey) 82 } 83 84 func indexOf(element string, data []string) int { 85 for k, v := range data { 86 if element == v { 87 return k 88 } 89 } 90 return -1 //not found. 91 } 92 93 func convertValNamesToRemovedValidators(accounts *testerAccountPool, oldVals []istanbul.ValidatorData, valNames []string) *big.Int { 94 bitmap := big.NewInt(0) 95 for _, v := range valNames { 96 for j := range oldVals { 97 if accounts.address(v) == oldVals[j].Address { 98 bitmap = bitmap.SetBit(bitmap, j, 1) 99 } 100 } 101 } 102 103 return bitmap 104 } 105 106 func convertValNames(accounts *testerAccountPool, valNames []string) []common.Address { 107 returnArray := make([]common.Address, len(valNames)) 108 109 for i, valName := range valNames { 110 returnArray[i] = accounts.address(valName) 111 } 112 113 return returnArray 114 } 115 116 func convertValNamesToValidatorsData(accounts *testerAccountPool, valNames []string) []istanbul.ValidatorData { 117 returnArray := make([]istanbul.ValidatorData, len(valNames)) 118 119 for i, valName := range valNames { 120 returnArray[i] = istanbul.ValidatorData{ 121 accounts.address(valName), 122 blscrypto.SerializedPublicKey{}, 123 } 124 } 125 126 return returnArray 127 } 128 129 // Define a mock blockchain 130 type mockBlockchain struct { 131 headers map[uint64]*types.Header 132 } 133 134 func (bc *mockBlockchain) AddHeader(number uint64, header *types.Header) { 135 bc.headers[number] = header 136 } 137 138 func (bc *mockBlockchain) GetHeaderByNumber(number uint64) *types.Header { 139 return bc.headers[number] 140 } 141 142 func (bc *mockBlockchain) Config() *params.ChainConfig { 143 return ¶ms.ChainConfig{FullHeaderChainAvailable: true} 144 } 145 146 func (bc *mockBlockchain) CurrentHeader() *types.Header { 147 return nil 148 } 149 150 func (bc *mockBlockchain) GetHeader(hash common.Hash, number uint64) *types.Header { 151 return nil 152 } 153 154 func (bc *mockBlockchain) GetHeaderByHash(hash common.Hash) *types.Header { 155 return nil 156 } 157 158 func (bc *mockBlockchain) GetBlock(hash common.Hash, number uint64) *types.Block { 159 return nil 160 } 161 162 // Tests that validator set changes are evaluated correctly for various simple and complex scenarios. 163 func TestValSetChange(t *testing.T) { 164 // Define the various voting scenarios to test 165 tests := []struct { 166 epoch uint64 167 validators []string 168 valsetdiffs []testerValSetDiff 169 results []string 170 err error 171 }{ 172 { 173 // Single validator, empty val set diff 174 epoch: 1, 175 validators: []string{"A"}, 176 valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{}, removedValidators: []string{}}}, 177 results: []string{"A"}, 178 err: nil, 179 }, { 180 // Single validator, add two new validators 181 epoch: 1, 182 validators: []string{"A"}, 183 valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"B", "C"}, removedValidators: []string{}}}, 184 results: []string{"A", "B", "C"}, 185 err: nil, 186 }, { 187 // Two validator, remove two validators 188 epoch: 1, 189 validators: []string{"A", "B"}, 190 valsetdiffs: []testerValSetDiff{{proposer: "B", addedValidators: []string{}, removedValidators: []string{"A", "B"}}}, 191 results: []string{}, 192 err: nil, 193 }, { 194 // Three validator, add two validators and remove two validators 195 epoch: 1, 196 validators: []string{"A", "B", "C"}, 197 valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}}, 198 results: []string{"A", "D", "E"}, 199 err: nil, 200 }, { 201 // Three validator, add two validators and remove two validators with an unauthorized proposer 202 epoch: 1, 203 validators: []string{"A", "B", "C"}, 204 valsetdiffs: []testerValSetDiff{{proposer: "D", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}}, 205 results: []string{"A", "D", "E"}, 206 err: errUnauthorized, 207 }, 208 { 209 // Three validator, add two validators and remove two validators. Second header will add 1 validators and remove 2 validators. 210 epoch: 1, 211 validators: []string{"A", "B", "C"}, 212 valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}, 213 {proposer: "E", addedValidators: []string{"F"}, removedValidators: []string{"A", "D"}}}, 214 results: []string{"F", "E"}, 215 err: nil, 216 }, { 217 // Three validator, add two validators and remove two validators. Second header will add 1 validators and remove 2 validators. The first header will 218 // be ignored, since it's no the last header of an epoch 219 epoch: 2, 220 validators: []string{"A", "B", "C"}, 221 valsetdiffs: []testerValSetDiff{{proposer: "A", addedValidators: []string{"D", "E"}, removedValidators: []string{"B", "C"}}, 222 {proposer: "A", addedValidators: []string{"F"}, removedValidators: []string{"A", "B"}}}, 223 results: []string{"F", "C"}, 224 err: nil, 225 }, 226 } 227 // Run through the scenarios and test them 228 for i, tt := range tests { 229 // Create the account pool and generate the initial set of validators 230 accounts := newTesterAccountPool() 231 232 validators := make([]istanbul.ValidatorData, len(tt.validators)) 233 for j, validator := range tt.validators { 234 validators[j] = istanbul.ValidatorData{ 235 accounts.address(validator), 236 blscrypto.SerializedPublicKey{}, 237 } 238 } 239 240 // Create the genesis block with the initial set of validators 241 genesis := &core.Genesis{ 242 Difficulty: defaultDifficulty, 243 Mixhash: types.IstanbulDigest, 244 Config: params.TestChainConfig, 245 } 246 extra, _ := rlp.EncodeToBytes(&types.IstanbulExtra{}) 247 genesis.ExtraData = append(make([]byte, types.IstanbulExtraVanity), extra...) 248 b := genesis.ToBlock(nil) 249 h := b.Header() 250 err := writeValidatorSetDiff(h, []istanbul.ValidatorData{}, validators) 251 if err != nil { 252 t.Errorf("Could not update genesis validator set, got err: %v", err) 253 } 254 genesis.ExtraData = h.Extra 255 db := ethdb.NewMemDatabase() 256 257 config := istanbul.DefaultConfig 258 if tt.epoch != 0 { 259 config.Epoch = tt.epoch 260 } 261 262 chain := &mockBlockchain{ 263 headers: make(map[uint64]*types.Header), 264 } 265 266 engine := New(config, db).(*Backend) 267 268 privateKey := accounts.accounts[tt.validators[0]] 269 address := crypto.PubkeyToAddress(privateKey.PublicKey) 270 signerFn := func(_ ethAccounts.Account, data []byte) ([]byte, error) { 271 return crypto.Sign(data, privateKey) 272 } 273 274 signerBLSHashFn := func(_ ethAccounts.Account, data []byte) (blscrypto.SerializedSignature, error) { 275 key := privateKey 276 privateKeyBytes, err := blscrypto.ECDSAToBLS(key) 277 if err != nil { 278 return blscrypto.SerializedSignature{}, err 279 } 280 281 privateKey, err := bls.DeserializePrivateKey(privateKeyBytes) 282 if err != nil { 283 return blscrypto.SerializedSignature{}, err 284 } 285 defer privateKey.Destroy() 286 287 signature, err := privateKey.SignMessage(data, []byte{}, false) 288 if err != nil { 289 return blscrypto.SerializedSignature{}, err 290 } 291 defer signature.Destroy() 292 signatureBytes, err := signature.Serialize() 293 if err != nil { 294 return blscrypto.SerializedSignature{}, err 295 } 296 297 return blscrypto.SerializedSignatureFromBytes(signatureBytes) 298 } 299 300 signerBLSMessageFn := func(_ ethAccounts.Account, data []byte, extraData []byte) (blscrypto.SerializedSignature, error) { 301 key := privateKey 302 privateKeyBytes, err := blscrypto.ECDSAToBLS(key) 303 if err != nil { 304 return blscrypto.SerializedSignature{}, err 305 } 306 307 privateKey, err := bls.DeserializePrivateKey(privateKeyBytes) 308 if err != nil { 309 return blscrypto.SerializedSignature{}, err 310 } 311 defer privateKey.Destroy() 312 313 signature, err := privateKey.SignMessage(data, extraData, true) 314 if err != nil { 315 return blscrypto.SerializedSignature{}, err 316 } 317 defer signature.Destroy() 318 signatureBytes, err := signature.Serialize() 319 if err != nil { 320 return blscrypto.SerializedSignature{}, err 321 } 322 323 return blscrypto.SerializedSignatureFromBytes(signatureBytes) 324 } 325 326 engine.Authorize(address, signerFn, signerBLSHashFn, signerBLSMessageFn) 327 328 chain.AddHeader(0, genesis.ToBlock(nil).Header()) 329 330 // Assemble a chain of headers from header validator set diffs 331 var prevHeader *types.Header 332 var currentVals []istanbul.ValidatorData 333 var snap *Snapshot 334 for j, valsetdiff := range tt.valsetdiffs { 335 header := &types.Header{ 336 Number: big.NewInt(int64(j) + 1), 337 Time: big.NewInt(int64(j) * int64(config.BlockPeriod)), 338 Difficulty: defaultDifficulty, 339 MixDigest: types.IstanbulDigest, 340 } 341 342 var buf bytes.Buffer 343 344 buf.Write(bytes.Repeat([]byte{0x00}, types.IstanbulExtraVanity)) 345 346 var oldVals []istanbul.ValidatorData 347 if currentVals == nil { 348 oldVals = convertValNamesToValidatorsData(accounts, tests[i].validators) 349 } else { 350 oldVals = currentVals 351 } 352 353 ist := &types.IstanbulExtra{ 354 AddedValidators: convertValNames(accounts, valsetdiff.addedValidators), 355 AddedValidatorsPublicKeys: make([]blscrypto.SerializedPublicKey, len(valsetdiff.addedValidators)), 356 RemovedValidators: convertValNamesToRemovedValidators(accounts, oldVals, valsetdiff.removedValidators), 357 AggregatedSeal: types.IstanbulAggregatedSeal{}, 358 ParentAggregatedSeal: types.IstanbulAggregatedSeal{}, 359 } 360 361 payload, err := rlp.EncodeToBytes(&ist) 362 if err != nil { 363 t.Errorf("test %d, valsetdiff %d: error in encoding extra header info", i, j) 364 } 365 header.Extra = append(buf.Bytes(), payload...) 366 367 if j > 0 { 368 header.ParentHash = prevHeader.Hash() 369 } 370 371 accounts.sign(header, valsetdiff.proposer) 372 373 chain.AddHeader(uint64(j+1), header) 374 375 prevHeader = header 376 snap, err = engine.snapshot(chain, prevHeader.Number.Uint64(), prevHeader.Hash(), nil) 377 if err != tt.err { 378 t.Errorf("test %d: error mismatch: have %v, want %v", i, err, tt.err) 379 } 380 381 if err != nil { 382 continue 383 } 384 385 currentVals = snap.validators() 386 } 387 if tt.err != nil { 388 continue 389 } 390 391 // Verify the final list of validators against the expected ones 392 validators = make([]istanbul.ValidatorData, len(tt.results)) 393 for j, validator := range tt.results { 394 validators[j] = istanbul.ValidatorData{ 395 accounts.address(validator), 396 blscrypto.SerializedPublicKey{}, 397 } 398 } 399 result := snap.validators() 400 if len(result) != len(validators) { 401 t.Errorf("test %d: validators mismatch: have %x, want %x", i, result, validators) 402 continue 403 } 404 405 sort.Sort(istanbul.ValidatorsDataByAddress(result)) 406 sort.Sort(istanbul.ValidatorsDataByAddress(validators)) 407 for j := 0; j < len(result); j++ { 408 if !bytes.Equal(result[j].Address[:], validators[j].Address[:]) { 409 t.Errorf("test %d, validator %d: validator mismatch: have %x, want %x", i, j, result[j], validators[j]) 410 } 411 } 412 } 413 } 414 415 func TestSaveAndLoad(t *testing.T) { 416 snap := &Snapshot{ 417 Epoch: 5, 418 Number: 10, 419 Hash: common.HexToHash("1234567890"), 420 ValSet: validator.NewSet([]istanbul.ValidatorData{ 421 { 422 common.BytesToAddress([]byte("1234567894")), 423 blscrypto.SerializedPublicKey{}, 424 }, 425 { 426 common.BytesToAddress([]byte("1234567895")), 427 blscrypto.SerializedPublicKey{}, 428 }, 429 }), 430 } 431 db := ethdb.NewMemDatabase() 432 err := snap.store(db) 433 if err != nil { 434 t.Errorf("store snapshot failed: %v", err) 435 } 436 437 snap1, err := loadSnapshot(snap.Epoch, db, snap.Hash) 438 if err != nil { 439 t.Errorf("load snapshot failed: %v", err) 440 } 441 if snap.Epoch != snap1.Epoch { 442 t.Errorf("epoch mismatch: have %v, want %v", snap1.Epoch, snap.Epoch) 443 } 444 if snap.Hash != snap1.Hash { 445 t.Errorf("hash mismatch: have %v, want %v", snap1.Number, snap.Number) 446 } 447 if !reflect.DeepEqual(snap.ValSet, snap.ValSet) { 448 t.Errorf("validator set mismatch: have %v, want %v", snap1.ValSet, snap.ValSet) 449 } 450 }