diff --git a/epoch_failover_test.go b/epoch_failover_test.go index b4c761d9..2841112e 100644 --- a/epoch_failover_test.go +++ b/epoch_failover_test.go @@ -31,11 +31,7 @@ import ( // we expect the future empty notarization for round 2 to increment the round func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{ - Out: make(chan *testutil.TestBlock, 2), - BlockShouldBeBuilt: make(chan struct{}, 1), - In: make(chan *testutil.TestBlock, 2), - } + bb := testutil.NewTestBlockBuilder().WithBuiltBuffer(2) conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) e, err := NewEpoch(conf) @@ -49,39 +45,29 @@ func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) { // from earlier. notarizeAndFinalizeRound(t, e, bb) - block0, _, err := storage.Retrieve(0) require.NoError(t, err) - block1, ok := bb.BuildBlock(context.Background(), ProtocolMetadata{ + block1 := testutil.NewTestBlock(ProtocolMetadata{ Round: 1, Prev: block0.BlockHeader().Digest, Seq: 1, }, emptyBlacklist) - require.True(t, ok) - block2, ok := bb.BuildBlock(context.Background(), ProtocolMetadata{ + block2 := testutil.NewTestBlock(ProtocolMetadata{ Round: 3, Prev: block1.BlockHeader().Digest, Seq: 2, }, emptyBlacklist) - require.True(t, ok) - // Artificially force the block builder to output the blocks we want. - for len(bb.Out) > 0 { - <-bb.Out - } - for _, block := range []VerifiedBlock{block1, block2} { - bb.Out <- block.(*testutil.TestBlock) - bb.In <- block.(*testutil.TestBlock) - } + notarizeAndFinalizeRound(t, e, bb) emptyNotarization := testutil.NewEmptyNotarization(nodes[:3], 2) e.HandleMessage(&Message{ EmptyNotarization: emptyNotarization, }, nodes[1]) - notarizeAndFinalizeRound(t, e, bb) + notarizeAndFinalizeRoundWithMetadata(t, e, bb, &block1.Metadata) wal.AssertNotarization(2) nextBlockSeqToCommit := uint64(2) @@ -89,7 +75,7 @@ func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) { runCrashAndRestartExecution(t, e, bb, wal, storage, func(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder, storage *testutil.InMemStorage, wal *testutil.TestWAL) { // Ensure our node proposes block with sequence 3 for round 4 - block, _ := notarizeAndFinalizeRound(t, e, bb) + block, _ := notarizeAndFinalizeRoundWithMetadata(t, e, bb, &block2.Metadata) require.Equal(t, nextBlockSeqToCommit, block.BlockHeader().Seq) require.Equal(t, nextRoundToCommit, block.BlockHeader().Round) require.Equal(t, uint64(3), storage.NumBlocks()) @@ -97,7 +83,7 @@ func TestEpochLeaderFailoverWithEmptyNotarization(t *testing.T) { } func TestEpochRebroadcastsEmptyVoteAfterBlockProposalReceived(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} comm := newRebroadcastComm(nodes) @@ -115,16 +101,15 @@ func TestEpochRebroadcastsEmptyVoteAfterBlockProposalReceived(t *testing.T) { // receive the block proposal for round 0 md := e.Metadata() - _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) + block, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block := <-bb.Out vote, err := testutil.NewTestVote(block, nodes[0]) require.NoError(t, err) err = e.HandleMessage(&Message{ BlockMessage: &BlockMessage{ Vote: *vote, - Block: block, + Block: block.(*testutil.TestBlock), }, }, nodes[0]) require.NoError(t, err) @@ -143,7 +128,7 @@ func TestEpochRebroadcastsEmptyVoteAfterBlockProposalReceived(t *testing.T) { } func TestEpochLeaderFailoverReceivesEmptyVotesEarly(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -182,6 +167,8 @@ func TestEpochLeaderFailoverReceivesEmptyVotesEarly(t *testing.T) { testutil.WaitForBlockProposerTimeout(t, e, &e.StartTime, e.Metadata().Round) + block := bb.GetBuiltBlock() + runCrashAndRestartExecution(t, e, bb, wal, storage, func(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder, storage *testutil.InMemStorage, wal *testutil.TestWAL) { walContent, err := wal.ReadAll() require.NoError(t, err) @@ -203,8 +190,6 @@ func TestEpochLeaderFailoverReceivesEmptyVotesEarly(t *testing.T) { require.Equal(t, uint64(3), header.Seq) // Ensure our node proposes block with sequence 3 for round 4 - block := <-bb.Out - for i := 1; i <= quorum; i++ { testutil.InjectTestFinalizeVote(t, e, block, nodes[i]) } @@ -220,7 +205,7 @@ func TestEpochLeaderFailoverReceivesEmptyVotesEarly(t *testing.T) { func TestReceiveEmptyNotarizationWithNoQC(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) @@ -237,7 +222,7 @@ func TestReceiveEmptyNotarizationWithNoQC(t *testing.T) { func TestEpochLeaderFailover(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) e, err := NewEpoch(conf) @@ -302,7 +287,7 @@ func TestEpochLeaderFailover(t *testing.T) { func TestEpochLeaderFailoverDoNotPersistEmptyRoundTwice(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) numRounds := uint64(2) e, err := NewEpoch(conf) @@ -359,7 +344,7 @@ func TestEpochLeaderFailoverDoNotPersistEmptyRoundTwice(t *testing.T) { func TestEpochLeaderRecursivelyFetchNotarizedBlocks(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() recordedMessages := make(chan *Message, 100) @@ -410,7 +395,7 @@ func TestEpochLeaderRecursivelyFetchNotarizedBlocks(t *testing.T) { func TestEpochLeaderFailoverInLeaderRound(t *testing.T) { nodes := []NodeID{{1}, {2}, {3}, {4}} - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() recordedMessages := make(chan *Message, 100) comm := &recordingComm{Communication: testutil.NewNoopComm(nodes), BroadcastMessages: recordedMessages} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[3], comm, bb) @@ -444,7 +429,7 @@ func TestEpochLeaderFailoverInLeaderRound(t *testing.T) { } } - block := <-bb.Out + block := bb.GetBuiltBlock() md := EmptyVoteMetadata{ Round: block.Metadata.Round, } @@ -465,7 +450,7 @@ func TestEpochLeaderFailoverInLeaderRound(t *testing.T) { } func TestEpochNoFinalizationAfterEmptyVote(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -491,21 +476,19 @@ func TestEpochNoFinalizationAfterEmptyVote(t *testing.T) { require.NoError(t, err) leader := LeaderForRound(nodes, 1) - _, ok := bb.BuildBlock(context.Background(), ProtocolMetadata{ + block, ok := bb.BuildBlock(context.Background(), ProtocolMetadata{ Prev: b.BlockHeader().Digest, Round: 1, Seq: 1, }, emptyBlacklist) require.True(t, ok) - block := <-bb.Out - vote, err := testutil.NewTestVote(block, leader) require.NoError(t, err) err = e.HandleMessage(&Message{ BlockMessage: &BlockMessage{ Vote: *vote, - Block: block, + Block: block.(*testutil.TestBlock), }, }, leader) require.NoError(t, err) @@ -535,7 +518,7 @@ func TestEpochNoFinalizationAfterEmptyVote(t *testing.T) { } func TestEpochLeaderFailoverAfterProposal(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -560,11 +543,10 @@ func TestEpochLeaderFailoverAfterProposal(t *testing.T) { leader := LeaderForRound(nodes, 3) md := e.Metadata() _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) + block := bb.GetBuiltBlock() require.True(t, ok) require.Equal(t, md.Round, md.Seq) - block := <-bb.Out - vote, err := testutil.NewTestVote(block, leader) require.NoError(t, err) err = e.HandleMessage(&Message{ @@ -625,7 +607,7 @@ func TestEpochLeaderFailoverAfterProposal(t *testing.T) { } func TestEpochLeaderFailoverTwice(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -713,7 +695,7 @@ func TestEpochLeaderFailoverTwice(t *testing.T) { } func TestEpochLeaderFailoverGarbageCollectedEmptyVotes(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -777,7 +759,7 @@ func TestEpochLeaderFailoverBecauseOfBadBlock(t *testing.T) { // This test ensures that if a block is proposed by a node, but it is invalid, // the node will immediately proceed to notarize the empty block. - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} recordedMessages := make(chan *Message, 100) @@ -797,7 +779,7 @@ func TestEpochLeaderFailoverBecauseOfBadBlock(t *testing.T) { _, ok := bb.BuildBlock(context.Background(), e.Metadata(), emptyBlacklist) require.True(t, ok) - block := <-bb.Out + block := bb.GetBuiltBlock() block.VerificationError = errors.New("invalid block") vote, err := testutil.NewTestVote(block, nodes[1]) @@ -855,7 +837,7 @@ func TestEpochLeaderFailoverNotNeeded(t *testing.T) { return nil }) - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -880,7 +862,7 @@ func TestEpochLeaderFailoverNotNeeded(t *testing.T) { _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block := <-bb.Out + block := bb.GetBuiltBlock() vote, err := testutil.NewTestVote(block, nodes[3]) require.NoError(t, err) @@ -907,7 +889,7 @@ func TestEpochLeaderFailoverNotNeeded(t *testing.T) { func TestEpochBlacklist(t *testing.T) { blacklistedLeaderInLogs := make(chan struct{}) - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 3)} + bb := testutil.NewTestBlockBuilder().WithBlockShouldBeBuiltBuffer(3) nodes := []NodeID{{1}, {2}, {3}, {4}} @@ -1024,7 +1006,7 @@ func TestEpochBlacklist(t *testing.T) { // Now it's our turn to propose a new block. bb.BlockShouldBeBuilt <- struct{}{} - block = <-bb.Out + block = bb.GetBuiltBlock() // Inject specifically votes from the last two nodes, to ensure the blacklisted node will be redeemed // the next time we will propose a block. @@ -1063,7 +1045,7 @@ func TestEpochBlacklist(t *testing.T) { // Now it's our turn to propose a new block. bb.BlockShouldBeBuilt <- struct{}{} - block = <-bb.Out + block = bb.GetBuiltBlock() require.Equal(t, Blacklist{ NodeCount: 4, @@ -1142,7 +1124,7 @@ func (r *rebroadcastComm) Broadcast(msg *Message) { } func TestEpochRebroadcastsEmptyVote(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} comm := newRebroadcastComm(nodes) @@ -1218,11 +1200,7 @@ func runCrashAndRestartExecution(t *testing.T, e *Epoch, bb *testutil.TestBlockB nodes := e.Comm.Nodes() // Clone the block builder - bbAfterCrash := &testutil.TestBlockBuilder{ - Out: cloneBlockChan(bb.Out), - In: cloneBlockChan(bb.In), - BlockShouldBeBuilt: make(chan struct{}, cap(bb.BlockShouldBeBuilt)), - } + bbAfterCrash := testutil.NewTestBlockBuilder().WithBlockShouldBeBuiltBuffer(uint64(cap(bb.BlockShouldBeBuilt))) // Case 1: t.Run(fmt.Sprintf("%s-no-crash", t.Name()), func(t *testing.T) { @@ -1243,23 +1221,6 @@ func runCrashAndRestartExecution(t *testing.T, e *Epoch, bb *testutil.TestBlockB }) } -func cloneBlockChan(in chan *testutil.TestBlock) chan *testutil.TestBlock { - tmp := make(chan *testutil.TestBlock, cap(in)) - out := make(chan *testutil.TestBlock, cap(in)) - - for len(in) > 0 { - block := <-in - tmp <- block - out <- block - } - - for len(tmp) > 0 { - in <- <-tmp - } - - return out -} - type recordingComm struct { Communication BroadcastMessages chan *Message diff --git a/epoch_multinode_test.go b/epoch_multinode_test.go index 2ace9d53..abab7883 100644 --- a/epoch_multinode_test.go +++ b/epoch_multinode_test.go @@ -173,11 +173,7 @@ func TestSimplexMultiNodeBlacklist(t *testing.T) { net.Disconnect(nodes[3]) for i := range net.Instances[:3] { - select { - case net.Instances[i].BB.BlockShouldBeBuilt <- struct{}{}: - default: - - } + net.Instances[i].BB.TriggerBlockShouldBeBuilt() } for _, n := range net.Instances[:3] { @@ -245,18 +241,14 @@ func TestSimplexMultiNodeBlacklist(t *testing.T) { for i := 0; i < 2; i++ { net.Instances[i].BB.TriggerNewBlock() for _, n := range allButThirdNode { - n.BB.BlockShouldBeBuilt <- struct{}{} + n.BB.TriggerBlockShouldBeBuilt() n.Storage.WaitForBlockCommit(uint64(6 + i)) } } // Skip the third node because it is disconnected. for i := range allButThirdNode { - select { - case net.Instances[i].BB.BlockShouldBeBuilt <- struct{}{}: - default: - - } + net.Instances[i].BB.TriggerBlockShouldBeBuilt() } for _, n := range allButThirdNode { @@ -274,7 +266,7 @@ func TestSimplexMultiNodeBlacklist(t *testing.T) { for i := 0; i < 2; i++ { net.Instances[i].BB.TriggerNewBlock() for _, n := range allButThirdNode { - n.BB.BlockShouldBeBuilt <- struct{}{} + n.BB.TriggerBlockShouldBeBuilt() block := n.Storage.WaitForBlockCommit(uint64(8 + i)) lastBlacklist = block.Blacklist() } @@ -285,11 +277,7 @@ func TestSimplexMultiNodeBlacklist(t *testing.T) { // The third node will now time out. for i := range allButThirdNode { - select { - case net.Instances[i].BB.BlockShouldBeBuilt <- struct{}{}: - default: - - } + net.Instances[i].BB.TriggerBlockShouldBeBuilt() } for _, n := range allButThirdNode { @@ -299,7 +287,7 @@ func TestSimplexMultiNodeBlacklist(t *testing.T) { // The fourth node should now be able to propose a block. net.Instances[3].BB.TriggerNewBlock() for _, n := range allButThirdNode { - n.BB.BlockShouldBeBuilt <- struct{}{} + n.BB.TriggerBlockShouldBeBuilt() block := n.Storage.WaitForBlockCommit(uint64(10)) lastBlacklist = block.Blacklist() } @@ -341,7 +329,7 @@ func TestSplitVotes(t *testing.T) { net.TriggerLeaderBlockBuilder(0) for _, n := range net.Instances { n.WAL.AssertBlockProposal(0) - n.TriggerBlockShouldBeBuilt() + n.BB.TriggerBlockShouldBeBuilt() if n.E.ID.Equals(splitNode2.E.ID) || n.E.ID.Equals(splitNode3.E.ID) { require.Equal(t, uint64(0), n.E.Metadata().Round) diff --git a/epoch_test.go b/epoch_test.go index 2acb3d2f..b8e873fa 100644 --- a/epoch_test.go +++ b/epoch_test.go @@ -43,7 +43,7 @@ var ( // // Once we receive the empty notarization, we can verify the block(seq 1, round 2) and send out a finalize vote func TestFinalizeSameSequence(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} initialBlock := createBlocks(t, nodes, 1)[0] @@ -82,12 +82,12 @@ func TestFinalizeSameSequence(t *testing.T) { Seq: 1, // set next seq to 1 not 2 Prev: initialBlock.VerifiedBlock.BlockHeader().Digest, } - _, ok := bb.BuildBlock(context.Background(), md, simplex.Blacklist{ + vb, ok := bb.BuildBlock(context.Background(), md, simplex.Blacklist{ NodeCount: uint16(len(e.EpochConfig.Comm.Nodes())), }) require.True(t, ok) - block := <-bb.Out + block := vb.(*testutil.TestBlock) var verified atomic.Bool block.OnVerify = func() { verified.Store(true) @@ -174,7 +174,7 @@ func TestFinalizeSameSequenceGap(t *testing.T) { } func testFinalizeSameSequenceGap(t *testing.T, nodes []NodeID, numEmptyNotarizations uint64, numNotarizations uint64, seqToDoubleFinalize uint64) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() initialBlock := createBlocks(t, nodes, 1)[0] recordingComm := &recordingComm{Communication: testutil.NewNoopComm(nodes), BroadcastMessages: make(chan *Message, 100), SentMessages: make(chan *Message, 100)} @@ -223,12 +223,12 @@ func testFinalizeSameSequenceGap(t *testing.T, nodes []NodeID, numEmptyNotarizat Seq: seqToDoubleFinalize, Prev: finalizeVoteSeqs[seqToDoubleFinalize-1].Finalization.Digest, } - _, ok := bb.BuildBlock(context.Background(), md, simplex.Blacklist{ + vb, ok := bb.BuildBlock(context.Background(), md, simplex.Blacklist{ NodeCount: uint16(len(e.EpochConfig.Comm.Nodes())), }) require.True(t, ok) - block := <-bb.Out + block := vb.(*testutil.TestBlock) verified := make(chan struct{}, 1) block.OnVerify = func() { verified <- struct{}{} @@ -292,7 +292,7 @@ func testFinalizeSameSequenceGap(t *testing.T, nodes []NodeID, numEmptyNotarizat } func TestBlockNotVerifiedIfParentNotNotarized(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} @@ -357,14 +357,12 @@ func TestBlockNotVerifiedIfParentNotNotarized(t *testing.T) { } func TestEpochHandleNotarizationFutureRound(t *testing.T) { - bb := &testutil.TestBlockBuilder{} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} // Create the two blocks ahead of time blocks := createBlocks(t, nodes, 2) firstBlock := blocks[0].VerifiedBlock.(*testutil.TestBlock) secondBlock := blocks[1].VerifiedBlock.(*testutil.TestBlock) - bb.Out = make(chan *testutil.TestBlock, 1) - bb.In = make(chan *testutil.TestBlock, 1) conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) quorum := Quorum(len(nodes)) @@ -374,14 +372,6 @@ func TestEpochHandleNotarizationFutureRound(t *testing.T) { require.NoError(t, e.Start()) - // Load the first block into the block builder, so it will not create its own block but use the pre-built one. - // Drain the out channel before loading it - //for len(bb.out) > 0 { - // <-bb.out - //} - bb.In <- firstBlock - bb.Out <- firstBlock - // Create a notarization for round 1 which is a future round because we haven't gone through round 0 yet. notarization, err := testutil.NewNotarization(conf.Logger, conf.SignatureAggregator, secondBlock, nodes) require.NoError(t, err) @@ -392,7 +382,7 @@ func TestEpochHandleNotarizationFutureRound(t *testing.T) { }, nodes[1]) // Run through round 0 - notarizeAndFinalizeRound(t, e, bb) + notarizeAndFinalizeRoundWithMetadata(t, e, bb, &firstBlock.Metadata) // Emulate round 1 by sending the block vote, err := testutil.NewTestVote(secondBlock, nodes[1]) @@ -419,7 +409,7 @@ func TestEpochHandleNotarizationFutureRound(t *testing.T) { // TestEpochIndexFinalization ensures that we properly index past finalizations when // there have been empty rounds func TestEpochIndexFinalization(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) e, err := NewEpoch(conf) @@ -467,7 +457,7 @@ func TestEpochConsecutiveProposalsDoNotGetVerified(t *testing.T) { }, } { t.Run(test.name, func(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) @@ -480,12 +470,13 @@ func TestEpochConsecutiveProposalsDoNotGetVerified(t *testing.T) { leader := nodes[0] md := e.Metadata() - _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) + vb, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) require.Equal(t, md.Round, md.Seq) onlyVerifyOnce := make(chan struct{}) - block := <-bb.Out + + block := vb.(*testutil.TestBlock) block.OnVerify = func() { close(onlyVerifyOnce) } @@ -526,7 +517,7 @@ func TestEpochConsecutiveProposalsDoNotGetVerified(t *testing.T) { func TestEpochIncreasesRoundAfterFinalization(t *testing.T) { l := testutil.MakeLogger(t, 1) - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}, {5}, {6}} conf, _, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[2], testutil.NewNoopComm(nodes), bb) @@ -553,7 +544,7 @@ func TestEpochIncreasesRoundAfterFinalization(t *testing.T) { } func TestEpochNotarizeTwiceThenFinalize(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} @@ -568,7 +559,7 @@ func TestEpochNotarizeTwiceThenFinalize(t *testing.T) { require.NoError(t, e.Start()) // Round 0 - block0 := <-bb.Out + block0 := bb.GetBuiltBlock() testutil.InjectTestVote(t, e, block0, nodes[1]) testutil.InjectTestVote(t, e, block0, nodes[2]) @@ -587,7 +578,7 @@ func TestEpochNotarizeTwiceThenFinalize(t *testing.T) { md := e.Metadata() _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block1 := <-bb.Out + block1 := bb.GetBuiltBlock() vote, err := testutil.NewTestVote(block1, nodes[2]) require.NoError(t, err) @@ -606,7 +597,7 @@ func TestEpochNotarizeTwiceThenFinalize(t *testing.T) { md = e.Metadata() _, ok = bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block2 := <-bb.Out + block2 := bb.GetBuiltBlock() vote, err = testutil.NewTestVote(block2, nodes[3]) require.NoError(t, err) @@ -666,7 +657,7 @@ func TestEpochNotarizeTwiceThenFinalize(t *testing.T) { } func TestEpochFinalizeThenNotarize(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -693,7 +684,7 @@ func TestEpochFinalizeThenNotarize(t *testing.T) { require.True(t, ok) } - block := <-bb.Out + block := bb.GetBuiltBlock() vote, err := testutil.NewTestVote(block, nodes[0]) require.NoError(t, err) @@ -715,7 +706,7 @@ func TestEpochFinalizeThenNotarize(t *testing.T) { } func TestEpochSimpleFlow(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -731,7 +722,7 @@ func TestEpochSimpleFlow(t *testing.T) { } func TestEpochStartedTwice(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -759,16 +750,20 @@ func advanceRoundFromEmpty(t *testing.T, e *Epoch) { } func advanceRoundFromNotarization(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder) (VerifiedBlock, *Notarization) { - return advanceRound(t, e, bb, true, false) + return advanceRound(t, e, bb, true, false, nil) } func advanceRoundFromFinalization(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder) VerifiedBlock { - block, _ := advanceRound(t, e, bb, false, true) + block, _ := advanceRound(t, e, bb, false, true, nil) return block } func notarizeAndFinalizeRound(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder) (VerifiedBlock, *Notarization) { - return advanceRound(t, e, bb, true, true) + return advanceRound(t, e, bb, true, true, nil) +} + +func notarizeAndFinalizeRoundWithMetadata(t *testing.T, e *Epoch, bb *testutil.TestBlockBuilder, md *ProtocolMetadata) (VerifiedBlock, *Notarization) { + return advanceRound(t, e, bb, true, true, md) } func FuzzEpochInterleavingMessages(f *testing.F) { @@ -790,7 +785,7 @@ func TestEpochInterleavingMessages(t *testing.T) { func testEpochInterleavingMessages(t *testing.T, seed int64) { rounds := 10 - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, rounds)} + bb := testutil.NewTestBlockBuilder().WithBuiltBuffer(uint64(rounds)) nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -844,7 +839,7 @@ func createCallbacks(t *testing.T, rounds int, protocolMetadata ProtocolMetadata }, leader) }) } else { - bb.Out <- block + bb.SetBuiltBlock(block) } for j := 1; j <= 2; j++ { @@ -882,7 +877,7 @@ func createCallbacks(t *testing.T, rounds int, protocolMetadata ProtocolMetadata func TestEpochBlockSentTwice(t *testing.T) { var tooFarMsg, alreadyReceivedMsg bool - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) @@ -979,7 +974,7 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) { }, } - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, storage := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) l := conf.Logger.(*testutil.TestLogger) @@ -997,7 +992,7 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) { require.NoError(t, e.Start()) - block := <-bb.Out + block := bb.GetBuiltBlock() wal.AssertWALSize(1) @@ -1097,7 +1092,7 @@ func TestEpochQCSignedByNonExistentNodes(t *testing.T) { func TestEpochBlockSentFromNonLeader(t *testing.T) { nonLeaderMessage := false - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) l := conf.Logger.(*testutil.TestLogger) @@ -1138,7 +1133,7 @@ func TestEpochBlockSentFromNonLeader(t *testing.T) { func TestEpochBlockTooHighRound(t *testing.T) { var rejectedBlock bool - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) @@ -1211,7 +1206,7 @@ func TestEpochBlockTooHighRound(t *testing.T) { // TestMetadataProposedRound ensures the metadata only builds off blocks // with finalizations or notarizations func TestMetadataProposedRound(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -1227,7 +1222,7 @@ func TestMetadataProposedRound(t *testing.T) { } func TestEpochVotesForEquivocatedVotes(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} recordedMessages := make(chan *Message, 100) @@ -1243,7 +1238,7 @@ func TestEpochVotesForEquivocatedVotes(t *testing.T) { _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block := <-bb.Out + block := bb.GetBuiltBlock() // the leader and this node are sending the votes for the same block leader := nodes[0] @@ -1319,26 +1314,43 @@ func (b *listenerComm) Send(msg *Message, id NodeID) { b.in <- msg } -// garbageCollectSuspectedNodes progresses [e] to a new round. If [notarize] is set, the round will progress due to a notarization. +func TestBlockDeserializer(t *testing.T) { + var blockDeserializer testutil.BlockDeserializer + + ctx := context.Background() + tb := testutil.NewTestBlock(ProtocolMetadata{Seq: 1, Round: 2, Epoch: 3}, emptyBlacklist) + tbBytes, err := tb.Bytes() + require.NoError(t, err) + tb2, err := blockDeserializer.DeserializeBlock(ctx, tbBytes) + require.NoError(t, err) + require.Equal(t, tb, tb2) +} + +// advanceRound progresses [e] to a new round. If [notarize] is set, the round will progress due to a notarization. // If [finalize] is set, the round will advance and the block will be indexed to storage. -func advanceRound(t *testing.T, e *simplex.Epoch, bb *testutil.TestBlockBuilder, notarize bool, finalize bool) (simplex.VerifiedBlock, *simplex.Notarization) { +// If [injectedMD] is non-nil, it will be used as the metadata for the new block instead of generating one from the epoch. +func advanceRound(t *testing.T, e *simplex.Epoch, bb *testutil.TestBlockBuilder, notarize bool, finalize bool, injectedMD *ProtocolMetadata) (simplex.VerifiedBlock, *simplex.Notarization) { require.True(t, notarize || finalize, "must either notarize or finalize a round to advance") nextSeqToCommit := e.Storage.NumBlocks() nodes := e.Comm.Nodes() quorum := simplex.Quorum(len(nodes)) // leader is the proposer of the new block for the given round leader := simplex.LeaderForRound(nodes, e.Metadata().Round) + md := e.Metadata() + if injectedMD != nil { + md = *injectedMD + } + // only create blocks if we are not the node running the epoch isEpochNode := leader.Equals(e.ID) if !isEpochNode { - md := e.Metadata() _, ok := bb.BuildBlock(context.Background(), md, simplex.Blacklist{ NodeCount: uint16(len(e.EpochConfig.Comm.Nodes())), }) require.True(t, ok) } - block := <-bb.Out + block := bb.GetBuiltBlock() if !isEpochNode { // send node a message from the leader @@ -1384,18 +1396,6 @@ func advanceRound(t *testing.T, e *simplex.Epoch, bb *testutil.TestBlockBuilder, return block, notarization } -func TestBlockDeserializer(t *testing.T) { - var blockDeserializer testutil.BlockDeserializer - - ctx := context.Background() - tb := testutil.NewTestBlock(ProtocolMetadata{Seq: 1, Round: 2, Epoch: 3}, emptyBlacklist) - tbBytes, err := tb.Bytes() - require.NoError(t, err) - tb2, err := blockDeserializer.DeserializeBlock(ctx, tbBytes) - require.NoError(t, err) - require.Equal(t, tb, tb2) -} - func TestQuorum(t *testing.T) { for _, testCase := range []struct { n int diff --git a/pos_test.go b/pos_test.go index 78650ae4..1f21f858 100644 --- a/pos_test.go +++ b/pos_test.go @@ -6,11 +6,12 @@ package simplex_test import ( "bytes" "fmt" + "testing" + "time" + "github.com/ava-labs/simplex" "github.com/ava-labs/simplex/testutil" "github.com/stretchr/testify/require" - "testing" - "time" ) func TestPoS(t *testing.T) { @@ -113,7 +114,7 @@ func TestPoS(t *testing.T) { if bytes.Equal(n.E.ID, nodes[0]) || bytes.Equal(n.E.ID, nodes[3]) { continue } - n.TriggerBlockShouldBeBuilt() + n.BB.TriggerBlockShouldBeBuilt() n.AdvanceTime(n.E.EpochConfig.MaxProposalWait / 4) } @@ -146,7 +147,7 @@ func TestPoS(t *testing.T) { if bytes.Equal(n.E.ID, nodes[2]) { continue } - n.TriggerBlockShouldBeBuilt() + n.BB.TriggerBlockShouldBeBuilt() n.AdvanceTime(n.E.EpochConfig.MaxProposalWait / 4) if n.WAL.ContainsEmptyVote(15) { timedOut[i] = struct{}{} diff --git a/recovery_test.go b/recovery_test.go index 9e083dbb..06006e84 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -12,14 +12,13 @@ import ( . "github.com/ava-labs/simplex" "github.com/ava-labs/simplex/record" "github.com/ava-labs/simplex/testutil" - "github.com/stretchr/testify/require" ) // TestRecoverFromWALProposed tests that the epoch can recover from // a wal with a single block record written to it(that we have proposed). func TestRecoverFromWALProposed(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -58,7 +57,7 @@ func TestRecoverFromWALProposed(t *testing.T) { require.NotEqual(t, 0, rounds) } - block := <-bb.Out + block := bb.GetBuiltBlock() if rounds == 0 { require.Equal(t, firstBlock, block) } @@ -96,7 +95,7 @@ func TestRecoverFromWALProposed(t *testing.T) { // TestRecoverFromWALNotarized tests that the epoch can recover from a wal // with a block record written to it, and a notarization record. func TestRecoverFromNotarization(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -150,7 +149,7 @@ func TestRecoverFromNotarization(t *testing.T) { // TestRecoverFromWALFinalized tests that the epoch can recover from a wal // with a block already stored in the storage func TestRecoverFromWalWithStorage(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -212,7 +211,7 @@ func TestRecoverFromWalWithStorage(t *testing.T) { // TestWalCreated tests that the epoch correctly writes to the WAL func TestWalCreatedProperly(t *testing.T) { ctx := context.Background() - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -235,7 +234,7 @@ func TestWalCreatedProperly(t *testing.T) { require.Len(t, records, 1) blockFromWal, err := BlockFromRecord(ctx, conf.BlockDeserializer, records[0]) require.NoError(t, err) - block := <-bb.Out + block := bb.GetBuiltBlock() require.Equal(t, blockFromWal, block) // start at one since our node has already voted @@ -264,6 +263,7 @@ func TestWalCreatedProperly(t *testing.T) { committedData, err := blockRetrieved.Bytes() require.NoError(t, err) bBytes, err := block.Bytes() + require.NoError(t, err) require.Equal(t, bBytes, committedData) } @@ -271,9 +271,8 @@ func TestWalCreatedProperly(t *testing.T) { // a block proposed by a node other than the epoch node func TestWalWritesBlockRecord(t *testing.T) { ctx := context.Background() - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} - wal := testutil.NewTestWAL(t) // nodes[1] is not the leader for the first round conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[1], testutil.NewNoopComm(nodes), bb) @@ -295,7 +294,7 @@ func TestWalWritesBlockRecord(t *testing.T) { _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - block := <-bb.Out + block := bb.GetBuiltBlock() // send epoch node this block vote, err := testutil.NewTestVote(block, nodes[0]) require.NoError(t, err) @@ -319,7 +318,7 @@ func TestWalWritesBlockRecord(t *testing.T) { func TestWalWritesFinalization(t *testing.T) { ctx := context.Background() - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() sigAggregrator := &testutil.TestSignatureAggregator{N: 4} nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -329,7 +328,7 @@ func TestWalWritesFinalization(t *testing.T) { require.NoError(t, err) require.NoError(t, e.Start()) - firstBlock := <-bb.Out + firstBlock := bb.GetBuiltBlock() // notarize the first block for i := 1; i < quorum; i++ { testutil.InjectTestVote(t, e, firstBlock, nodes[i]) @@ -352,7 +351,7 @@ func TestWalWritesFinalization(t *testing.T) { md.Prev = firstBlock.BlockHeader().Digest _, ok := bb.BuildBlock(context.Background(), md, emptyBlacklist) require.True(t, ok) - secondBlock := <-bb.Out + secondBlock := bb.GetBuiltBlock() // increase the round but don't index storage require.Equal(t, uint64(1), e.Metadata().Round) @@ -406,7 +405,7 @@ func TestWalWritesFinalization(t *testing.T) { // Appends to the wal -> block, notarization, second block, notarization block 2, finalization for block 2. func TestRecoverFromMultipleNotarizations(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -478,7 +477,7 @@ func TestRecoverFromMultipleNotarizations(t *testing.T) { // TestRecoveryBlocksIndexed tests that the epoch properly skips // block records that are already indexed in the storage. func TestRecoveryBlocksIndexed(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} quorum := Quorum(len(nodes)) @@ -538,7 +537,7 @@ func TestRecoveryBlocksIndexed(t *testing.T) { func TestEpochCorrectlyInitializesMetadataFromStorage(t *testing.T) { ctx := context.Background() - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], testutil.NewNoopComm(nodes), bb) @@ -556,7 +555,7 @@ func TestEpochCorrectlyInitializesMetadataFromStorage(t *testing.T) { } func TestRecoveryAsLeader(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() ctx := context.Background() nodes := []NodeID{{1}, {2}, {3}, {4}} finalizedBlocks := createBlocks(t, nodes, 4) @@ -572,7 +571,7 @@ func TestRecoveryAsLeader(t *testing.T) { require.Equal(t, uint64(4), e.Storage.NumBlocks()) require.NoError(t, e.Start()) - <-bb.Out + bb.GetBuiltBlock() // wait for the block to finish verifying time.Sleep(50 * time.Millisecond) @@ -584,7 +583,7 @@ func TestRecoveryAsLeader(t *testing.T) { func TestRecoveryReVerifiesBlocks(t *testing.T) { ctx := context.Background() - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []NodeID{{1}, {2}, {3}, {4}} finalizedBlocks := createBlocks(t, nodes, 4) diff --git a/replication_request_test.go b/replication_request_test.go index db271c8b..cfe9befe 100644 --- a/replication_request_test.go +++ b/replication_request_test.go @@ -7,13 +7,12 @@ import ( "github.com/ava-labs/simplex" "github.com/ava-labs/simplex/testutil" - "github.com/stretchr/testify/require" ) // TestReplicationRequestIndexedBlocks tests replication requests for indexed blocks. func TestReplicationRequestIndexedBlocks(t *testing.T) { - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} comm := NewListenerComm(nodes) ctx := context.Background() @@ -68,7 +67,7 @@ func TestReplicationRequestIndexedBlocks(t *testing.T) { // TestReplicationRequestNotarizations tests replication requests for notarized blocks. func TestReplicationRequestNotarizations(t *testing.T) { // generate 5 blocks & notarizations - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} comm := NewListenerComm(nodes) conf, _, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], comm, bb) @@ -122,7 +121,7 @@ func TestReplicationRequestNotarizations(t *testing.T) { // TestReplicationRequestMixed ensures the replication response also includes empty notarizations func TestReplicationRequestMixed(t *testing.T) { // generate 5 blocks & notarizations - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} comm := NewListenerComm(nodes) conf, wal, _ := testutil.DefaultTestNodeEpochConfig(t, nodes[0], comm, bb) diff --git a/replication_test.go b/replication_test.go index c8f3c72e..bdafb4d0 100644 --- a/replication_test.go +++ b/replication_test.go @@ -13,10 +13,11 @@ import ( "time" "github.com/ava-labs/simplex" - . "github.com/ava-labs/simplex/testutil" + "github.com/stretchr/testify/require" "go.uber.org/zap/zapcore" - "github.com/stretchr/testify/require" + "github.com/ava-labs/simplex/testutil" + . "github.com/ava-labs/simplex/testutil" ) // TestReplication tests the replication process of a node that @@ -114,12 +115,13 @@ func TestReplicationAdversarialNode(t *testing.T) { blocks := []simplex.VerifiedBlock{} for i := uint64(0); i < 2; i++ { - block := net.TriggerLeaderBlockBuilder(i) + net.TriggerLeaderBlockBuilder(i) - blocks = append(blocks, block) - for _, n := range net.Instances[:3] { + for j, n := range net.Instances[:3] { committed := n.Storage.WaitForBlockCommit(i) - require.Equal(t, block, committed.(*TestBlock)) + if j == 0 { + blocks = append(blocks, committed) + } } } @@ -212,13 +214,14 @@ func TestRebroadcastingWithReplication(t *testing.T) { } // the lagging node has been asleep, it should be notified blocks are available - laggingNode.TriggerBlockShouldBeBuilt() + laggingNode.BB.TriggerBlockShouldBeBuilt() net.SetAllNodesMessageFilter(AllowAllMessages) net.Connect(laggingNode.E.ID) - block := net.TriggerLeaderBlockBuilder(numNotarizations) + net.TriggerLeaderBlockBuilder(numNotarizations) timeout := time.NewTimer(30 * time.Second) - for i := uint64(0); i <= block.Metadata.Seq; i++ { + expectedSeq := numNotarizations - missedSeqs + for i := uint64(0); i <= expectedSeq; i++ { for _, n := range net.Instances { for { committed := n.Storage.NumBlocks() @@ -241,7 +244,7 @@ func TestRebroadcastingWithReplication(t *testing.T) { } for _, n := range net.Instances { - require.Equal(t, block.Metadata.Seq+1, n.Storage.NumBlocks()) + require.Equal(t, expectedSeq+1, n.Storage.NumBlocks()) } } @@ -389,7 +392,7 @@ func TestReplicationStartsBeforeCurrentRound(t *testing.T) { func TestReplicationFutureFinalization(t *testing.T) { // send a block, then simultaneously send a finalization for the block - bb := &TestBlockBuilder{Out: make(chan *TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} quorum := simplex.Quorum(len(nodes)) @@ -405,7 +408,7 @@ func TestReplicationFutureFinalization(t *testing.T) { require.True(t, ok) require.Equal(t, md.Round, md.Seq) - block := <-bb.Out + block := bb.GetBuiltBlock() block.VerificationDelay = make(chan struct{}) // add a delay to the block verification vote, err := NewTestVote(block, nodes[0]) @@ -579,10 +582,9 @@ func TestReplicationStuckInProposingBlock(t *testing.T) { var cancelBlockBuilding sync.WaitGroup cancelBlockBuilding.Add(1) - tbb := &TestBlockBuilder{Out: make(chan *TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1), In: make(chan *TestBlock, 1)} + tbb := testutil.NewTestBlockBuilder() bb := NewTestControlledBlockBuilder(t) bb.TestBlockBuilder = *tbb - storage := NewInMemStorage() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} blocks := createBlocks(t, nodes, 5) @@ -611,11 +613,8 @@ func TestReplicationStuckInProposingBlock(t *testing.T) { require.NoError(t, err) require.NoError(t, e.Start()) - bb.In <- blocks[0].VerifiedBlock.(*TestBlock) - bb.Out <- blocks[0].VerifiedBlock.(*TestBlock) - bb.TriggerNewBlock() - notarizeAndFinalizeRound(t, e, &bb.TestBlockBuilder) + notarizeAndFinalizeRoundWithMetadata(t, e, &bb.TestBlockBuilder, &blocks[0].Finalization.Finalization.ProtocolMetadata) gb := storage.WaitForBlockCommit(0) require.Equal(t, gb, blocks[0].VerifiedBlock.(*TestBlock)) @@ -638,15 +637,11 @@ func TestReplicationStuckInProposingBlock(t *testing.T) { } // Drain the block builder channels - for len(bb.TestBlockBuilder.BlockShouldBeBuilt) > 0 && len(bb.Out) > 0 { + for len(bb.TestBlockBuilder.BlockShouldBeBuilt) > 0 { select { case <-bb.TestBlockBuilder.BlockShouldBeBuilt: default: } - select { - case <-bb.Out: - default: - } } // Prepare the quorum round answer to be sent as a response to the replication request @@ -917,7 +912,7 @@ func createBlocks(t *testing.T, nodes []simplex.NodeID, seqCount uint64) []simpl } func TestReplicationVerifyNotarization(t *testing.T) { - bb := &TestBlockBuilder{Out: make(chan *TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} // This function takes a QC and makes it that it is signed by only 2 out of 4 nodes, @@ -961,7 +956,7 @@ func TestReplicationVerifyNotarization(t *testing.T) { require.True(t, ok) require.Equal(t, md.Round, md.Seq) - block := <-bb.Out + block := bb.GetBuiltBlock() finalization, _ := NewFinalizationRecord(t, e.Logger, e.SignatureAggregator, block, nodes[0:quorum]) @@ -1003,7 +998,7 @@ func TestReplicationVerifyNotarization(t *testing.T) { } func TestReplicationVerifyEmptyNotarization(t *testing.T) { - bb := &TestBlockBuilder{Out: make(chan *TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} @@ -1047,7 +1042,7 @@ func TestReplicationVerifyEmptyNotarization(t *testing.T) { require.True(t, ok) require.Equal(t, md.Round, md.Seq) - block := <-bb.Out + block := bb.GetBuiltBlock() finalization, _ := NewFinalizationRecord(t, e.Logger, e.SignatureAggregator, block, nodes[0:quorum]) diff --git a/replication_timeout_test.go b/replication_timeout_test.go index 1acebf50..c2a23ed7 100644 --- a/replication_timeout_test.go +++ b/replication_timeout_test.go @@ -14,7 +14,6 @@ import ( "github.com/ava-labs/simplex" "github.com/ava-labs/simplex/testutil" - "github.com/stretchr/testify/require" ) @@ -587,7 +586,7 @@ func TestReplicationResendsFinalizedBlocksThatFailedVerification(t *testing.T) { // send a block, then simultaneously send a finalization for the block l := testutil.MakeLogger(t, 1) - bb := &testutil.TestBlockBuilder{Out: make(chan *testutil.TestBlock, 1)} + bb := testutil.NewTestBlockBuilder() nodes := []simplex.NodeID{{1}, {2}, {3}, {4}} quorum := simplex.Quorum(len(nodes)) @@ -609,7 +608,7 @@ func TestReplicationResendsFinalizedBlocksThatFailedVerification(t *testing.T) { require.True(t, ok) require.Equal(t, md.Round, md.Seq) - block := <-bb.Out + block := bb.GetBuiltBlock() block.VerificationError = errors.New("block verification failed") finalization, _ := testutil.NewFinalizationRecord(t, l, e.SignatureAggregator, block, nodes[0:quorum]) diff --git a/testutil/block_builder.go b/testutil/block_builder.go index 1aa0bdf5..01e499d0 100644 --- a/testutil/block_builder.go +++ b/testutil/block_builder.go @@ -6,41 +6,65 @@ package testutil import ( "context" "testing" + "time" "github.com/ava-labs/simplex" ) type TestBlockBuilder struct { - Out chan *TestBlock - In chan *TestBlock + // built is a channel that holds built test blocks + built chan *TestBlock BlockShouldBeBuilt chan struct{} } func NewTestBlockBuilder() *TestBlockBuilder { return &TestBlockBuilder{ - Out: make(chan *TestBlock, 1), - In: make(chan *TestBlock, 1), + built: make(chan *TestBlock, 1), BlockShouldBeBuilt: make(chan struct{}, 1), } } -// BuildBlock builds a new testblock and sends it to the BlockBuilder channel -func (t *TestBlockBuilder) BuildBlock(_ context.Context, metadata simplex.ProtocolMetadata, blacklist simplex.Blacklist) (simplex.VerifiedBlock, bool) { - if len(t.In) > 0 { - block := <-t.In - return block, true - } +func (t *TestBlockBuilder) WithBuiltBuffer(buffer uint64) *TestBlockBuilder { + t.built = make(chan *TestBlock, buffer) + return t +} +func (t *TestBlockBuilder) WithBlockShouldBeBuiltBuffer(buffer uint64) *TestBlockBuilder { + t.BlockShouldBeBuilt = make(chan struct{}, buffer) + return t +} + +func (t *TestBlockBuilder) BuildBlock(_ context.Context, metadata simplex.ProtocolMetadata, blacklist simplex.Blacklist) (simplex.VerifiedBlock, bool) { tb := NewTestBlock(metadata, blacklist) select { - case t.Out <- tb: + case t.built <- tb: + return tb, true default: } - return tb, true } +func (t *TestBlockBuilder) GetBuiltBlock() *TestBlock { + timeout := time.NewTimer(10 * time.Second) + defer timeout.Stop() + + select { + case b := <-t.built: + return b + case <-timeout.C: + panic("timed out waiting for built block") + } +} + +func (t *TestBlockBuilder) SetBuiltBlock(block *TestBlock) { + select { + case t.built <- block: + default: + panic("built channel is full") + } +} + func (t *TestBlockBuilder) WaitForPendingBlock(ctx context.Context) { select { case <-t.BlockShouldBeBuilt: @@ -48,6 +72,8 @@ func (t *TestBlockBuilder) WaitForPendingBlock(ctx context.Context) { } } +// testControlledBlockBuilder is a BlockBuilder that only builds a block when +// a control signal is received. type testControlledBlockBuilder struct { t *testing.T control chan struct{} @@ -68,7 +94,13 @@ func (t *testControlledBlockBuilder) TriggerNewBlock() { select { case t.control <- struct{}{}: default: + } +} +func (t *testControlledBlockBuilder) TriggerBlockShouldBeBuilt() { + select { + case t.BlockShouldBeBuilt <- struct{}{}: + default: } } diff --git a/testutil/network.go b/testutil/network.go index 5970939a..85491ad7 100644 --- a/testutil/network.go +++ b/testutil/network.go @@ -46,7 +46,7 @@ func (n *InMemNetwork) StartInstances() { } } -func (n *InMemNetwork) TriggerLeaderBlockBuilder(round uint64) *TestBlock { +func (n *InMemNetwork) TriggerLeaderBlockBuilder(round uint64) { leader := simplex.LeaderForRound(n.nodes, round) for _, instance := range n.Instances { if !instance.E.ID.Equals(leader) { @@ -61,12 +61,11 @@ func (n *InMemNetwork) TriggerLeaderBlockBuilder(round uint64) *TestBlock { WaitToEnterRound(n.t, instance.E, round) instance.BB.TriggerNewBlock() - return <-instance.BB.Out + return } // we should always find the leader require.Fail(n.t, "leader not found") - return nil } func (n *InMemNetwork) addNode(node *TestNode) { @@ -130,7 +129,7 @@ func (n *InMemNetwork) AdvanceWithoutLeader(round uint64, laggingNodeId simplex. } for _, n := range n.Instances { - n.TriggerBlockShouldBeBuilt() + n.BB.TriggerBlockShouldBeBuilt() } for _, n := range n.Instances { diff --git a/testutil/node.go b/testutil/node.go index 4decc8b8..b24d6915 100644 --- a/testutil/node.go +++ b/testutil/node.go @@ -89,14 +89,6 @@ type TestNodeConfig struct { ReplicationEnabled bool } -// TriggerBlockShouldBeBuilt signals this nodes block builder it is expecting a block to be built. -func (t *TestNode) TriggerBlockShouldBeBuilt() { - select { - case t.BB.BlockShouldBeBuilt <- struct{}{}: - default: - } -} - func (t *TestNode) AdvanceTime(duration time.Duration) { now := time.UnixMilli(t.currentTime.Load()).Add(duration) t.currentTime.Store(now.UnixMilli())