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  }