github.com/koko1123/flow-go-1@v0.29.6/engine/common/synchronization/engine_test.go (about)

     1  package synchronization
     2  
     3  import (
     4  	"io"
     5  	"math"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  	"github.com/stretchr/testify/suite"
    15  
    16  	"github.com/koko1123/flow-go-1/consensus/hotstuff/notifications/pubsub"
    17  	"github.com/koko1123/flow-go-1/engine"
    18  	"github.com/koko1123/flow-go-1/model/events"
    19  	"github.com/koko1123/flow-go-1/model/flow"
    20  	"github.com/koko1123/flow-go-1/model/flow/filter"
    21  	"github.com/koko1123/flow-go-1/model/messages"
    22  	synccore "github.com/koko1123/flow-go-1/module/chainsync"
    23  	"github.com/koko1123/flow-go-1/module/id"
    24  	"github.com/koko1123/flow-go-1/module/metrics"
    25  	module "github.com/koko1123/flow-go-1/module/mock"
    26  	netint "github.com/koko1123/flow-go-1/network"
    27  	"github.com/koko1123/flow-go-1/network/channels"
    28  	"github.com/koko1123/flow-go-1/network/mocknetwork"
    29  	"github.com/koko1123/flow-go-1/network/p2p/cache"
    30  	protocolint "github.com/koko1123/flow-go-1/state/protocol"
    31  	protocolEvents "github.com/koko1123/flow-go-1/state/protocol/events"
    32  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    33  	storerr "github.com/koko1123/flow-go-1/storage"
    34  	storage "github.com/koko1123/flow-go-1/storage/mock"
    35  	"github.com/koko1123/flow-go-1/utils/unittest"
    36  )
    37  
    38  func TestSyncEngine(t *testing.T) {
    39  	suite.Run(t, new(SyncSuite))
    40  }
    41  
    42  type SyncSuite struct {
    43  	suite.Suite
    44  	myID         flow.Identifier
    45  	participants flow.IdentityList
    46  	head         *flow.Header
    47  	heights      map[uint64]*flow.Block
    48  	blockIDs     map[flow.Identifier]*flow.Block
    49  	net          *mocknetwork.Network
    50  	con          *mocknetwork.Conduit
    51  	me           *module.Local
    52  	state        *protocol.State
    53  	snapshot     *protocol.Snapshot
    54  	blocks       *storage.Blocks
    55  	comp         *mocknetwork.Engine
    56  	core         *module.SyncCore
    57  	e            *Engine
    58  }
    59  
    60  func (ss *SyncSuite) SetupTest() {
    61  	// seed the RNG
    62  	rand.Seed(time.Now().UnixNano())
    63  
    64  	// generate own ID
    65  	ss.participants = unittest.IdentityListFixture(3, unittest.WithRole(flow.RoleConsensus))
    66  	keys := unittest.NetworkingKeys(len(ss.participants))
    67  
    68  	for i, p := range ss.participants {
    69  		p.NetworkPubKey = keys[i].PublicKey()
    70  	}
    71  	ss.myID = ss.participants[0].NodeID
    72  
    73  	// generate a header for the final state
    74  	header := unittest.BlockHeaderFixture()
    75  	ss.head = header
    76  
    77  	// create maps to enable block returns
    78  	ss.heights = make(map[uint64]*flow.Block)
    79  	ss.blockIDs = make(map[flow.Identifier]*flow.Block)
    80  
    81  	// set up the network module mock
    82  	ss.net = &mocknetwork.Network{}
    83  	ss.net.On("Register", mock.Anything, mock.Anything).Return(
    84  		func(channel channels.Channel, engine netint.MessageProcessor) netint.Conduit {
    85  			return ss.con
    86  		},
    87  		nil,
    88  	)
    89  
    90  	// set up the network conduit mock
    91  	ss.con = &mocknetwork.Conduit{}
    92  
    93  	// set up the local module mock
    94  	ss.me = &module.Local{}
    95  	ss.me.On("NodeID").Return(
    96  		func() flow.Identifier {
    97  			return ss.myID
    98  		},
    99  	)
   100  
   101  	// set up the protocol state mock
   102  	ss.state = &protocol.State{}
   103  	ss.state.On("Final").Return(
   104  		func() protocolint.Snapshot {
   105  			return ss.snapshot
   106  		},
   107  	)
   108  	ss.state.On("AtBlockID", mock.Anything).Return(
   109  		func(blockID flow.Identifier) protocolint.Snapshot {
   110  			if ss.head.ID() == blockID {
   111  				return ss.snapshot
   112  			} else {
   113  				return unittest.StateSnapshotForUnknownBlock()
   114  			}
   115  		},
   116  	).Maybe()
   117  
   118  	// set up the snapshot mock
   119  	ss.snapshot = &protocol.Snapshot{}
   120  	ss.snapshot.On("Head").Return(
   121  		func() *flow.Header {
   122  			return ss.head
   123  		},
   124  		nil,
   125  	)
   126  	ss.snapshot.On("Identities", mock.Anything).Return(
   127  		func(selector flow.IdentityFilter) flow.IdentityList {
   128  			return ss.participants.Filter(selector)
   129  		},
   130  		nil,
   131  	)
   132  
   133  	// set up blocks storage mock
   134  	ss.blocks = &storage.Blocks{}
   135  	ss.blocks.On("ByHeight", mock.Anything).Return(
   136  		func(height uint64) *flow.Block {
   137  			return ss.heights[height]
   138  		},
   139  		func(height uint64) error {
   140  			_, enabled := ss.heights[height]
   141  			if !enabled {
   142  				return storerr.ErrNotFound
   143  			}
   144  			return nil
   145  		},
   146  	)
   147  	ss.blocks.On("ByID", mock.Anything).Return(
   148  		func(blockID flow.Identifier) *flow.Block {
   149  			return ss.blockIDs[blockID]
   150  		},
   151  		func(blockID flow.Identifier) error {
   152  			_, enabled := ss.blockIDs[blockID]
   153  			if !enabled {
   154  				return storerr.ErrNotFound
   155  			}
   156  			return nil
   157  		},
   158  	)
   159  
   160  	// set up compliance engine mock
   161  	ss.comp = &mocknetwork.Engine{}
   162  	ss.comp.On("SubmitLocal", mock.Anything).Return()
   163  
   164  	// set up sync core
   165  	ss.core = &module.SyncCore{}
   166  
   167  	// initialize the engine
   168  	log := zerolog.New(io.Discard)
   169  	metrics := metrics.NewNoopCollector()
   170  
   171  	finalizedHeader, err := NewFinalizedHeaderCache(log, ss.state, pubsub.NewFinalizationDistributor())
   172  	require.NoError(ss.T(), err, "could not create finalized snapshot cache")
   173  
   174  	idCache, err := cache.NewProtocolStateIDCache(log, ss.state, protocolEvents.NewDistributor())
   175  	require.NoError(ss.T(), err, "could not create protocol state identity cache")
   176  	e, err := New(log, metrics, ss.net, ss.me, ss.blocks, ss.comp, ss.core, finalizedHeader,
   177  		id.NewIdentityFilterIdentifierProvider(
   178  			filter.And(
   179  				filter.HasRole(flow.RoleConsensus),
   180  				filter.Not(filter.HasNodeID(ss.me.NodeID())),
   181  			),
   182  			idCache,
   183  		))
   184  	require.NoError(ss.T(), err, "should pass engine initialization")
   185  
   186  	ss.e = e
   187  }
   188  
   189  func (ss *SyncSuite) TestOnSyncRequest() {
   190  
   191  	// generate origin and request message
   192  	originID := unittest.IdentifierFixture()
   193  	req := &messages.SyncRequest{
   194  		Nonce:  rand.Uint64(),
   195  		Height: 0,
   196  	}
   197  
   198  	// regardless of request height, if within tolerance, we should not respond
   199  	ss.core.On("HandleHeight", ss.head, req.Height)
   200  	ss.core.On("WithinTolerance", ss.head, req.Height).Return(true)
   201  	err := ss.e.requestHandler.onSyncRequest(originID, req)
   202  	ss.Assert().NoError(err, "same height sync request should pass")
   203  	ss.con.AssertNotCalled(ss.T(), "Unicast", mock.Anything, mock.Anything)
   204  
   205  	// if request height is higher than local finalized, we should not respond
   206  	req.Height = ss.head.Height + 1
   207  	ss.core.On("HandleHeight", ss.head, req.Height)
   208  	ss.core.On("WithinTolerance", ss.head, req.Height).Return(false)
   209  	err = ss.e.requestHandler.onSyncRequest(originID, req)
   210  	ss.Assert().NoError(err, "same height sync request should pass")
   211  	ss.con.AssertNotCalled(ss.T(), "Unicast", mock.Anything, mock.Anything)
   212  
   213  	// if the request height is lower than head and outside tolerance, we should submit correct response
   214  	req.Height = ss.head.Height - 1
   215  	ss.core.On("HandleHeight", ss.head, req.Height)
   216  	ss.core.On("WithinTolerance", ss.head, req.Height).Return(false)
   217  	ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Run(
   218  		func(args mock.Arguments) {
   219  			res := args.Get(0).(*messages.SyncResponse)
   220  			assert.Equal(ss.T(), ss.head.Height, res.Height, "response should contain head height")
   221  			assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   222  			recipientID := args.Get(1).(flow.Identifier)
   223  			assert.Equal(ss.T(), originID, recipientID, "should send response to original sender")
   224  		},
   225  	)
   226  	err = ss.e.requestHandler.onSyncRequest(originID, req)
   227  	require.NoError(ss.T(), err, "smaller height sync request should pass")
   228  
   229  	ss.core.AssertExpectations(ss.T())
   230  }
   231  
   232  func (ss *SyncSuite) TestOnSyncResponse() {
   233  
   234  	// generate origin ID and response message
   235  	originID := unittest.IdentifierFixture()
   236  	res := &messages.SyncResponse{
   237  		Nonce:  rand.Uint64(),
   238  		Height: rand.Uint64(),
   239  	}
   240  
   241  	// the height should be handled
   242  	ss.core.On("HandleHeight", ss.head, res.Height)
   243  	ss.e.onSyncResponse(originID, res)
   244  	ss.core.AssertExpectations(ss.T())
   245  }
   246  
   247  func (ss *SyncSuite) TestOnRangeRequest() {
   248  
   249  	// generate originID and range request
   250  	originID := unittest.IdentifierFixture()
   251  	req := &messages.RangeRequest{
   252  		Nonce:      rand.Uint64(),
   253  		FromHeight: 0,
   254  		ToHeight:   0,
   255  	}
   256  
   257  	// fill in blocks at heights -1 to -4 from head
   258  	ref := ss.head.Height
   259  	for height := ref; height >= ref-4; height-- {
   260  		block := unittest.BlockFixture()
   261  		block.Header.Height = height
   262  		ss.heights[height] = &block
   263  	}
   264  
   265  	// empty range should be a no-op
   266  	ss.T().Run("empty range", func(t *testing.T) {
   267  		req.FromHeight = ref
   268  		req.ToHeight = ref - 1
   269  		err := ss.e.requestHandler.onRangeRequest(originID, req)
   270  		require.NoError(ss.T(), err, "empty range request should pass")
   271  		ss.con.AssertNumberOfCalls(ss.T(), "Unicast", 0)
   272  	})
   273  
   274  	// range with only unknown block should be a no-op
   275  	ss.T().Run("range with unknown block", func(t *testing.T) {
   276  		req.FromHeight = ref + 1
   277  		req.ToHeight = ref + 3
   278  		err := ss.e.requestHandler.onRangeRequest(originID, req)
   279  		require.NoError(ss.T(), err, "unknown range request should pass")
   280  		ss.con.AssertNumberOfCalls(ss.T(), "Unicast", 0)
   281  	})
   282  
   283  	// a request for same from and to should send single block
   284  	ss.T().Run("from == to", func(t *testing.T) {
   285  		req.FromHeight = ref - 1
   286  		req.ToHeight = ref - 1
   287  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Once().Run(
   288  			func(args mock.Arguments) {
   289  				res := args.Get(0).(*messages.BlockResponse)
   290  				expected := ss.heights[ref-1]
   291  				actual := res.Blocks[0].ToInternal()
   292  				assert.Equal(ss.T(), expected, actual, "response should contain right block")
   293  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   294  				recipientID := args.Get(1).(flow.Identifier)
   295  				assert.Equal(ss.T(), originID, recipientID, "should send response to original requester")
   296  			},
   297  		)
   298  		err := ss.e.requestHandler.onRangeRequest(originID, req)
   299  		require.NoError(ss.T(), err, "range request with higher to height should pass")
   300  	})
   301  
   302  	// a request for a range that we partially have should send partial response
   303  	ss.T().Run("have partial range", func(t *testing.T) {
   304  		req.FromHeight = ref - 2
   305  		req.ToHeight = ref + 2
   306  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Once().Run(
   307  			func(args mock.Arguments) {
   308  				res := args.Get(0).(*messages.BlockResponse)
   309  				expected := []*flow.Block{ss.heights[ref-2], ss.heights[ref-1], ss.heights[ref]}
   310  				assert.ElementsMatch(ss.T(), expected, res.BlocksInternal(), "response should contain right blocks")
   311  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   312  				recipientID := args.Get(1).(flow.Identifier)
   313  				assert.Equal(ss.T(), originID, recipientID, "should send response to original requester")
   314  			},
   315  		)
   316  		err := ss.e.requestHandler.onRangeRequest(originID, req)
   317  		require.NoError(ss.T(), err, "valid range with missing blocks should fail")
   318  	})
   319  
   320  	// a request for a range we entirely have should send all blocks
   321  	ss.T().Run("have entire range", func(t *testing.T) {
   322  		req.FromHeight = ref - 2
   323  		req.ToHeight = ref
   324  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Once().Run(
   325  			func(args mock.Arguments) {
   326  				res := args.Get(0).(*messages.BlockResponse)
   327  				expected := []*flow.Block{ss.heights[ref-2], ss.heights[ref-1], ss.heights[ref]}
   328  				assert.ElementsMatch(ss.T(), expected, res.BlocksInternal(), "response should contain right blocks")
   329  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   330  				recipientID := args.Get(1).(flow.Identifier)
   331  				assert.Equal(ss.T(), originID, recipientID, "should send response to original requester")
   332  			},
   333  		)
   334  		err := ss.e.requestHandler.onRangeRequest(originID, req)
   335  		require.NoError(ss.T(), err, "valid range request should pass")
   336  	})
   337  
   338  	// a request for a range larger than MaxSize should be clamped
   339  	ss.T().Run("oversized range", func(t *testing.T) {
   340  		req.FromHeight = ref - 4
   341  		req.ToHeight = math.MaxUint64
   342  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Once().Run(
   343  			func(args mock.Arguments) {
   344  				res := args.Get(0).(*messages.BlockResponse)
   345  				expected := []*flow.Block{ss.heights[ref-4], ss.heights[ref-3], ss.heights[ref-2]}
   346  				assert.ElementsMatch(ss.T(), expected, res.BlocksInternal(), "response should contain right blocks")
   347  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   348  				recipientID := args.Get(1).(flow.Identifier)
   349  				assert.Equal(ss.T(), originID, recipientID, "should send response to original requester")
   350  			},
   351  		)
   352  
   353  		// Rebuild sync core with a smaller max size
   354  		var err error
   355  		config := synccore.DefaultConfig()
   356  		config.MaxSize = 2
   357  		ss.e.requestHandler.core, err = synccore.New(ss.e.log, config, metrics.NewNoopCollector())
   358  		require.NoError(ss.T(), err)
   359  
   360  		err = ss.e.requestHandler.onRangeRequest(originID, req)
   361  		require.NoError(ss.T(), err, "valid range request exceeding max size should still pass")
   362  	})
   363  }
   364  
   365  func (ss *SyncSuite) TestOnBatchRequest() {
   366  
   367  	// generate origin ID and batch request
   368  	originID := unittest.IdentifierFixture()
   369  	req := &messages.BatchRequest{
   370  		Nonce:    rand.Uint64(),
   371  		BlockIDs: nil,
   372  	}
   373  
   374  	// an empty request should not lead to response
   375  	ss.T().Run("empty request", func(t *testing.T) {
   376  		req.BlockIDs = []flow.Identifier{}
   377  		err := ss.e.requestHandler.onBatchRequest(originID, req)
   378  		require.NoError(ss.T(), err, "should pass empty request")
   379  		ss.con.AssertNumberOfCalls(ss.T(), "Unicast", 0)
   380  	})
   381  
   382  	// a non-empty request for missing block ID should be a no-op
   383  	ss.T().Run("request for missing blocks", func(t *testing.T) {
   384  		req.BlockIDs = unittest.IdentifierListFixture(1)
   385  		err := ss.e.requestHandler.onBatchRequest(originID, req)
   386  		require.NoError(ss.T(), err, "should pass request for missing block")
   387  		ss.con.AssertNumberOfCalls(ss.T(), "Unicast", 0)
   388  	})
   389  
   390  	// a non-empty request for existing block IDs should send right response
   391  	ss.T().Run("request for existing blocks", func(t *testing.T) {
   392  		block := unittest.BlockFixture()
   393  		block.Header.Height = ss.head.Height - 1
   394  		req.BlockIDs = []flow.Identifier{block.ID()}
   395  		ss.blockIDs[block.ID()] = &block
   396  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Run(
   397  			func(args mock.Arguments) {
   398  				res := args.Get(0).(*messages.BlockResponse)
   399  				assert.Equal(ss.T(), &block, res.Blocks[0].ToInternal(), "response should contain right block")
   400  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   401  				recipientID := args.Get(1).(flow.Identifier)
   402  				assert.Equal(ss.T(), originID, recipientID, "response should be send to original requester")
   403  			},
   404  		).Once()
   405  		err := ss.e.requestHandler.onBatchRequest(originID, req)
   406  		require.NoError(ss.T(), err, "should pass request with valid block")
   407  	})
   408  
   409  	// a request for too many blocks should be clamped
   410  	ss.T().Run("oversized range", func(t *testing.T) {
   411  		// setup request for 5 blocks. response should contain the first 2 (MaxSize)
   412  		ss.blockIDs = make(map[flow.Identifier]*flow.Block)
   413  		req.BlockIDs = make([]flow.Identifier, 5)
   414  		for i := 0; i < len(req.BlockIDs); i++ {
   415  			b := unittest.BlockFixture()
   416  			b.Header.Height = ss.head.Height - uint64(i)
   417  			req.BlockIDs[i] = b.ID()
   418  			ss.blockIDs[b.ID()] = &b
   419  		}
   420  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil).Run(
   421  			func(args mock.Arguments) {
   422  				res := args.Get(0).(*messages.BlockResponse)
   423  				assert.ElementsMatch(ss.T(), []*flow.Block{ss.blockIDs[req.BlockIDs[0]], ss.blockIDs[req.BlockIDs[1]]}, res.BlocksInternal(), "response should contain right block")
   424  				assert.Equal(ss.T(), req.Nonce, res.Nonce, "response should contain request nonce")
   425  				recipientID := args.Get(1).(flow.Identifier)
   426  				assert.Equal(ss.T(), originID, recipientID, "response should be send to original requester")
   427  			},
   428  		)
   429  
   430  		// Rebuild sync core with a smaller max size
   431  		var err error
   432  		config := synccore.DefaultConfig()
   433  		config.MaxSize = 2
   434  		ss.e.requestHandler.core, err = synccore.New(ss.e.log, config, metrics.NewNoopCollector())
   435  		require.NoError(ss.T(), err)
   436  
   437  		err = ss.e.requestHandler.onBatchRequest(originID, req)
   438  		require.NoError(ss.T(), err, "valid batch request exceeding max size should still pass")
   439  	})
   440  }
   441  
   442  func (ss *SyncSuite) TestOnBlockResponse() {
   443  
   444  	// generate origin and block response
   445  	originID := unittest.IdentifierFixture()
   446  	res := &messages.BlockResponse{
   447  		Nonce:  rand.Uint64(),
   448  		Blocks: []messages.UntrustedBlock{},
   449  	}
   450  
   451  	// add one block that should be processed
   452  	processable := unittest.BlockFixture()
   453  	ss.core.On("HandleBlock", processable.Header).Return(true)
   454  	res.Blocks = append(res.Blocks, messages.UntrustedBlockFromInternal(&processable))
   455  
   456  	// add one block that should not be processed
   457  	unprocessable := unittest.BlockFixture()
   458  	ss.core.On("HandleBlock", unprocessable.Header).Return(false)
   459  	res.Blocks = append(res.Blocks, messages.UntrustedBlockFromInternal(&unprocessable))
   460  
   461  	ss.comp.On("SubmitLocal", mock.Anything).Run(func(args mock.Arguments) {
   462  		res := args.Get(0).(*events.SyncedBlock)
   463  		ss.Assert().Equal(&processable, res.Block)
   464  		ss.Assert().Equal(originID, res.OriginID)
   465  	},
   466  	)
   467  
   468  	ss.e.onBlockResponse(originID, res)
   469  	ss.comp.AssertExpectations(ss.T())
   470  	ss.core.AssertExpectations(ss.T())
   471  }
   472  
   473  func (ss *SyncSuite) TestPollHeight() {
   474  
   475  	// check that we send to three nodes from our total list
   476  	others := ss.participants.Filter(filter.HasNodeID(ss.participants[1:].NodeIDs()...))
   477  	ss.con.On("Multicast", mock.Anything, synccore.DefaultPollNodes, others[0].NodeID, others[1].NodeID).Return(nil).Run(
   478  		func(args mock.Arguments) {
   479  			req := args.Get(0).(*messages.SyncRequest)
   480  			require.Equal(ss.T(), ss.head.Height, req.Height, "request should contain finalized height")
   481  		},
   482  	)
   483  	ss.e.pollHeight()
   484  	ss.con.AssertExpectations(ss.T())
   485  }
   486  
   487  func (ss *SyncSuite) TestSendRequests() {
   488  
   489  	ranges := unittest.RangeListFixture(1)
   490  	batches := unittest.BatchListFixture(1)
   491  
   492  	// should submit and mark requested all ranges
   493  	ss.con.On("Multicast", mock.AnythingOfType("*messages.RangeRequest"), synccore.DefaultBlockRequestNodes, mock.Anything, mock.Anything).Return(nil).Run(
   494  		func(args mock.Arguments) {
   495  			req := args.Get(0).(*messages.RangeRequest)
   496  			ss.Assert().Equal(ranges[0].From, req.FromHeight)
   497  			ss.Assert().Equal(ranges[0].To, req.ToHeight)
   498  		},
   499  	)
   500  	ss.core.On("RangeRequested", ranges[0])
   501  
   502  	// should submit and mark requested all batches
   503  	ss.con.On("Multicast", mock.AnythingOfType("*messages.BatchRequest"), synccore.DefaultBlockRequestNodes, mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(
   504  		func(args mock.Arguments) {
   505  			req := args.Get(0).(*messages.BatchRequest)
   506  			ss.Assert().Equal(batches[0].BlockIDs, req.BlockIDs)
   507  		},
   508  	)
   509  	ss.core.On("BatchRequested", batches[0])
   510  
   511  	// exclude my node ID
   512  	ss.e.sendRequests(ss.participants[1:].NodeIDs(), ranges, batches)
   513  	ss.con.AssertExpectations(ss.T())
   514  }
   515  
   516  // test a synchronization engine can be started and stopped
   517  func (ss *SyncSuite) TestStartStop() {
   518  	unittest.AssertReturnsBefore(ss.T(), func() {
   519  		<-ss.e.Ready()
   520  		<-ss.e.Done()
   521  	}, time.Second)
   522  }
   523  
   524  // TestProcessingMultipleItems tests that items are processed in async way
   525  func (ss *SyncSuite) TestProcessingMultipleItems() {
   526  	<-ss.e.Ready()
   527  
   528  	originID := unittest.IdentifierFixture()
   529  	for i := 0; i < 5; i++ {
   530  		msg := &messages.SyncResponse{
   531  			Nonce:  uint64(i),
   532  			Height: uint64(1000 + i),
   533  		}
   534  		ss.core.On("HandleHeight", mock.Anything, msg.Height).Once()
   535  		require.NoError(ss.T(), ss.e.Process(channels.SyncCommittee, originID, msg))
   536  	}
   537  
   538  	finalHeight := ss.head.Height
   539  	for i := 0; i < 5; i++ {
   540  		msg := &messages.SyncRequest{
   541  			Nonce:  uint64(i),
   542  			Height: finalHeight - 100,
   543  		}
   544  
   545  		originID := unittest.IdentifierFixture()
   546  		ss.core.On("WithinTolerance", mock.Anything, mock.Anything).Return(false)
   547  		ss.core.On("HandleHeight", mock.Anything, msg.Height).Once()
   548  		ss.con.On("Unicast", mock.Anything, mock.Anything).Return(nil)
   549  
   550  		require.NoError(ss.T(), ss.e.Process(channels.SyncCommittee, originID, msg))
   551  	}
   552  
   553  	// give at least some time to process items
   554  	time.Sleep(time.Millisecond * 100)
   555  
   556  	ss.core.AssertExpectations(ss.T())
   557  }
   558  
   559  // TestOnFinalizedBlock tests that when new finalized block is discovered engine updates cached variables
   560  // to latest state
   561  func (ss *SyncSuite) TestOnFinalizedBlock() {
   562  	finalizedBlock := unittest.BlockHeaderWithParentFixture(ss.head)
   563  	// change head
   564  	ss.head = finalizedBlock
   565  
   566  	err := ss.e.finalizedHeader.updateHeader()
   567  	require.NoError(ss.T(), err)
   568  	actualHeader := ss.e.finalizedHeader.Get()
   569  	require.ElementsMatch(ss.T(), ss.e.participantsProvider.Identifiers(), ss.participants[1:].NodeIDs())
   570  	require.Equal(ss.T(), actualHeader, finalizedBlock)
   571  }
   572  
   573  // TestProcessUnsupportedMessageType tests that Process and ProcessLocal correctly handle a case where invalid message type
   574  // was submitted from network layer.
   575  func (ss *SyncSuite) TestProcessUnsupportedMessageType() {
   576  	invalidEvent := uint64(42)
   577  	engines := []netint.MessageProcessor{ss.e, ss.e.requestHandler}
   578  	for _, e := range engines {
   579  		err := e.Process("ch", unittest.IdentifierFixture(), invalidEvent)
   580  		// shouldn't result in error since byzantine inputs are expected
   581  		require.NoError(ss.T(), err)
   582  	}
   583  
   584  	// in case of local processing error cannot be consumed since all inputs are trusted
   585  	err := ss.e.ProcessLocal(invalidEvent)
   586  	require.Error(ss.T(), err)
   587  	require.True(ss.T(), engine.IsIncompatibleInputTypeError(err))
   588  }