github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/backend/engine_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 "time" 26 27 "github.com/electroneum/electroneum-sc/common" 28 "github.com/electroneum/electroneum-sc/consensus" 29 "github.com/electroneum/electroneum-sc/consensus/istanbul" 30 istanbulcommon "github.com/electroneum/electroneum-sc/consensus/istanbul/common" 31 "github.com/electroneum/electroneum-sc/consensus/istanbul/testutils" 32 "github.com/electroneum/electroneum-sc/core" 33 "github.com/electroneum/electroneum-sc/core/rawdb" 34 "github.com/electroneum/electroneum-sc/core/types" 35 "github.com/electroneum/electroneum-sc/core/vm" 36 "github.com/electroneum/electroneum-sc/crypto" 37 ) 38 39 func newBlockchainFromConfig(genesis *core.Genesis, nodeKeys []*ecdsa.PrivateKey, cfg istanbul.Config) (*core.BlockChain, *Backend) { 40 memDB := rawdb.NewMemoryDatabase() 41 42 // Use the first key as private key 43 backend := New(cfg, nodeKeys[0], memDB) 44 45 genesis.MustCommit(memDB) 46 47 blockchain, err := core.NewBlockChain(memDB, nil, genesis.Config, backend, vm.Config{}, nil, nil) 48 if err != nil { 49 panic(err) 50 } 51 52 backend.Start(blockchain, blockchain.CurrentBlock, rawdb.HasBadBlock) 53 54 snap, err := backend.snapshot(blockchain, 0, common.Hash{}, nil) 55 if err != nil { 56 panic(err) 57 } 58 if snap == nil { 59 panic("failed to get snapshot") 60 } 61 proposerAddr := snap.ValSet.GetProposer().Address() 62 63 // find proposer key 64 for _, key := range nodeKeys { 65 addr := crypto.PubkeyToAddress(key.PublicKey) 66 if addr.String() == proposerAddr.String() { 67 backend.privateKey = key 68 backend.address = addr 69 } 70 } 71 72 return blockchain, backend 73 } 74 75 // in this test, we can set n to 1, and it means we can process Istanbul and commit a 76 // block by one node. Otherwise, if n is larger than 1, we have to generate 77 // other fake events to process Istanbul. 78 func newBlockChain(n int) (*core.BlockChain, *Backend) { 79 genesis, nodeKeys := testutils.GenesisAndKeys(n) 80 config := copyConfig(istanbul.DefaultConfig) 81 82 return newBlockchainFromConfig(genesis, nodeKeys, config) 83 } 84 85 // copyConfig create a copy of istanbul.Config, so that changing it does not update the original 86 func copyConfig(config *istanbul.Config) istanbul.Config { 87 cpy := *config 88 return cpy 89 } 90 91 func makeHeader(parent *types.Block, config *istanbul.Config) *types.Header { 92 header := &types.Header{ 93 ParentHash: parent.Hash(), 94 Number: parent.Number().Add(parent.Number(), common.Big1), 95 GasLimit: core.CalcGasLimit(parent.GasLimit(), parent.GasLimit()), 96 GasUsed: 0, 97 Time: parent.Time() + config.BlockPeriod, 98 Difficulty: istanbulcommon.DefaultDifficulty, 99 } 100 return header 101 } 102 103 func makeBlock(chain *core.BlockChain, engine *Backend, parent *types.Block) *types.Block { 104 block := makeBlockWithoutSeal(chain, engine, parent, true) 105 stopCh := make(chan struct{}) 106 resultCh := make(chan *types.Block, 10) 107 go engine.Seal(chain, block, resultCh, stopCh) 108 blk := <-resultCh 109 return blk 110 } 111 112 func makeBlockWithoutSeal(chain *core.BlockChain, engine *Backend, parent *types.Block, includeCoinbase bool) *types.Block { 113 header := makeHeader(parent, engine.config) 114 if includeCoinbase { 115 header.Coinbase = engine.Address() 116 } else { 117 header.Coinbase = common.Address{} 118 } 119 120 engine.Prepare(chain, header) 121 state, _ := chain.StateAt(parent.Root()) 122 block, _ := engine.FinalizeAndAssemble(chain, header, state, nil, nil, nil) 123 return block 124 } 125 126 func TestIBFTPrepare(t *testing.T) { 127 chain, engine := newBlockChain(1) 128 defer engine.Stop() 129 header := makeHeader(chain.Genesis(), engine.config) 130 err := engine.Prepare(chain, header) 131 if err != nil { 132 t.Errorf("error mismatch: have %v, want nil", err) 133 } 134 header.ParentHash = common.StringToHash("1234567890") 135 err = engine.Prepare(chain, header) 136 if err != consensus.ErrUnknownAncestor { 137 t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor) 138 } 139 } 140 141 func TestQBFTPrepare(t *testing.T) { 142 chain, engine := newBlockChain(1) 143 defer engine.Stop() 144 header := makeHeader(chain.Genesis(), engine.config) 145 err := engine.Prepare(chain, header) 146 if err != nil { 147 t.Errorf("error mismatch: have %v, want nil", err) 148 } 149 150 header.ParentHash = common.StringToHash("1234567890") 151 err = engine.Prepare(chain, header) 152 if err != consensus.ErrUnknownAncestor { 153 t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrUnknownAncestor) 154 } 155 } 156 157 func TestSealStopChannel(t *testing.T) { 158 chain, engine := newBlockChain(1) 159 defer engine.Stop() 160 block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 161 stop := make(chan struct{}, 1) 162 eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) 163 eventLoop := func() { 164 ev := <-eventSub.Chan() 165 _, ok := ev.Data.(istanbul.RequestEvent) 166 if !ok { 167 t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) 168 } 169 stop <- struct{}{} 170 eventSub.Unsubscribe() 171 } 172 resultCh := make(chan *types.Block, 10) 173 go func() { 174 err := engine.Seal(chain, block, resultCh, stop) 175 if err != nil { 176 t.Errorf("error mismatch: have %v, want nil", err) 177 } 178 }() 179 go eventLoop() 180 181 finalBlock := <-resultCh 182 if finalBlock != nil { 183 t.Errorf("block mismatch: have %v, want nil", finalBlock) 184 } 185 } 186 187 func TestSealCommittedOtherHash(t *testing.T) { 188 chain, engine := newBlockChain(1) 189 defer engine.Stop() 190 block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), false) 191 otherBlock := makeBlockWithoutSeal(chain, engine, block, false) 192 expectedCommittedSeal := append([]byte{1, 2, 3}, bytes.Repeat([]byte{0x00}, types.IstanbulExtraSeal-3)...) 193 194 eventSub := engine.EventMux().Subscribe(istanbul.RequestEvent{}) 195 blockOutputChannel := make(chan *types.Block) 196 stopChannel := make(chan struct{}) 197 198 go func() { 199 ev := <-eventSub.Chan() 200 if _, ok := ev.Data.(istanbul.RequestEvent); !ok { 201 t.Errorf("unexpected event comes: %v", reflect.TypeOf(ev.Data)) 202 } 203 if err := engine.Commit(otherBlock, [][]byte{expectedCommittedSeal}, big.NewInt(0)); err != nil { 204 t.Error(err.Error()) 205 } 206 eventSub.Unsubscribe() 207 }() 208 209 go func() { 210 if err := engine.Seal(chain, block, blockOutputChannel, stopChannel); err != nil { 211 t.Error(err.Error()) 212 } 213 }() 214 215 select { 216 case <-blockOutputChannel: 217 t.Error("Wrong block found!") 218 default: 219 //no block found, stop the sealing 220 close(stopChannel) 221 } 222 223 output := <-blockOutputChannel 224 if output != nil { 225 t.Error("Block not nil!") 226 } 227 } 228 229 func updateQBFTBlock(block *types.Block, addr common.Address) *types.Block { 230 header := block.Header() 231 header.Coinbase = addr 232 return block.WithSeal(header) 233 } 234 235 func TestSealCommitted(t *testing.T) { 236 chain, engine := newBlockChain(1) 237 defer engine.Stop() 238 block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 239 expectedBlock := updateQBFTBlock(block, engine.Address()) 240 241 resultCh := make(chan *types.Block, 10) 242 go func() { 243 err := engine.Seal(chain, block, resultCh, make(chan struct{})) 244 245 if err != nil { 246 t.Errorf("error mismatch: have %v, want %v", err, expectedBlock) 247 } 248 }() 249 250 finalBlock := <-resultCh 251 if finalBlock.Hash() != expectedBlock.Hash() { 252 t.Errorf("hash mismatch: have %v, want %v", finalBlock.Hash(), expectedBlock.Hash()) 253 } 254 } 255 256 func TestVerifyHeader(t *testing.T) { 257 chain, engine := newBlockChain(1) 258 defer engine.Stop() 259 260 // istanbulcommon.ErrEmptyCommittedSeals case 261 block := makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 262 block = updateQBFTBlock(block, engine.Address()) 263 err := engine.VerifyHeader(chain, block.Header(), false) 264 if err != istanbulcommon.ErrEmptyCommittedSeals { 265 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrEmptyCommittedSeals) 266 } 267 268 // short extra data 269 header := block.Header() 270 header.Extra = []byte{} 271 err = engine.VerifyHeader(chain, header, false) 272 if err != istanbulcommon.ErrInvalidExtraDataFormat { 273 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidExtraDataFormat) 274 } 275 // incorrect extra format 276 header.Extra = []byte("0000000000000000000000000000000012300000000000000000000000000000000000000000000000000000000000000000") 277 err = engine.VerifyHeader(chain, header, false) 278 if err != istanbulcommon.ErrInvalidExtraDataFormat { 279 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidExtraDataFormat) 280 } 281 282 // non zero MixDigest 283 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 284 header = block.Header() 285 header.MixDigest = common.StringToHash("123456789") 286 err = engine.VerifyHeader(chain, header, false) 287 if err != istanbulcommon.ErrInvalidMixDigest { 288 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidMixDigest) 289 } 290 291 // invalid uncles hash 292 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 293 header = block.Header() 294 header.UncleHash = common.StringToHash("123456789") 295 err = engine.VerifyHeader(chain, header, false) 296 if err != istanbulcommon.ErrInvalidUncleHash { 297 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidUncleHash) 298 } 299 300 // invalid difficulty 301 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 302 header = block.Header() 303 header.Difficulty = big.NewInt(2) 304 err = engine.VerifyHeader(chain, header, false) 305 if err != istanbulcommon.ErrInvalidDifficulty { 306 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidDifficulty) 307 } 308 309 // invalid timestamp 310 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 311 header = block.Header() 312 header.Time = chain.Genesis().Time() + (engine.config.BlockPeriod - 1) 313 err = engine.VerifyHeader(chain, header, false) 314 if err != istanbulcommon.ErrInvalidTimestamp { 315 t.Errorf("error mismatch: have %v, want %v", err, istanbulcommon.ErrInvalidTimestamp) 316 } 317 318 // future block 319 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 320 header = block.Header() 321 header.Time = uint64(time.Now().Unix() + 10) 322 err = engine.VerifyHeader(chain, header, false) 323 if err != consensus.ErrFutureBlock { 324 t.Errorf("error mismatch: have %v, want %v", err, consensus.ErrFutureBlock) 325 } 326 327 // future block which is within AllowedFutureBlockTime 328 block = makeBlockWithoutSeal(chain, engine, chain.Genesis(), true) 329 header = block.Header() 330 header.Time = new(big.Int).Add(big.NewInt(time.Now().Unix()), new(big.Int).SetUint64(10)).Uint64() 331 priorValue := engine.config.AllowedFutureBlockTime 332 engine.config.AllowedFutureBlockTime = 10 333 err = engine.VerifyHeader(chain, header, false) 334 engine.config.AllowedFutureBlockTime = priorValue //restore changed value 335 if err == consensus.ErrFutureBlock { 336 t.Errorf("error mismatch: have %v, want nil", err) 337 } 338 339 // TODO This test does not make sense anymore as validate vote type is not stored in nonce 340 // invalid nonce 341 /*block = makeBlockWithoutSeal(chain, engine, chain.Genesis()) 342 header = block.Header() 343 copy(header.Nonce[:], hexutil.MustDecode("0x111111111111")) 344 header.Number = big.NewInt(int64(engine.config.Epoch)) 345 err = engine.VerifyHeader(chain, header, false) 346 if err != errInvalidNonce { 347 t.Errorf("error mismatch: have %v, want %v", err, errInvalidNonce) 348 }*/ 349 } 350 351 func TestVerifyHeaders(t *testing.T) { 352 chain, engine := newBlockChain(1) 353 defer engine.Stop() 354 genesis := chain.Genesis() 355 356 // success case 357 headers := []*types.Header{} 358 blocks := []*types.Block{} 359 size := 100 360 361 for i := 0; i < size; i++ { 362 var b *types.Block 363 if i == 0 { 364 b = makeBlockWithoutSeal(chain, engine, genesis, false) 365 b = updateQBFTBlock(b, engine.Address()) 366 } else { 367 b = makeBlockWithoutSeal(chain, engine, blocks[i-1], false) 368 b = updateQBFTBlock(b, engine.Address()) 369 } 370 blocks = append(blocks, b) 371 headers = append(headers, blocks[i].Header()) 372 } 373 // now = func() time.Time { 374 // return time.Unix(int64(headers[size-1].Time), 0) 375 // } 376 _, results := engine.VerifyHeaders(chain, headers, nil) 377 const timeoutDura = 2 * time.Second 378 timeout := time.NewTimer(timeoutDura) 379 index := 0 380 OUT1: 381 for { 382 select { 383 case err := <-results: 384 if err != nil { 385 if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 386 t.Errorf("error mismatch: have %v, want istanbulcommon.ErrEmptyCommittedSeals|istanbulcommon.ErrInvalidCommittedSeals|ErrUnknownAncestor", err) 387 break OUT1 388 } 389 } 390 index++ 391 if index == size { 392 break OUT1 393 } 394 case <-timeout.C: 395 break OUT1 396 } 397 } 398 _, results = engine.VerifyHeaders(chain, headers, nil) 399 timeout = time.NewTimer(timeoutDura) 400 OUT2: 401 for { 402 select { 403 case err := <-results: 404 if err != nil { 405 if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 406 t.Errorf("error mismatch: have %v, want istanbulcommon.ErrEmptyCommittedSeals|istanbulcommon.ErrInvalidCommittedSeals|ErrUnknownAncestor", err) 407 break OUT2 408 } 409 } 410 case <-timeout.C: 411 break OUT2 412 } 413 } 414 // error header cases 415 headers[2].Number = big.NewInt(100) 416 _, results = engine.VerifyHeaders(chain, headers, nil) 417 timeout = time.NewTimer(timeoutDura) 418 index = 0 419 errors := 0 420 expectedErrors := 0 421 OUT3: 422 for { 423 select { 424 case err := <-results: 425 if err != nil { 426 if err != istanbulcommon.ErrEmptyCommittedSeals && err != istanbulcommon.ErrInvalidCommittedSeals && err != consensus.ErrUnknownAncestor { 427 errors++ 428 } 429 } 430 index++ 431 if index == size { 432 if errors != expectedErrors { 433 t.Errorf("error mismatch: have %v, want %v", errors, expectedErrors) 434 } 435 break OUT3 436 } 437 case <-timeout.C: 438 break OUT3 439 } 440 } 441 }