github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/consensus/sealing/engine_test.go (about)

     1  package sealing
     2  
     3  import (
     4  	"sync"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/stretchr/testify/require"
     9  	"github.com/stretchr/testify/suite"
    10  
    11  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    12  	"github.com/onflow/flow-go/engine"
    13  	mockconsensus "github.com/onflow/flow-go/engine/consensus/mock"
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/model/messages"
    16  	"github.com/onflow/flow-go/module/metrics"
    17  	mockmodule "github.com/onflow/flow-go/module/mock"
    18  	"github.com/onflow/flow-go/network/channels"
    19  	mockprotocol "github.com/onflow/flow-go/state/protocol/mock"
    20  	mockstorage "github.com/onflow/flow-go/storage/mock"
    21  	"github.com/onflow/flow-go/utils/unittest"
    22  )
    23  
    24  func TestSealingEngineContext(t *testing.T) {
    25  	suite.Run(t, new(SealingEngineSuite))
    26  }
    27  
    28  type SealingEngineSuite struct {
    29  	suite.Suite
    30  
    31  	core    *mockconsensus.SealingCore
    32  	state   *mockprotocol.State
    33  	index   *mockstorage.Index
    34  	results *mockstorage.ExecutionResults
    35  	myID    flow.Identifier
    36  
    37  	// Sealing Engine
    38  	engine *Engine
    39  }
    40  
    41  func (s *SealingEngineSuite) SetupTest() {
    42  	metrics := metrics.NewNoopCollector()
    43  	s.core = &mockconsensus.SealingCore{}
    44  	s.state = &mockprotocol.State{}
    45  	s.index = &mockstorage.Index{}
    46  	s.results = &mockstorage.ExecutionResults{}
    47  	s.myID = unittest.IdentifierFixture()
    48  	me := &mockmodule.Local{}
    49  	// set up local module mock
    50  	me.On("NodeID").Return(
    51  		func() flow.Identifier {
    52  			return s.myID
    53  		},
    54  	)
    55  
    56  	rootHeader, err := unittest.RootSnapshotFixture(unittest.IdentityListFixture(5, unittest.WithAllRoles())).Head()
    57  	require.NoError(s.T(), err)
    58  
    59  	s.engine = &Engine{
    60  		log:           unittest.Logger(),
    61  		unit:          engine.NewUnit(),
    62  		core:          s.core,
    63  		me:            me,
    64  		engineMetrics: metrics,
    65  		cacheMetrics:  metrics,
    66  		rootHeader:    rootHeader,
    67  		index:         s.index,
    68  		results:       s.results,
    69  		state:         s.state,
    70  	}
    71  
    72  	// setup inbound queues for trusted inputs and message handler for untrusted inputs
    73  	err = s.engine.setupTrustedInboundQueues()
    74  	require.NoError(s.T(), err)
    75  	err = s.engine.setupMessageHandler(unittest.NewSealingConfigs(RequiredApprovalsForSealConstructionTestingValue))
    76  	require.NoError(s.T(), err)
    77  
    78  	<-s.engine.Ready()
    79  }
    80  
    81  // TestOnFinalizedBlock tests if finalized block gets processed when send through `Engine`.
    82  // Tests the whole processing pipeline.
    83  func (s *SealingEngineSuite) TestOnFinalizedBlock() {
    84  
    85  	finalizedBlock := unittest.BlockHeaderFixture()
    86  	finalizedBlockID := finalizedBlock.ID()
    87  
    88  	s.state.On("Final").Return(unittest.StateSnapshotForKnownBlock(finalizedBlock, nil))
    89  	s.core.On("ProcessFinalizedBlock", finalizedBlockID).Return(nil).Once()
    90  	s.engine.OnFinalizedBlock(model.BlockFromFlow(finalizedBlock))
    91  
    92  	// matching engine has at least 100ms ticks for processing events
    93  	time.Sleep(1 * time.Second)
    94  
    95  	s.core.AssertExpectations(s.T())
    96  }
    97  
    98  // TestOnBlockIncorporated tests if incorporated block gets processed when send through `Engine`.
    99  // Tests the whole processing pipeline.
   100  func (s *SealingEngineSuite) TestOnBlockIncorporated() {
   101  	parentBlock := unittest.BlockHeaderFixture()
   102  	incorporatedBlock := unittest.BlockHeaderWithParentFixture(parentBlock)
   103  	incorporatedBlockID := incorporatedBlock.ID()
   104  	// setup payload fixture
   105  	payload := unittest.PayloadFixture(unittest.WithAllTheFixins)
   106  	index := &flow.Index{}
   107  
   108  	for _, result := range payload.Results {
   109  		index.ResultIDs = append(index.ReceiptIDs, result.ID())
   110  		s.results.On("ByID", result.ID()).Return(result, nil).Once()
   111  
   112  		IR := flow.NewIncorporatedResult(parentBlock.ID(), result)
   113  		s.core.On("ProcessIncorporatedResult", IR).Return(nil).Once()
   114  	}
   115  	s.index.On("ByBlockID", parentBlock.ID()).Return(index, nil)
   116  
   117  	// setup headers storage
   118  	headers := &mockstorage.Headers{}
   119  	headers.On("ByBlockID", incorporatedBlockID).Return(incorporatedBlock, nil).Once()
   120  	s.engine.headers = headers
   121  
   122  	s.engine.OnBlockIncorporated(model.BlockFromFlow(incorporatedBlock))
   123  
   124  	// matching engine has at least 100ms ticks for processing events
   125  	time.Sleep(1 * time.Second)
   126  
   127  	s.core.AssertExpectations(s.T())
   128  }
   129  
   130  // TestMultipleProcessingItems tests that the engine queues multiple receipts and approvals
   131  // and eventually feeds them into sealing.Core for processing
   132  func (s *SealingEngineSuite) TestMultipleProcessingItems() {
   133  	originID := unittest.IdentifierFixture()
   134  	block := unittest.BlockFixture()
   135  
   136  	receipts := make([]*flow.ExecutionReceipt, 20)
   137  	for i := range receipts {
   138  		receipt := unittest.ExecutionReceiptFixture(
   139  			unittest.WithExecutorID(originID),
   140  			unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&block))),
   141  		)
   142  		receipts[i] = receipt
   143  	}
   144  
   145  	numApprovalsPerReceipt := 1
   146  	approvals := make([]*flow.ResultApproval, 0, len(receipts)*numApprovalsPerReceipt)
   147  	responseApprovals := make([]*messages.ApprovalResponse, 0)
   148  	approverID := unittest.IdentifierFixture()
   149  	for _, receipt := range receipts {
   150  		for j := 0; j < numApprovalsPerReceipt; j++ {
   151  			approval := unittest.ResultApprovalFixture(unittest.WithExecutionResultID(receipt.ID()),
   152  				unittest.WithApproverID(approverID))
   153  			responseApproval := &messages.ApprovalResponse{
   154  				Approval: *approval,
   155  			}
   156  			responseApprovals = append(responseApprovals, responseApproval)
   157  			approvals = append(approvals, approval)
   158  			s.core.On("ProcessApproval", approval).Return(nil).Twice()
   159  		}
   160  	}
   161  
   162  	var wg sync.WaitGroup
   163  	wg.Add(1)
   164  	go func() {
   165  		defer wg.Done()
   166  		for _, approval := range approvals {
   167  			err := s.engine.Process(channels.ReceiveApprovals, approverID, approval)
   168  			s.Require().NoError(err, "should process approval")
   169  		}
   170  	}()
   171  	wg.Add(1)
   172  	go func() {
   173  		defer wg.Done()
   174  		for _, approval := range responseApprovals {
   175  			err := s.engine.Process(channels.ReceiveApprovals, approverID, approval)
   176  			s.Require().NoError(err, "should process approval")
   177  		}
   178  	}()
   179  
   180  	wg.Wait()
   181  
   182  	// sealing engine has at least 100ms ticks for processing events
   183  	time.Sleep(1 * time.Second)
   184  
   185  	s.core.AssertExpectations(s.T())
   186  }
   187  
   188  // try to submit an approval where the message origin is inconsistent with the message creator
   189  func (s *SealingEngineSuite) TestApprovalInvalidOrigin() {
   190  	// approval from valid origin (i.e. a verification node) but with random ApproverID
   191  	originID := unittest.IdentifierFixture()
   192  	approval := unittest.ResultApprovalFixture() // with random ApproverID
   193  
   194  	err := s.engine.Process(channels.ReceiveApprovals, originID, approval)
   195  	s.Require().NoError(err, "approval from unknown verifier should be dropped but not error")
   196  
   197  	// sealing engine has at least 100ms ticks for processing events
   198  	time.Sleep(1 * time.Second)
   199  
   200  	// In both cases, we expect the approval to be rejected without hitting the mempools
   201  	s.core.AssertNumberOfCalls(s.T(), "ProcessApproval", 0)
   202  }
   203  
   204  // TestProcessUnsupportedMessageType tests that Process and ProcessLocal correctly handle a case where invalid message type
   205  // was submitted from network layer.
   206  func (s *SealingEngineSuite) TestProcessUnsupportedMessageType() {
   207  	invalidEvent := uint64(42)
   208  	err := s.engine.Process("ch", unittest.IdentifierFixture(), invalidEvent)
   209  	// shouldn't result in error since byzantine inputs are expected
   210  	require.NoError(s.T(), err)
   211  	// in case of local processing error cannot be consumed since all inputs are trusted
   212  	err = s.engine.ProcessLocal(invalidEvent)
   213  	require.Error(s.T(), err)
   214  	require.True(s.T(), engine.IsIncompatibleInputTypeError(err))
   215  }