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  }