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 }