github.com/iotexproject/iotex-core@v1.14.1-rc1/consensus/scheme/rolldpos/rolldpos_test.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package rolldpos 7 8 import ( 9 "encoding/hex" 10 "fmt" 11 "net" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/facebookgo/clock" 17 "github.com/golang/mock/gomock" 18 "github.com/iotexproject/go-pkgs/crypto" 19 "github.com/iotexproject/go-pkgs/hash" 20 "github.com/iotexproject/iotex-proto/golang/iotextypes" 21 "github.com/pkg/errors" 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 "golang.org/x/net/context" 25 "google.golang.org/protobuf/proto" 26 "google.golang.org/protobuf/types/known/timestamppb" 27 28 "github.com/iotexproject/iotex-core/action/protocol" 29 "github.com/iotexproject/iotex-core/action/protocol/account" 30 accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" 31 "github.com/iotexproject/iotex-core/action/protocol/rewarding" 32 "github.com/iotexproject/iotex-core/action/protocol/rolldpos" 33 "github.com/iotexproject/iotex-core/actpool" 34 "github.com/iotexproject/iotex-core/blockchain" 35 "github.com/iotexproject/iotex-core/blockchain/block" 36 "github.com/iotexproject/iotex-core/blockchain/blockdao" 37 "github.com/iotexproject/iotex-core/blockchain/filedao" 38 "github.com/iotexproject/iotex-core/blockchain/genesis" 39 "github.com/iotexproject/iotex-core/consensus/consensusfsm" 40 cp "github.com/iotexproject/iotex-core/crypto" 41 "github.com/iotexproject/iotex-core/db" 42 "github.com/iotexproject/iotex-core/endorsement" 43 "github.com/iotexproject/iotex-core/p2p/node" 44 "github.com/iotexproject/iotex-core/state/factory" 45 "github.com/iotexproject/iotex-core/test/identityset" 46 "github.com/iotexproject/iotex-core/test/mock/mock_blockchain" 47 "github.com/iotexproject/iotex-core/testutil" 48 ) 49 50 type addrKeyPair struct { 51 priKey crypto.PrivateKey 52 encodedAddr string 53 } 54 55 func TestNewRollDPoS(t *testing.T) { 56 t.Parallel() 57 58 ctrl := gomock.NewController(t) 59 60 g := genesis.Default 61 builderCfg := BuilderConfig{ 62 Chain: blockchain.DefaultConfig, 63 Consensus: DefaultConfig, 64 DardanellesUpgrade: consensusfsm.DefaultDardanellesUpgradeConfig, 65 DB: db.DefaultConfig, 66 Genesis: g, 67 SystemActive: true, 68 } 69 rp := rolldpos.NewProtocol( 70 g.NumCandidateDelegates, 71 g.NumDelegates, 72 g.NumSubEpochs, 73 ) 74 delegatesByEpoch := func(uint64) ([]string, error) { return nil, nil } 75 t.Run("normal", func(t *testing.T) { 76 sk := identityset.PrivateKey(0) 77 r, err := NewRollDPoSBuilder(). 78 SetConfig(builderCfg). 79 SetAddr(identityset.Address(0).String()). 80 SetPriKey(sk). 81 SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). 82 SetBroadcast(func(_ proto.Message) error { 83 return nil 84 }). 85 SetDelegatesByEpochFunc(delegatesByEpoch). 86 SetProposersByEpochFunc(delegatesByEpoch). 87 RegisterProtocol(rp). 88 Build() 89 assert.NoError(t, err) 90 assert.NotNil(t, r) 91 }) 92 t.Run("mock-clock", func(t *testing.T) { 93 sk := identityset.PrivateKey(0) 94 r, err := NewRollDPoSBuilder(). 95 SetConfig(builderCfg). 96 SetAddr(identityset.Address(0).String()). 97 SetPriKey(sk). 98 SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). 99 SetBroadcast(func(_ proto.Message) error { 100 return nil 101 }). 102 SetClock(clock.NewMock()). 103 SetDelegatesByEpochFunc(delegatesByEpoch). 104 SetProposersByEpochFunc(delegatesByEpoch). 105 RegisterProtocol(rp). 106 Build() 107 assert.NoError(t, err) 108 assert.NotNil(t, r) 109 _, ok := r.ctx.Clock().(*clock.Mock) 110 assert.True(t, ok) 111 }) 112 113 t.Run("root chain API", func(t *testing.T) { 114 sk := identityset.PrivateKey(0) 115 r, err := NewRollDPoSBuilder(). 116 SetConfig(builderCfg). 117 SetAddr(identityset.Address(0).String()). 118 SetPriKey(sk). 119 SetChainManager(NewChainManager(mock_blockchain.NewMockBlockchain(ctrl))). 120 SetBroadcast(func(_ proto.Message) error { 121 return nil 122 }). 123 SetClock(clock.NewMock()). 124 SetDelegatesByEpochFunc(delegatesByEpoch). 125 SetProposersByEpochFunc(delegatesByEpoch). 126 RegisterProtocol(rp). 127 Build() 128 assert.NoError(t, err) 129 assert.NotNil(t, r) 130 }) 131 t.Run("missing-dep", func(t *testing.T) { 132 sk := identityset.PrivateKey(0) 133 r, err := NewRollDPoSBuilder(). 134 SetConfig(builderCfg). 135 SetAddr(identityset.Address(0).String()). 136 SetPriKey(sk). 137 SetBroadcast(func(_ proto.Message) error { 138 return nil 139 }). 140 SetDelegatesByEpochFunc(delegatesByEpoch). 141 SetProposersByEpochFunc(delegatesByEpoch). 142 RegisterProtocol(rp). 143 Build() 144 assert.Error(t, err) 145 assert.Nil(t, r) 146 }) 147 } 148 149 func makeBlock(t *testing.T, accountIndex, numOfEndosements int, makeInvalidEndorse bool, height int) *block.Block { 150 unixTime := 1500000000 151 blkTime := int64(-1) 152 if height != 9 { 153 height = 9 154 blkTime = int64(-7723372030) 155 } 156 timeT := time.Unix(blkTime, 0) 157 rap := block.RunnableActionsBuilder{} 158 ra := rap.Build() 159 blk, err := block.NewBuilder(ra). 160 SetHeight(uint64(height)). 161 SetTimestamp(timeT). 162 SetVersion(1). 163 SetReceiptRoot(hash.Hash256b([]byte("hello, world!"))). 164 SetDeltaStateDigest(hash.Hash256b([]byte("world, hello!"))). 165 SetPrevBlockHash(hash.Hash256b([]byte("hello, block!"))). 166 SignAndBuild(identityset.PrivateKey(accountIndex)) 167 require.NoError(t, err) 168 footerForBlk := &block.Footer{} 169 typesFooter := iotextypes.BlockFooter{} 170 171 for i := 0; i < numOfEndosements; i++ { 172 timeTime := time.Unix(int64(unixTime), 0) 173 hs := blk.HashBlock() 174 var consensusVote *ConsensusVote 175 if makeInvalidEndorse { 176 consensusVote = NewConsensusVote(hs[:], LOCK) 177 } else { 178 consensusVote = NewConsensusVote(hs[:], COMMIT) 179 } 180 en, err := endorsement.Endorse(identityset.PrivateKey(i), consensusVote, timeTime) 181 require.NoError(t, err) 182 enProto, err := en.Proto() 183 require.NoError(t, err) 184 typesFooter.Endorsements = append(typesFooter.Endorsements, enProto) 185 } 186 ts := timestamppb.New(time.Unix(int64(unixTime), 0)) 187 typesFooter.Timestamp = ts 188 require.NotNil(t, typesFooter.Timestamp) 189 err = footerForBlk.ConvertFromBlockFooterPb(&typesFooter) 190 require.NoError(t, err) 191 blk.Footer = *footerForBlk 192 return &blk 193 } 194 195 func TestValidateBlockFooter(t *testing.T) { 196 ctrl := gomock.NewController(t) 197 candidates := make([]string, 5) 198 for i := 0; i < len(candidates); i++ { 199 candidates[i] = identityset.Address(i).String() 200 } 201 clock := clock.NewMock() 202 blockHeight := uint64(8) 203 footer := &block.Footer{} 204 bc := mock_blockchain.NewMockBlockchain(ctrl) 205 bc.EXPECT().BlockFooterByHeight(blockHeight).Return(footer, nil).Times(5) 206 207 sk1 := identityset.PrivateKey(1) 208 g := genesis.Default 209 g.NumDelegates = 4 210 g.NumSubEpochs = 1 211 g.BlockInterval = 10 * time.Second 212 g.Timestamp = int64(1500000000) 213 builderCfg := BuilderConfig{ 214 Chain: blockchain.DefaultConfig, 215 Consensus: DefaultConfig, 216 DardanellesUpgrade: consensusfsm.DefaultDardanellesUpgradeConfig, 217 DB: db.DefaultConfig, 218 Genesis: g, 219 SystemActive: true, 220 } 221 bc.EXPECT().Genesis().Return(g).Times(5) 222 rp := rolldpos.NewProtocol( 223 g.NumCandidateDelegates, 224 g.NumDelegates, 225 g.NumSubEpochs, 226 ) 227 delegatesByEpoch := func(uint64) ([]string, error) { 228 return []string{ 229 candidates[0], 230 candidates[1], 231 candidates[2], 232 candidates[3], 233 }, nil 234 } 235 r, err := NewRollDPoSBuilder(). 236 SetConfig(builderCfg). 237 SetAddr(identityset.Address(1).String()). 238 SetPriKey(sk1). 239 SetChainManager(NewChainManager(bc)). 240 SetBroadcast(func(_ proto.Message) error { 241 return nil 242 }). 243 SetDelegatesByEpochFunc(delegatesByEpoch). 244 SetProposersByEpochFunc(delegatesByEpoch). 245 SetClock(clock). 246 RegisterProtocol(rp). 247 Build() 248 require.NoError(t, err) 249 require.NotNil(t, r) 250 251 // all right 252 blk := makeBlock(t, 1, 4, false, 9) 253 err = r.ValidateBlockFooter(blk) 254 require.NoError(t, err) 255 256 // Proposer is wrong 257 blk = makeBlock(t, 4, 4, false, 9) 258 err = r.ValidateBlockFooter(blk) 259 require.Error(t, err) 260 261 // Not enough endorsements 262 blk = makeBlock(t, 1, 2, false, 9) 263 err = r.ValidateBlockFooter(blk) 264 require.Error(t, err) 265 266 // round information is wrong 267 blk = makeBlock(t, 1, 4, false, 0) 268 err = r.ValidateBlockFooter(blk) 269 require.Error(t, err) 270 271 // Some endorsement is invalid 272 blk = makeBlock(t, 1, 4, true, 9) 273 err = r.ValidateBlockFooter(blk) 274 require.Error(t, err) 275 } 276 277 func TestRollDPoS_Metrics(t *testing.T) { 278 t.Parallel() 279 280 ctrl := gomock.NewController(t) 281 282 candidates := make([]string, 5) 283 for i := 0; i < len(candidates); i++ { 284 candidates[i] = identityset.Address(i).String() 285 } 286 287 clock := clock.NewMock() 288 blockHeight := uint64(8) 289 footer := &block.Footer{} 290 bc := mock_blockchain.NewMockBlockchain(ctrl) 291 bc.EXPECT().TipHeight().Return(blockHeight).Times(1) 292 bc.EXPECT().BlockFooterByHeight(blockHeight).Return(footer, nil).Times(2) 293 294 sk1 := identityset.PrivateKey(1) 295 cfg := DefaultConfig 296 cfg.ConsensusDBPath = "consensus.db" 297 g := genesis.Default 298 g.NumDelegates = 4 299 g.NumSubEpochs = 1 300 g.BlockInterval = 10 * time.Second 301 g.Timestamp = int64(1500000000) 302 builderCfg := BuilderConfig{ 303 Chain: blockchain.DefaultConfig, 304 Consensus: cfg, 305 DardanellesUpgrade: consensusfsm.DefaultDardanellesUpgradeConfig, 306 DB: db.DefaultConfig, 307 Genesis: g, 308 SystemActive: true, 309 } 310 bc.EXPECT().Genesis().Return(g).Times(2) 311 rp := rolldpos.NewProtocol( 312 g.NumCandidateDelegates, 313 g.NumDelegates, 314 g.NumSubEpochs, 315 ) 316 delegatesByEpoch := func(uint64) ([]string, error) { 317 return []string{ 318 candidates[0], 319 candidates[1], 320 candidates[2], 321 candidates[3], 322 }, nil 323 } 324 r, err := NewRollDPoSBuilder(). 325 SetConfig(builderCfg). 326 SetAddr(identityset.Address(1).String()). 327 SetPriKey(sk1). 328 SetChainManager(NewChainManager(bc)). 329 SetBroadcast(func(_ proto.Message) error { 330 return nil 331 }). 332 SetClock(clock). 333 SetDelegatesByEpochFunc(delegatesByEpoch). 334 SetProposersByEpochFunc(delegatesByEpoch). 335 RegisterProtocol(rp). 336 Build() 337 require.NoError(t, err) 338 require.NotNil(t, r) 339 ctx := r.ctx.(*rollDPoSCtx) 340 clock.Add(ctx.BlockInterval(blockHeight)) 341 require.NoError(t, ctx.Start(context.Background())) 342 ctx.round, err = ctx.roundCalc.UpdateRound(ctx.round, blockHeight+1, ctx.BlockInterval(blockHeight+1), clock.Now(), 2*time.Second) 343 require.NoError(t, err) 344 345 m, err := r.Metrics() 346 require.NoError(t, err) 347 assert.Equal(t, uint64(3), m.LatestEpoch) 348 assert.Equal(t, candidates[:4], m.LatestDelegates) 349 assert.Equal(t, candidates[1], m.LatestBlockProducer) 350 } 351 352 // E2E RollDPoS tests bellow 353 354 type directOverlay struct { 355 addr net.Addr 356 peers map[net.Addr]*RollDPoS 357 } 358 359 func (o *directOverlay) Start(_ context.Context) error { return nil } 360 361 func (o *directOverlay) Stop(_ context.Context) error { return nil } 362 363 func (o *directOverlay) Broadcast(msg proto.Message) error { 364 // Only broadcast consensus message 365 if cMsg, ok := msg.(*iotextypes.ConsensusMessage); ok { 366 for _, r := range o.peers { 367 if err := r.HandleConsensusMsg(cMsg); err != nil { 368 return errors.Wrap(err, "error when handling consensus message directly") 369 } 370 } 371 } 372 return nil 373 } 374 375 func (o *directOverlay) Tell(uint32, net.Addr, proto.Message) error { return nil } 376 377 func (o *directOverlay) Self() net.Addr { return o.addr } 378 379 func (o *directOverlay) GetPeers() []net.Addr { 380 addrs := make([]net.Addr, 0, len(o.peers)) 381 for addr := range o.peers { 382 addrs = append(addrs, addr) 383 } 384 return addrs 385 } 386 387 func TestRollDPoSConsensus(t *testing.T) { 388 newConsensusComponents := func(numNodes int) ([]*RollDPoS, []*directOverlay, []blockchain.Blockchain) { 389 cfg := DefaultConfig 390 cfg.ConsensusDBPath = "" 391 cfg.Delay = 300 * time.Millisecond 392 cfg.FSM.AcceptBlockTTL = 800 * time.Millisecond 393 cfg.FSM.AcceptProposalEndorsementTTL = 400 * time.Millisecond 394 cfg.FSM.AcceptLockEndorsementTTL = 400 * time.Millisecond 395 cfg.FSM.CommitTTL = 400 * time.Millisecond 396 cfg.FSM.UnmatchedEventTTL = time.Second 397 cfg.FSM.UnmatchedEventInterval = 10 * time.Millisecond 398 cfg.ToleratedOvertime = 200 * time.Millisecond 399 g := genesis.Default 400 g.BlockInterval = 2 * time.Second 401 g.Blockchain.NumDelegates = uint64(numNodes) 402 g.Blockchain.NumSubEpochs = 1 403 g.EnableGravityChainVoting = false 404 builderCfg := BuilderConfig{ 405 Chain: blockchain.DefaultConfig, 406 Consensus: cfg, 407 DardanellesUpgrade: consensusfsm.DefaultDardanellesUpgradeConfig, 408 DB: db.DefaultConfig, 409 Genesis: g, 410 SystemActive: true, 411 } 412 chainAddrs := make([]*addrKeyPair, 0, numNodes) 413 networkAddrs := make([]net.Addr, 0, numNodes) 414 for i := 0; i < numNodes; i++ { 415 sk := identityset.PrivateKey(i) 416 addr := addrKeyPair{ 417 encodedAddr: identityset.Address(i).String(), 418 priKey: sk, 419 } 420 chainAddrs = append(chainAddrs, &addr) 421 networkAddrs = append(networkAddrs, node.NewTCPNode(fmt.Sprintf("127.0.0.%d:4689", i+1))) 422 } 423 424 chainRawAddrs := make([]string, 0, numNodes) 425 addressMap := make(map[string]*addrKeyPair) 426 for _, addr := range chainAddrs { 427 chainRawAddrs = append(chainRawAddrs, addr.encodedAddr) 428 addressMap[addr.encodedAddr] = addr 429 } 430 cp.SortCandidates(chainRawAddrs, 1, cp.CryptoSeed) 431 for i, rawAddress := range chainRawAddrs { 432 chainAddrs[i] = addressMap[rawAddress] 433 } 434 435 delegatesByEpochFunc := func(_ uint64) ([]string, error) { 436 candidates := make([]string, 0, numNodes) 437 for _, addr := range chainAddrs { 438 candidates = append(candidates, addr.encodedAddr) 439 } 440 return candidates, nil 441 } 442 443 chains := make([]blockchain.Blockchain, 0, numNodes) 444 p2ps := make([]*directOverlay, 0, numNodes) 445 cs := make([]*RollDPoS, 0, numNodes) 446 bc := blockchain.DefaultConfig 447 factoryCfg := factory.GenerateConfig(bc, g) 448 for i := 0; i < numNodes; i++ { 449 ctx := context.Background() 450 bc.ProducerPrivKey = hex.EncodeToString(chainAddrs[i].priKey.Bytes()) 451 registry := protocol.NewRegistry() 452 sf, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry)) 453 require.NoError(t, err) 454 require.NoError(t, sf.Start(genesis.WithGenesisContext( 455 protocol.WithRegistry(ctx, registry), 456 g, 457 ))) 458 actPool, err := actpool.NewActPool(g, sf, actpool.DefaultConfig) 459 require.NoError(t, err) 460 require.NoError(t, err) 461 acc := account.NewProtocol(rewarding.DepositGas) 462 require.NoError(t, acc.Register(registry)) 463 rp := rolldpos.NewProtocol(g.NumCandidateDelegates, g.NumDelegates, g.NumSubEpochs) 464 require.NoError(t, rp.Register(registry)) 465 store, err := filedao.NewFileDAOInMemForTest() 466 require.NoError(t, err) 467 dao := blockdao.NewBlockDAOWithIndexersAndCache(store, []blockdao.BlockIndexer{sf}, db.DefaultConfig.MaxCacheSize) 468 chain := blockchain.NewBlockchain( 469 bc, 470 g, 471 dao, 472 factory.NewMinter(sf, actPool), 473 blockchain.BlockValidatorOption(block.NewValidator( 474 sf, 475 protocol.NewGenericValidator(sf, accountutil.AccountState), 476 )), 477 ) 478 chains = append(chains, chain) 479 480 p2p := &directOverlay{ 481 addr: networkAddrs[i], 482 peers: make(map[net.Addr]*RollDPoS), 483 } 484 p2ps = append(p2ps, p2p) 485 486 consensus, err := NewRollDPoSBuilder(). 487 SetAddr(chainAddrs[i].encodedAddr). 488 SetPriKey(chainAddrs[i].priKey). 489 SetConfig(builderCfg). 490 SetChainManager(NewChainManager(chain)). 491 SetBroadcast(p2p.Broadcast). 492 SetDelegatesByEpochFunc(delegatesByEpochFunc). 493 SetProposersByEpochFunc(delegatesByEpochFunc). 494 RegisterProtocol(rp). 495 Build() 496 require.NoError(t, err) 497 498 cs = append(cs, consensus) 499 } 500 for i := 0; i < numNodes; i++ { 501 for j := 0; j < numNodes; j++ { 502 if i != j { 503 p2ps[i].peers[p2ps[j].addr] = cs[j] 504 } 505 } 506 } 507 return cs, p2ps, chains 508 } 509 510 t.Run("1-block", func(t *testing.T) { 511 // TODO: fix and enable the test 512 t.Skip() 513 514 ctx := context.Background() 515 cs, p2ps, chains := newConsensusComponents(24) 516 517 for i := 0; i < 24; i++ { 518 require.NoError(t, chains[i].Start(ctx)) 519 require.NoError(t, p2ps[i].Start(ctx)) 520 } 521 wg := sync.WaitGroup{} 522 wg.Add(24) 523 for i := 0; i < 24; i++ { 524 go func(idx int) { 525 defer wg.Done() 526 err := cs[idx].Start(ctx) 527 require.NoError(t, err) 528 }(i) 529 } 530 wg.Wait() 531 532 defer func() { 533 for i := 0; i < 24; i++ { 534 require.NoError(t, cs[i].Stop(ctx)) 535 require.NoError(t, p2ps[i].Stop(ctx)) 536 require.NoError(t, chains[i].Stop(ctx)) 537 } 538 }() 539 assert.NoError(t, testutil.WaitUntil(200*time.Millisecond, 10*time.Second, func() (bool, error) { 540 for _, chain := range chains { 541 if chain.TipHeight() < 1 { 542 return false, nil 543 } 544 } 545 return true, nil 546 })) 547 }) 548 549 t.Run("1-epoch", func(t *testing.T) { 550 if testing.Short() { 551 t.Skip("Skip the 1-epoch test in short mode.") 552 } 553 ctx := context.Background() 554 cs, p2ps, chains := newConsensusComponents(24) 555 556 for i := 0; i < 24; i++ { 557 require.NoError(t, chains[i].Start(ctx)) 558 require.NoError(t, p2ps[i].Start(ctx)) 559 } 560 wg := sync.WaitGroup{} 561 wg.Add(24) 562 for i := 0; i < 24; i++ { 563 go func(idx int) { 564 defer wg.Done() 565 err := cs[idx].Start(ctx) 566 require.NoError(t, err) 567 }(i) 568 } 569 wg.Wait() 570 571 defer func() { 572 for i := 0; i < 24; i++ { 573 require.NoError(t, cs[i].Stop(ctx)) 574 require.NoError(t, p2ps[i].Stop(ctx)) 575 require.NoError(t, chains[i].Stop(ctx)) 576 } 577 }() 578 assert.NoError(t, testutil.WaitUntil(200*time.Millisecond, 100*time.Second, func() (bool, error) { 579 for _, chain := range chains { 580 if chain.TipHeight() < 48 { 581 return false, nil 582 } 583 } 584 return true, nil 585 })) 586 }) 587 588 t.Run("network-partition-time-rotation", func(t *testing.T) { 589 // TODO: fix and enable the test 590 t.Skip() 591 592 ctx := context.Background() 593 cs, p2ps, chains := newConsensusComponents(24) 594 // 1 should be the block 1's proposer 595 for i, p2p := range p2ps { 596 if i == 1 { 597 p2p.peers = make(map[net.Addr]*RollDPoS) 598 } else { 599 delete(p2p.peers, p2ps[1].addr) 600 } 601 } 602 603 for i := 0; i < 24; i++ { 604 require.NoError(t, chains[i].Start(ctx)) 605 require.NoError(t, p2ps[i].Start(ctx)) 606 } 607 wg := sync.WaitGroup{} 608 wg.Add(24) 609 for i := 0; i < 24; i++ { 610 go func(idx int) { 611 defer wg.Done() 612 cs[idx].ctx.(*rollDPoSCtx).roundCalc.timeBasedRotation = true 613 err := cs[idx].Start(ctx) 614 require.NoError(t, err) 615 }(i) 616 } 617 wg.Wait() 618 619 defer func() { 620 for i := 0; i < 24; i++ { 621 require.NoError(t, cs[i].Stop(ctx)) 622 require.NoError(t, p2ps[i].Stop(ctx)) 623 require.NoError(t, chains[i].Stop(ctx)) 624 } 625 }() 626 627 assert.NoError(t, testutil.WaitUntil(200*time.Millisecond, 60*time.Second, func() (bool, error) { 628 for i, chain := range chains { 629 if i == 1 { 630 continue 631 } 632 if chain.TipHeight() < 4 { 633 return false, nil 634 } 635 } 636 return true, nil 637 })) 638 }) 639 640 t.Run("proposer-network-partition-blocking", func(t *testing.T) { 641 ctx := context.Background() 642 cs, p2ps, chains := newConsensusComponents(24) 643 // 1 should be the block 1's proposer 644 for i, p2p := range p2ps { 645 if i == 1 { 646 p2p.peers = make(map[net.Addr]*RollDPoS) 647 } else { 648 delete(p2p.peers, p2ps[1].addr) 649 } 650 } 651 652 for i := 0; i < 24; i++ { 653 require.NoError(t, chains[i].Start(ctx)) 654 require.NoError(t, p2ps[i].Start(ctx)) 655 } 656 wg := sync.WaitGroup{} 657 wg.Add(24) 658 for i := 0; i < 24; i++ { 659 go func(idx int) { 660 defer wg.Done() 661 err := cs[idx].Start(ctx) 662 require.NoError(t, err) 663 }(i) 664 } 665 wg.Wait() 666 667 defer func() { 668 for i := 0; i < 24; i++ { 669 require.NoError(t, cs[i].Stop(ctx)) 670 require.NoError(t, p2ps[i].Stop(ctx)) 671 require.NoError(t, chains[i].Stop(ctx)) 672 } 673 }() 674 time.Sleep(5 * time.Second) 675 for _, chain := range chains { 676 header, err := chain.BlockHeaderByHeight(1) 677 assert.Nil(t, header) 678 assert.Error(t, err) 679 } 680 }) 681 682 t.Run("non-proposer-network-partition-blocking", func(t *testing.T) { 683 ctx := context.Background() 684 cs, p2ps, chains := newConsensusComponents(24) 685 // 1 should be the block 1's proposer 686 for i, p2p := range p2ps { 687 if i == 0 { 688 p2p.peers = make(map[net.Addr]*RollDPoS) 689 } else { 690 delete(p2p.peers, p2ps[0].addr) 691 } 692 } 693 694 for i := 0; i < 24; i++ { 695 require.NoError(t, chains[i].Start(ctx)) 696 require.NoError(t, p2ps[i].Start(ctx)) 697 } 698 wg := sync.WaitGroup{} 699 wg.Add(24) 700 for i := 0; i < 24; i++ { 701 go func(idx int) { 702 defer wg.Done() 703 err := cs[idx].Start(ctx) 704 require.NoError(t, err) 705 }(i) 706 } 707 wg.Wait() 708 709 defer func() { 710 for i := 0; i < 24; i++ { 711 require.NoError(t, cs[i].Stop(ctx)) 712 require.NoError(t, p2ps[i].Stop(ctx)) 713 require.NoError(t, chains[i].Stop(ctx)) 714 } 715 }() 716 assert.NoError(t, testutil.WaitUntil(200*time.Millisecond, 60*time.Second, func() (bool, error) { 717 for i, chain := range chains { 718 if i == 0 { 719 continue 720 } 721 if chain.TipHeight() < 2 { 722 return false, nil 723 } 724 } 725 return true, nil 726 })) 727 for i, chain := range chains { 728 header, err := chain.BlockHeaderByHeight(1) 729 if i == 0 { 730 assert.Nil(t, header) 731 assert.Error(t, err) 732 } else { 733 assert.NotNil(t, header) 734 assert.NoError(t, err) 735 } 736 } 737 }) 738 }