github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/compliance/engine_test.go (about) 1 package compliance 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/mock" 11 "github.com/stretchr/testify/require" 12 "github.com/stretchr/testify/suite" 13 14 "github.com/onflow/flow-go/consensus/hotstuff/model" 15 "github.com/onflow/flow-go/model/flow" 16 "github.com/onflow/flow-go/model/messages" 17 "github.com/onflow/flow-go/module/irrecoverable" 18 modulemock "github.com/onflow/flow-go/module/mock" 19 "github.com/onflow/flow-go/utils/unittest" 20 ) 21 22 func TestComplianceEngine(t *testing.T) { 23 suite.Run(t, new(EngineSuite)) 24 } 25 26 // EngineSuite tests the compliance engine. 27 type EngineSuite struct { 28 CommonSuite 29 30 ctx irrecoverable.SignalerContext 31 cancel context.CancelFunc 32 errs <-chan error 33 engine *Engine 34 } 35 36 func (cs *EngineSuite) SetupTest() { 37 cs.CommonSuite.SetupTest() 38 39 e, err := NewEngine(unittest.Logger(), cs.me, cs.core) 40 require.NoError(cs.T(), err) 41 cs.engine = e 42 43 cs.ctx, cs.cancel, cs.errs = irrecoverable.WithSignallerAndCancel(context.Background()) 44 cs.engine.Start(cs.ctx) 45 go unittest.FailOnIrrecoverableError(cs.T(), cs.ctx.Done(), cs.errs) 46 47 unittest.AssertClosesBefore(cs.T(), cs.engine.Ready(), time.Second) 48 } 49 50 // TearDownTest stops the engine and checks there are no errors thrown to the SignallerContext. 51 func (cs *EngineSuite) TearDownTest() { 52 cs.cancel() 53 unittest.RequireCloseBefore(cs.T(), cs.engine.Done(), time.Second, "engine failed to stop") 54 select { 55 case err := <-cs.errs: 56 assert.NoError(cs.T(), err) 57 default: 58 } 59 } 60 61 // TestSubmittingMultipleVotes tests that we can send multiple blocks, and they 62 // are queued and processed in expected way 63 func (cs *EngineSuite) TestSubmittingMultipleEntries() { 64 // create a vote 65 blockCount := 15 66 67 var wg sync.WaitGroup 68 wg.Add(1) 69 go func() { 70 for i := 0; i < blockCount; i++ { 71 block := unittest.BlockWithParentFixture(cs.head) 72 proposal := messages.NewBlockProposal(block) 73 hotstuffProposal := model.ProposalFromFlow(block.Header) 74 cs.hotstuff.On("SubmitProposal", hotstuffProposal).Return().Once() 75 cs.voteAggregator.On("AddBlock", hotstuffProposal).Once() 76 cs.validator.On("ValidateProposal", hotstuffProposal).Return(nil).Once() 77 // execute the block submission 78 cs.engine.OnBlockProposal(flow.Slashable[*messages.BlockProposal]{ 79 OriginID: unittest.IdentifierFixture(), 80 Message: proposal, 81 }) 82 } 83 wg.Done() 84 }() 85 wg.Add(1) 86 go func() { 87 // create a proposal that directly descends from the latest finalized header 88 block := unittest.BlockWithParentFixture(cs.head) 89 proposal := unittest.ProposalFromBlock(block) 90 91 hotstuffProposal := model.ProposalFromFlow(block.Header) 92 cs.hotstuff.On("SubmitProposal", hotstuffProposal).Return().Once() 93 cs.voteAggregator.On("AddBlock", hotstuffProposal).Once() 94 cs.validator.On("ValidateProposal", hotstuffProposal).Return(nil).Once() 95 cs.engine.OnBlockProposal(flow.Slashable[*messages.BlockProposal]{ 96 OriginID: unittest.IdentifierFixture(), 97 Message: proposal, 98 }) 99 wg.Done() 100 }() 101 102 // wait for all messages to be delivered to the engine message queue 103 wg.Wait() 104 // wait for the votes queue to drain 105 assert.Eventually(cs.T(), func() bool { 106 return cs.engine.pendingBlocks.Len() == 0 107 }, time.Second, time.Millisecond*10) 108 } 109 110 // TestOnFinalizedBlock tests if finalized block gets processed when send through `Engine`. 111 // Tests the whole processing pipeline. 112 func (cs *EngineSuite) TestOnFinalizedBlock() { 113 finalizedBlock := unittest.BlockHeaderFixture() 114 cs.head = finalizedBlock 115 cs.headerDB[finalizedBlock.ID()] = finalizedBlock 116 117 *cs.pending = *modulemock.NewPendingBlockBuffer(cs.T()) 118 // wait for both expected calls before ending the test 119 wg := new(sync.WaitGroup) 120 wg.Add(2) 121 cs.pending.On("PruneByView", finalizedBlock.View). 122 Run(func(_ mock.Arguments) { wg.Done() }). 123 Return(nil).Once() 124 cs.pending.On("Size"). 125 Run(func(_ mock.Arguments) { wg.Done() }). 126 Return(uint(0)).Once() 127 128 err := cs.engine.processOnFinalizedBlock(model.BlockFromFlow(finalizedBlock)) 129 require.NoError(cs.T(), err) 130 unittest.AssertReturnsBefore(cs.T(), wg.Wait, time.Second, "an expected call to block buffer wasn't made") 131 }