github.com/koko1123/flow-go-1@v0.29.6/network/test/meshengine_test.go (about) 1 package test 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "os" 8 "strconv" 9 "strings" 10 "sync" 11 "testing" 12 "time" 13 14 "github.com/koko1123/flow-go-1/network/p2p" 15 16 "github.com/ipfs/go-log" 17 pubsub "github.com/libp2p/go-libp2p-pubsub" 18 "github.com/rs/zerolog" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "github.com/stretchr/testify/suite" 22 23 "github.com/koko1123/flow-go-1/network/mocknetwork" 24 "github.com/koko1123/flow-go-1/network/p2p/middleware" 25 "github.com/koko1123/flow-go-1/network/p2p/p2pnode" 26 27 "github.com/koko1123/flow-go-1/model/flow" 28 "github.com/koko1123/flow-go-1/model/flow/filter" 29 "github.com/koko1123/flow-go-1/model/libp2p/message" 30 "github.com/koko1123/flow-go-1/module/irrecoverable" 31 "github.com/koko1123/flow-go-1/module/observable" 32 "github.com/koko1123/flow-go-1/network" 33 "github.com/koko1123/flow-go-1/network/channels" 34 "github.com/koko1123/flow-go-1/network/internal/testutils" 35 "github.com/koko1123/flow-go-1/utils/unittest" 36 ) 37 38 // MeshEngineTestSuite evaluates the message delivery functionality for the overlay 39 // of engines over a complete graph 40 type MeshEngineTestSuite struct { 41 suite.Suite 42 testutils.ConduitWrapper // used as a wrapper around conduit methods 43 nets []network.Network // used to keep track of the networks 44 mws []network.Middleware // used to keep track of the middlewares 45 ids flow.IdentityList // used to keep track of the identifiers associated with networks 46 obs chan string // used to keep track of Protect events tagged by pubsub messages 47 cancel context.CancelFunc 48 } 49 50 // TestMeshNetTestSuite runs all tests in this test suit 51 func TestMeshNetTestSuite(t *testing.T) { 52 suite.Run(t, new(MeshEngineTestSuite)) 53 } 54 55 // SetupTest is executed prior to each test in this test suit 56 // it creates and initializes a set of network instances 57 func (suite *MeshEngineTestSuite) SetupTest() { 58 // defines total number of nodes in our network (minimum 3 needed to use 1-k messaging) 59 const count = 10 60 logger := zerolog.New(os.Stderr).Level(zerolog.ErrorLevel) 61 log.SetAllLoggers(log.LevelError) 62 63 // set up a channel to receive pubsub tags from connManagers of the nodes 64 var obs []observable.Observable 65 peerChannel := make(chan string) 66 ob := tagsObserver{ 67 tags: peerChannel, 68 log: logger, 69 } 70 71 ctx, cancel := context.WithCancel(context.Background()) 72 suite.cancel = cancel 73 74 signalerCtx := irrecoverable.NewMockSignalerContext(suite.T(), ctx) 75 76 var nodes []p2p.LibP2PNode 77 suite.ids, nodes, suite.mws, suite.nets, obs = testutils.GenerateIDsMiddlewaresNetworks( 78 suite.T(), 79 count, 80 logger, 81 unittest.NetworkCodec(), 82 mocknetwork.NewViolationsConsumer(suite.T()), 83 testutils.WithIdentityOpts(unittest.WithAllRoles()), 84 ) 85 86 testutils.StartNodesAndNetworks(signalerCtx, suite.T(), nodes, suite.nets, 100*time.Millisecond) 87 88 for _, observableConnMgr := range obs { 89 observableConnMgr.Subscribe(&ob) 90 } 91 suite.obs = peerChannel 92 } 93 94 // TearDownTest closes the networks within a specified timeout 95 func (suite *MeshEngineTestSuite) TearDownTest() { 96 suite.cancel() 97 testutils.StopComponents(suite.T(), suite.nets, 3*time.Second) 98 testutils.StopComponents(suite.T(), suite.mws, 3*time.Second) 99 } 100 101 // TestAllToAll_Publish evaluates the network of mesh engines against allToAllScenario scenario. 102 // Network instances during this test use their Publish method to disseminate messages. 103 func (suite *MeshEngineTestSuite) TestAllToAll_Publish() { 104 suite.allToAllScenario(suite.Publish) 105 } 106 107 // TestAllToAll_Multicast evaluates the network of mesh engines against allToAllScenario scenario. 108 // Network instances during this test use their Multicast method to disseminate messages. 109 func (suite *MeshEngineTestSuite) TestAllToAll_Multicast() { 110 suite.allToAllScenario(suite.Multicast) 111 } 112 113 // TestAllToAll_Unicast evaluates the network of mesh engines against allToAllScenario scenario. 114 // Network instances during this test use their Unicast method to disseminate messages. 115 func (suite *MeshEngineTestSuite) TestAllToAll_Unicast() { 116 suite.allToAllScenario(suite.Unicast) 117 } 118 119 // TestTargetedValidators_Unicast tests if only the intended recipients in a 1-k messaging actually receive the message. 120 // The messages are disseminated through the Unicast method of conduits. 121 func (suite *MeshEngineTestSuite) TestTargetedValidators_Unicast() { 122 suite.targetValidatorScenario(suite.Unicast) 123 } 124 125 // TestTargetedValidators_Multicast tests if only the intended recipients in a 1-k messaging actually receive the 126 // message. 127 // The messages are disseminated through the Multicast method of conduits. 128 func (suite *MeshEngineTestSuite) TestTargetedValidators_Multicast() { 129 suite.targetValidatorScenario(suite.Multicast) 130 } 131 132 // TestTargetedValidators_Publish tests if only the intended recipients in a 1-k messaging actually receive the message. 133 // The messages are disseminated through the Multicast method of conduits. 134 func (suite *MeshEngineTestSuite) TestTargetedValidators_Publish() { 135 suite.targetValidatorScenario(suite.Publish) 136 } 137 138 // TestMaxMessageSize_Unicast evaluates the messageSizeScenario scenario using 139 // the Unicast method of conduits. 140 func (suite *MeshEngineTestSuite) TestMaxMessageSize_Unicast() { 141 suite.messageSizeScenario(suite.Unicast, middleware.DefaultMaxUnicastMsgSize) 142 } 143 144 // TestMaxMessageSize_Multicast evaluates the messageSizeScenario scenario using 145 // the Multicast method of conduits. 146 func (suite *MeshEngineTestSuite) TestMaxMessageSize_Multicast() { 147 suite.messageSizeScenario(suite.Multicast, p2pnode.DefaultMaxPubSubMsgSize) 148 } 149 150 // TestMaxMessageSize_Publish evaluates the messageSizeScenario scenario using the 151 // Publish method of conduits. 152 func (suite *MeshEngineTestSuite) TestMaxMessageSize_Publish() { 153 suite.messageSizeScenario(suite.Publish, p2pnode.DefaultMaxPubSubMsgSize) 154 } 155 156 // TestUnregister_Publish tests that an engine cannot send any message using Publish 157 // or receive any messages after the conduit is closed 158 func (suite *MeshEngineTestSuite) TestUnregister_Publish() { 159 suite.conduitCloseScenario(suite.Publish) 160 } 161 162 // TestUnregister_Publish tests that an engine cannot send any message using Multicast 163 // or receive any messages after the conduit is closed 164 func (suite *MeshEngineTestSuite) TestUnregister_Multicast() { 165 suite.conduitCloseScenario(suite.Multicast) 166 } 167 168 // TestUnregister_Publish tests that an engine cannot send any message using Unicast 169 // or receive any messages after the conduit is closed 170 func (suite *MeshEngineTestSuite) TestUnregister_Unicast() { 171 suite.conduitCloseScenario(suite.Unicast) 172 } 173 174 // allToAllScenario creates a complete mesh of the engines 175 // each engine x then sends a "hello from node x" to other engines 176 // it evaluates the correctness of message delivery as well as content of the message 177 func (suite *MeshEngineTestSuite) allToAllScenario(send testutils.ConduitSendWrapperFunc) { 178 // allows nodes to find each other in case of Mulitcast and Publish 179 testutils.OptionalSleep(send) 180 181 // creating engines 182 count := len(suite.nets) 183 engs := make([]*testutils.MeshEngine, 0) 184 wg := sync.WaitGroup{} 185 186 // logs[i][j] keeps the message that node i sends to node j 187 logs := make(map[int][]string) 188 for i := range suite.nets { 189 eng := testutils.NewMeshEngine(suite.Suite.T(), suite.nets[i], count-1, channels.TestNetworkChannel) 190 engs = append(engs, eng) 191 logs[i] = make([]string, 0) 192 } 193 194 // allow nodes to heartbeat and discover each other 195 // each node will register ~D protect messages, where D is the default out-degree 196 for i := 0; i < pubsub.GossipSubD*count; i++ { 197 select { 198 case <-suite.obs: 199 case <-time.After(8 * time.Second): 200 assert.FailNow(suite.T(), "could not receive pubsub tag indicating mesh formed") 201 } 202 } 203 204 // Each node broadcasting a message to all others 205 for i := range suite.nets { 206 event := &message.TestMessage{ 207 Text: fmt.Sprintf("hello from node %v", i), 208 } 209 210 // others keeps the identifier of all nodes except ith node 211 others := suite.ids.Filter(filter.Not(filter.HasNodeID(suite.ids[i].NodeID))).NodeIDs() 212 require.NoError(suite.Suite.T(), send(event, engs[i].Con, others...)) 213 wg.Add(count - 1) 214 } 215 216 // fires a goroutine for each engine that listens to incoming messages 217 for i := range suite.nets { 218 go func(e *testutils.MeshEngine) { 219 for x := 0; x < count-1; x++ { 220 <-e.Received 221 wg.Done() 222 } 223 }(engs[i]) 224 } 225 226 unittest.AssertReturnsBefore(suite.Suite.T(), wg.Wait, 30*time.Second) 227 228 // evaluates that all messages are received 229 for index, e := range engs { 230 // confirms the number of received messages at each node 231 if len(e.Event) != (count - 1) { 232 assert.Fail(suite.Suite.T(), 233 fmt.Sprintf("Message reception mismatch at node %v. Expected: %v, Got: %v", index, count-1, len(e.Event))) 234 } 235 236 for i := 0; i < count-1; i++ { 237 assertChannelReceived(suite.T(), e, channels.TestNetworkChannel) 238 } 239 240 // extracts failed messages 241 receivedIndices, err := extractSenderID(count, e.Event, "hello from node") 242 require.NoError(suite.Suite.T(), err) 243 244 for j := 0; j < count; j++ { 245 // evaluates self-gossip 246 if j == index { 247 assert.False(suite.Suite.T(), (receivedIndices)[index], fmt.Sprintf("self gossiped for node %v detected", index)) 248 } 249 // evaluates content 250 if !(receivedIndices)[j] { 251 assert.False(suite.Suite.T(), (receivedIndices)[index], 252 fmt.Sprintf("Message not found in node #%v's messages. Expected: Message from node %v. Got: No message", index, j)) 253 } 254 } 255 } 256 } 257 258 // targetValidatorScenario sends a single message from last node to the first half of the nodes 259 // based on identifiers list. 260 // It then verifies that only the intended recipients receive the message. 261 // Message dissemination is done using the send wrapper of conduit. 262 func (suite *MeshEngineTestSuite) targetValidatorScenario(send testutils.ConduitSendWrapperFunc) { 263 // creating engines 264 count := len(suite.nets) 265 engs := make([]*testutils.MeshEngine, 0) 266 wg := sync.WaitGroup{} 267 268 for i := range suite.nets { 269 eng := testutils.NewMeshEngine(suite.Suite.T(), suite.nets[i], count-1, channels.TestNetworkChannel) 270 engs = append(engs, eng) 271 } 272 273 // allow nodes to heartbeat and discover each other 274 // each node will register ~D protect messages, where D is the default out-degree 275 for i := 0; i < pubsub.GossipSubD*count; i++ { 276 select { 277 case <-suite.obs: 278 case <-time.After(2 * time.Second): 279 assert.FailNow(suite.T(), "could not receive pubsub tag indicating mesh formed") 280 } 281 } 282 283 // choose half of the nodes as target 284 allIds := suite.ids.NodeIDs() 285 var targets []flow.Identifier 286 // create a target list of half of the nodes 287 for i := 0; i < len(allIds)/2; i++ { 288 targets = append(targets, allIds[i]) 289 } 290 291 // node 0 broadcasting a message to all targets 292 event := &message.TestMessage{ 293 Text: "hello from node 0", 294 } 295 require.NoError(suite.Suite.T(), send(event, engs[len(engs)-1].Con, targets...)) 296 297 // fires a goroutine for all engines to listens for the incoming message 298 for i := 0; i < len(allIds)/2; i++ { 299 wg.Add(1) 300 go func(e *testutils.MeshEngine) { 301 <-e.Received 302 wg.Done() 303 }(engs[i]) 304 } 305 306 unittest.AssertReturnsBefore(suite.T(), wg.Wait, 10*time.Second) 307 308 // evaluates that all messages are received 309 for index, e := range engs { 310 if index < len(engs)/2 { 311 assert.Len(suite.Suite.T(), e.Event, 1, fmt.Sprintf("message not received %v", index)) 312 assertChannelReceived(suite.T(), e, channels.TestNetworkChannel) 313 } else { 314 assert.Len(suite.Suite.T(), e.Event, 0, fmt.Sprintf("message received when none was expected %v", index)) 315 } 316 } 317 } 318 319 // messageSizeScenario provides a scenario to check if a message of maximum permissible size can be sent 320 // successfully. 321 // It broadcasts a message from the first node to all the nodes in the identifiers list using send wrapper function. 322 func (suite *MeshEngineTestSuite) messageSizeScenario(send testutils.ConduitSendWrapperFunc, size uint) { 323 // creating engines 324 count := len(suite.nets) 325 engs := make([]*testutils.MeshEngine, 0) 326 wg := sync.WaitGroup{} 327 328 for i := range suite.nets { 329 eng := testutils.NewMeshEngine(suite.Suite.T(), suite.nets[i], count-1, channels.TestNetworkChannel) 330 engs = append(engs, eng) 331 } 332 333 // allow nodes to heartbeat and discover each other 334 // each node will register ~D protect messages per mesh setup, where D is the default out-degree 335 for i := 0; i < pubsub.GossipSubD*count; i++ { 336 select { 337 case <-suite.obs: 338 case <-time.After(8 * time.Second): 339 assert.FailNow(suite.T(), "could not receive pubsub tag indicating mesh formed") 340 } 341 } 342 // others keeps the identifier of all nodes except node that is sender. 343 others := suite.ids.Filter(filter.Not(filter.HasNodeID(suite.ids[0].NodeID))).NodeIDs() 344 345 // generates and sends an event of custom size to the network 346 payload := testutils.NetworkPayloadFixture(suite.T(), size) 347 event := &message.TestMessage{ 348 Text: string(payload), 349 } 350 351 require.NoError(suite.T(), send(event, engs[0].Con, others...)) 352 353 // fires a goroutine for all engines (except sender) to listen for the incoming message 354 for _, eng := range engs[1:] { 355 wg.Add(1) 356 go func(e *testutils.MeshEngine) { 357 <-e.Received 358 wg.Done() 359 }(eng) 360 } 361 362 unittest.AssertReturnsBefore(suite.Suite.T(), wg.Wait, 30*time.Second) 363 364 // evaluates that all messages are received 365 for index, e := range engs[1:] { 366 assert.Len(suite.Suite.T(), e.Event, 1, "message not received by engine %d", index+1) 367 assertChannelReceived(suite.T(), e, channels.TestNetworkChannel) 368 } 369 } 370 371 // conduitCloseScenario tests after a Conduit is closed, an engine cannot send or receive a message for that channel. 372 func (suite *MeshEngineTestSuite) conduitCloseScenario(send testutils.ConduitSendWrapperFunc) { 373 374 testutils.OptionalSleep(send) 375 376 // creating engines 377 count := len(suite.nets) 378 engs := make([]*testutils.MeshEngine, 0) 379 wg := sync.WaitGroup{} 380 381 for i := range suite.nets { 382 eng := testutils.NewMeshEngine(suite.Suite.T(), suite.nets[i], count-1, channels.TestNetworkChannel) 383 engs = append(engs, eng) 384 } 385 386 // allow nodes to heartbeat and discover each other 387 // each node will register ~D protect messages, where D is the default out-degree 388 for i := 0; i < pubsub.GossipSubD*count; i++ { 389 select { 390 case <-suite.obs: 391 case <-time.After(2 * time.Second): 392 assert.FailNow(suite.T(), "could not receive pubsub tag indicating mesh formed") 393 } 394 } 395 396 // unregister a random engine from the test topic by calling close on it's conduit 397 unregisterIndex := rand.Intn(count) 398 err := engs[unregisterIndex].Con.Close() 399 assert.NoError(suite.T(), err) 400 401 // waits enough for peer manager to unsubscribe the node from the topic 402 // while libp2p is unsubscribing the node, the topology gets unstable 403 // and connections to the node may be refused (although very unlikely). 404 time.Sleep(2 * time.Second) 405 406 // each node attempts to broadcast a message to all others 407 for i := range suite.nets { 408 event := &message.TestMessage{ 409 Text: fmt.Sprintf("hello from node %v", i), 410 } 411 412 // others keeps the identifier of all nodes except ith node and the node that unregistered from the topic. 413 // nodes without valid topic registration for a channel will reject messages on that channel via unicast. 414 others := suite.ids.Filter(filter.Not(filter.HasNodeID(suite.ids[i].NodeID, suite.ids[unregisterIndex].NodeID))).NodeIDs() 415 416 if i == unregisterIndex { 417 // assert that unsubscribed engine cannot publish on that topic 418 require.Error(suite.Suite.T(), send(event, engs[i].Con, others...)) 419 continue 420 } 421 422 require.NoError(suite.Suite.T(), send(event, engs[i].Con, others...)) 423 } 424 425 // fire a goroutine to listen for incoming messages for each engine except for the one which unregistered 426 for i := range suite.nets { 427 if i == unregisterIndex { 428 continue 429 } 430 wg.Add(1) 431 go func(e *testutils.MeshEngine) { 432 expectedMsgCnt := count - 2 // count less self and unsubscribed engine 433 for x := 0; x < expectedMsgCnt; x++ { 434 <-e.Received 435 } 436 wg.Done() 437 }(engs[i]) 438 } 439 440 // assert every one except the unsubscribed engine received the message 441 unittest.AssertReturnsBefore(suite.Suite.T(), wg.Wait, 2*time.Second) 442 443 // assert that the unregistered engine did not receive the message 444 unregisteredEng := engs[unregisterIndex] 445 assert.Emptyf(suite.T(), unregisteredEng.Received, "unregistered engine received the topic message") 446 } 447 448 // assertChannelReceived asserts that the given channel was received on the given engine 449 func assertChannelReceived(t *testing.T, e *testutils.MeshEngine, channel channels.Channel) { 450 unittest.AssertReturnsBefore(t, func() { 451 assert.Equal(t, channel, <-e.Channel) 452 }, 100*time.Millisecond) 453 } 454 455 // extractSenderID returns a bool array with the index i true if there is a message from node i in the provided messages. 456 // enginesNum is the number of engines 457 // events is the channel of received events 458 // expectedMsgTxt is the common prefix among all the messages that we expect to receive, for example 459 // we expect to receive "hello from node x" in this test, and then expectedMsgTxt is "hello form node" 460 func extractSenderID(enginesNum int, events chan interface{}, expectedMsgTxt string) ([]bool, error) { 461 indices := make([]bool, enginesNum) 462 expectedMsgSize := len(expectedMsgTxt) 463 for i := 0; i < enginesNum-1; i++ { 464 var event interface{} 465 select { 466 case event = <-events: 467 default: 468 continue 469 } 470 echo := event.(*message.TestMessage) 471 msg := echo.Text 472 if len(msg) < expectedMsgSize { 473 return nil, fmt.Errorf("invalid message format") 474 } 475 senderIndex := msg[expectedMsgSize:] 476 senderIndex = strings.TrimLeft(senderIndex, " ") 477 nodeID, err := strconv.Atoi(senderIndex) 478 if err != nil { 479 return nil, fmt.Errorf("could not extract the node id from: %v", msg) 480 } 481 482 if indices[nodeID] { 483 return nil, fmt.Errorf("duplicate message reception: %v", msg) 484 } 485 486 if msg == fmt.Sprintf("%s %v", expectedMsgTxt, nodeID) { 487 indices[nodeID] = true 488 } 489 } 490 return indices, nil 491 }