github.com/onflow/flow-go@v0.33.17/network/p2p/tracer/gossipSubMeshTracer_test.go (about)

     1  package tracer_test
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/rs/zerolog"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"go.uber.org/atomic"
    13  
    14  	"github.com/onflow/flow-go/config"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/module/irrecoverable"
    17  	"github.com/onflow/flow-go/module/metrics"
    18  	mockmodule "github.com/onflow/flow-go/module/mock"
    19  	"github.com/onflow/flow-go/network/channels"
    20  	"github.com/onflow/flow-go/network/p2p"
    21  	p2ptest "github.com/onflow/flow-go/network/p2p/test"
    22  	"github.com/onflow/flow-go/network/p2p/tracer"
    23  	validator "github.com/onflow/flow-go/network/validator/pubsub"
    24  	"github.com/onflow/flow-go/utils/unittest"
    25  )
    26  
    27  // TestGossipSubMeshTracer tests the GossipSub mesh tracer. It creates four nodes, one with a mesh tracer and three without.
    28  // It then subscribes the nodes to the same topic and checks that the mesh tracer is able to detect the event of
    29  // a node joining the mesh.
    30  // It then checks that the mesh tracer is able to detect the event of a node leaving the mesh.
    31  // One of the nodes is running with an unknown peer id, which the identity provider is mocked to return an error and
    32  // the mesh tracer should log a warning message.
    33  func TestGossipSubMeshTracer(t *testing.T) {
    34  	defaultConfig, err := config.DefaultConfig()
    35  	require.NoError(t, err)
    36  	ctx, cancel := context.WithCancel(context.Background())
    37  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
    38  	sporkId := unittest.IdentifierFixture()
    39  	idProvider := mockmodule.NewIdentityProvider(t)
    40  	defer cancel()
    41  
    42  	channel1 := channels.PushBlocks
    43  	topic1 := channels.TopicFromChannel(channel1, sporkId)
    44  	channel2 := channels.PushReceipts
    45  	topic2 := channels.TopicFromChannel(channel2, sporkId)
    46  
    47  	loggerCycle := atomic.NewInt32(0)
    48  	warnLoggerCycle := atomic.NewInt32(0)
    49  
    50  	// logger hook to count the number of times the meshTracer logs at the interval specified.
    51  	hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) {
    52  		if level == zerolog.DebugLevel {
    53  			if message == tracer.MeshLogIntervalMsg {
    54  				loggerCycle.Inc()
    55  			}
    56  		}
    57  
    58  		if level == zerolog.WarnLevel {
    59  			if message == tracer.MeshLogIntervalWarnMsg {
    60  				warnLoggerCycle.Inc()
    61  			}
    62  		}
    63  	})
    64  	logger := zerolog.New(os.Stdout).Level(zerolog.DebugLevel).Hook(hook)
    65  
    66  	// creates one node with a gossipsub mesh meshTracer, and the other nodes without a gossipsub mesh meshTracer.
    67  	// we only need one node with a meshTracer to test the meshTracer.
    68  	// meshTracer logs at 1 second intervals for sake of testing.
    69  	// creates one node with a gossipsub mesh meshTracer, and the other nodes without a gossipsub mesh meshTracer.
    70  	// we only need one node with a meshTracer to test the meshTracer.
    71  	// meshTracer logs at 1 second intervals for sake of testing.
    72  	collector := newLocalMeshTracerMetricsCollector(t)
    73  	// set the meshTracer to log at 1 second intervals for sake of testing.
    74  	defaultConfig.NetworkConfig.GossipSub.RpcTracer.LocalMeshLogInterval = 1 * time.Second
    75  	// disables peer scoring for sake of testing; so that unknown peers are not penalized and could be detected by the meshTracer.
    76  	defaultConfig.NetworkConfig.GossipSub.PeerScoringEnabled = false
    77  	tracerNode, tracerId := p2ptest.NodeFixture(
    78  		t,
    79  		sporkId,
    80  		t.Name(),
    81  		idProvider,
    82  		p2ptest.WithLogger(logger),
    83  		p2ptest.OverrideFlowConfig(defaultConfig),
    84  		p2ptest.WithMetricsCollector(collector),
    85  		p2ptest.WithRole(flow.RoleConsensus))
    86  
    87  	idProvider.On("ByPeerID", tracerNode.ID()).Return(&tracerId, true).Maybe()
    88  
    89  	otherNode1, otherId1 := p2ptest.NodeFixture(
    90  		t,
    91  		sporkId,
    92  		t.Name(),
    93  		idProvider,
    94  		p2ptest.WithRole(flow.RoleConsensus))
    95  	idProvider.On("ByPeerID", otherNode1.ID()).Return(&otherId1, true).Maybe()
    96  
    97  	otherNode2, otherId2 := p2ptest.NodeFixture(
    98  		t,
    99  		sporkId,
   100  		t.Name(),
   101  		idProvider,
   102  		p2ptest.WithRole(flow.RoleConsensus))
   103  	idProvider.On("ByPeerID", otherNode2.ID()).Return(&otherId2, true).Maybe()
   104  
   105  	// create a node that does not have a valid flow identity to test whether mesh tracer logs a warning.
   106  	unknownNode, unknownId := p2ptest.NodeFixture(
   107  		t,
   108  		sporkId,
   109  		t.Name(),
   110  		idProvider,
   111  		p2ptest.WithRole(flow.RoleConsensus))
   112  	idProvider.On("ByPeerID", unknownNode.ID()).Return(nil, false).Maybe()
   113  
   114  	nodes := []p2p.LibP2PNode{tracerNode, otherNode1, otherNode2, unknownNode}
   115  	ids := flow.IdentityList{&tracerId, &otherId1, &otherId2, &unknownId}
   116  
   117  	p2ptest.RegisterPeerProviders(t, nodes)
   118  	p2ptest.StartNodes(t, signalerCtx, nodes)
   119  	defer p2ptest.StopNodes(t, nodes, cancel)
   120  
   121  	p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids)
   122  
   123  	// all nodes subscribe to topic1
   124  	// for topic 1 expect the meshTracer to be notified of the local mesh size being 1, 2, and 3 (when unknownNode, otherNode1, and otherNode2 join the mesh).
   125  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 1).Twice() // 1 for the first subscription, 1 for the first leave
   126  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 2).Twice() // 1 for the second subscription, 1 for the second leave
   127  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 3).Once()  // 3 for the third subscription.
   128  
   129  	for _, node := range nodes {
   130  		_, err := node.Subscribe(
   131  			topic1,
   132  			validator.TopicValidator(
   133  				unittest.Logger(),
   134  				unittest.AllowAllPeerFilter()))
   135  		require.NoError(t, err)
   136  	}
   137  
   138  	// the tracerNode and otherNode1 subscribe to topic2
   139  	// for topic 2 expect the meshTracer to be notified of the local mesh size being 1 (when otherNode1 join the mesh).
   140  	collector.l.On("OnLocalMeshSizeUpdated", topic2.String(), 1).Once()
   141  
   142  	for _, node := range []p2p.LibP2PNode{tracerNode, otherNode1} {
   143  		_, err := node.Subscribe(
   144  			topic2,
   145  			validator.TopicValidator(
   146  				unittest.Logger(),
   147  				unittest.AllowAllPeerFilter()))
   148  		require.NoError(t, err)
   149  	}
   150  
   151  	// eventually, the meshTracer should have the other nodes in its mesh.
   152  	assert.Eventually(t, func() bool {
   153  		topic1MeshSize := 0
   154  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) {
   155  			if peerId == otherNode1.ID() || peerId == otherNode2.ID() {
   156  				topic1MeshSize++
   157  			}
   158  		}
   159  
   160  		topic2MeshSize := 0
   161  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) {
   162  			if peerId == otherNode1.ID() {
   163  				topic2MeshSize++
   164  			}
   165  		}
   166  
   167  		return topic1MeshSize == 2 && topic2MeshSize == 1
   168  	}, 2*time.Second, 10*time.Millisecond)
   169  
   170  	// eventually, we expect the meshTracer to log the mesh at least once.
   171  	assert.Eventually(t, func() bool {
   172  		return loggerCycle.Load() > 0 && warnLoggerCycle.Load() > 0
   173  	}, 2*time.Second, 10*time.Millisecond)
   174  
   175  	// expect the meshTracer to be notified of the local mesh size being (when all nodes leave the mesh).
   176  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 0).Once()
   177  
   178  	// all nodes except the tracerNode unsubscribe from the topic1, which triggers sending a PRUNE to the tracerNode for each unsubscription.
   179  	// We expect the tracerNode to remove the otherNode1, otherNode2, and unknownNode from its mesh.
   180  	require.NoError(t, otherNode1.Unsubscribe(topic1))
   181  	require.NoError(t, otherNode2.Unsubscribe(topic1))
   182  	require.NoError(t, unknownNode.Unsubscribe(topic1))
   183  
   184  	assert.Eventually(t, func() bool {
   185  		// eventually, the tracerNode should not have the other node in its mesh for topic1.
   186  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) {
   187  			if peerId == otherNode1.ID() || peerId == otherNode2.ID() || peerId == unknownNode.ID() {
   188  				return false
   189  			}
   190  		}
   191  
   192  		// but the tracerNode should still have the otherNode1 in its mesh for topic2.
   193  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) {
   194  			if peerId != otherNode1.ID() {
   195  				return false
   196  			}
   197  		}
   198  		return true
   199  	}, 2*time.Second, 10*time.Millisecond)
   200  }
   201  
   202  // localMeshTracerMetricsCollector is a mock metrics that can be mocked for GossipSubLocalMeshMetrics while acting as a NoopCollector for other metrics.
   203  type localMeshTracerMetricsCollector struct {
   204  	*metrics.NoopCollector
   205  	l *mockmodule.LocalGossipSubRouterMetrics
   206  }
   207  
   208  func newLocalMeshTracerMetricsCollector(t *testing.T) *localMeshTracerMetricsCollector {
   209  	return &localMeshTracerMetricsCollector{
   210  		l:             mockmodule.NewLocalGossipSubRouterMetrics(t),
   211  		NoopCollector: metrics.NewNoopCollector(),
   212  	}
   213  }
   214  
   215  func (c *localMeshTracerMetricsCollector) OnLocalMeshSizeUpdated(topic string, size int) {
   216  	// calls the mock method to assert the metrics.
   217  	c.l.OnLocalMeshSizeUpdated(topic, size)
   218  }