github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/tracer/gossipSubMeshTracer_test.go (about)

     1  package tracer_test
     2  
     3  import (
     4  	"context"
     5  	"io"
     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(io.Discard).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  	// disables rejection of RPC's from unstaked peer so that unknown peers could be detected bu the meshTracer
    78  	defaultConfig.NetworkConfig.GossipSub.RpcInspector.Validation.InspectionProcess.Inspect.RejectUnstakedPeers = false
    79  	tracerNode, tracerId := p2ptest.NodeFixture(
    80  		t,
    81  		sporkId,
    82  		t.Name(),
    83  		idProvider,
    84  		p2ptest.WithLogger(logger),
    85  		p2ptest.OverrideFlowConfig(defaultConfig),
    86  		p2ptest.WithMetricsCollector(collector),
    87  		p2ptest.WithRole(flow.RoleConsensus))
    88  
    89  	idProvider.On("ByPeerID", tracerNode.ID()).Return(&tracerId, true).Maybe()
    90  
    91  	otherNode1, otherId1 := p2ptest.NodeFixture(
    92  		t,
    93  		sporkId,
    94  		t.Name(),
    95  		idProvider,
    96  		p2ptest.WithRole(flow.RoleConsensus))
    97  	idProvider.On("ByPeerID", otherNode1.ID()).Return(&otherId1, true).Maybe()
    98  
    99  	otherNode2, otherId2 := p2ptest.NodeFixture(
   100  		t,
   101  		sporkId,
   102  		t.Name(),
   103  		idProvider,
   104  		p2ptest.WithRole(flow.RoleConsensus))
   105  	idProvider.On("ByPeerID", otherNode2.ID()).Return(&otherId2, true).Maybe()
   106  
   107  	// create a node that does not have a valid flow identity to test whether mesh tracer logs a warning.
   108  	unknownNode, unknownId := p2ptest.NodeFixture(
   109  		t,
   110  		sporkId,
   111  		t.Name(),
   112  		idProvider,
   113  		p2ptest.WithRole(flow.RoleConsensus))
   114  	idProvider.On("ByPeerID", unknownNode.ID()).Return(nil, false).Maybe()
   115  
   116  	nodes := []p2p.LibP2PNode{tracerNode, otherNode1, otherNode2, unknownNode}
   117  	ids := flow.IdentityList{&tracerId, &otherId1, &otherId2, &unknownId}
   118  
   119  	p2ptest.RegisterPeerProviders(t, nodes)
   120  	p2ptest.StartNodes(t, signalerCtx, nodes)
   121  	defer p2ptest.StopNodes(t, nodes, cancel)
   122  
   123  	p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids)
   124  
   125  	// all nodes subscribe to topic1
   126  	// 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).
   127  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 1).Twice() // 1 for the first subscription, 1 for the first leave
   128  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 2).Twice() // 1 for the second subscription, 1 for the second leave
   129  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 3).Once()  // 3 for the third subscription.
   130  
   131  	for _, node := range nodes {
   132  		_, err := node.Subscribe(
   133  			topic1,
   134  			validator.TopicValidator(
   135  				unittest.Logger(),
   136  				unittest.AllowAllPeerFilter()))
   137  		require.NoError(t, err)
   138  	}
   139  
   140  	// the tracerNode and otherNode1 subscribe to topic2
   141  	// for topic 2 expect the meshTracer to be notified of the local mesh size being 1 (when otherNode1 join the mesh).
   142  	collector.l.On("OnLocalMeshSizeUpdated", topic2.String(), 1).Once()
   143  
   144  	for _, node := range []p2p.LibP2PNode{tracerNode, otherNode1} {
   145  		_, err := node.Subscribe(
   146  			topic2,
   147  			validator.TopicValidator(
   148  				unittest.Logger(),
   149  				unittest.AllowAllPeerFilter()))
   150  		require.NoError(t, err)
   151  	}
   152  
   153  	// eventually, the meshTracer should have the other nodes in its mesh.
   154  	assert.Eventually(t, func() bool {
   155  		topic1MeshSize := 0
   156  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) {
   157  			if peerId == otherNode1.ID() || peerId == otherNode2.ID() {
   158  				topic1MeshSize++
   159  			}
   160  		}
   161  
   162  		topic2MeshSize := 0
   163  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) {
   164  			if peerId == otherNode1.ID() {
   165  				topic2MeshSize++
   166  			}
   167  		}
   168  
   169  		return topic1MeshSize == 2 && topic2MeshSize == 1
   170  	}, 2*time.Second, 10*time.Millisecond)
   171  
   172  	// eventually, we expect the meshTracer to log the mesh at least once.
   173  	assert.Eventually(t, func() bool {
   174  		return loggerCycle.Load() > 0 && warnLoggerCycle.Load() > 0
   175  	}, 2*time.Second, 10*time.Millisecond)
   176  
   177  	// expect the meshTracer to be notified of the local mesh size being (when all nodes leave the mesh).
   178  	collector.l.On("OnLocalMeshSizeUpdated", topic1.String(), 0).Once()
   179  
   180  	// all nodes except the tracerNode unsubscribe from the topic1, which triggers sending a PRUNE to the tracerNode for each unsubscription.
   181  	// We expect the tracerNode to remove the otherNode1, otherNode2, and unknownNode from its mesh.
   182  	require.NoError(t, otherNode1.Unsubscribe(topic1))
   183  	require.NoError(t, otherNode2.Unsubscribe(topic1))
   184  	require.NoError(t, unknownNode.Unsubscribe(topic1))
   185  
   186  	assert.Eventually(t, func() bool {
   187  		// eventually, the tracerNode should not have the other node in its mesh for topic1.
   188  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic1) {
   189  			if peerId == otherNode1.ID() || peerId == otherNode2.ID() || peerId == unknownNode.ID() {
   190  				return false
   191  			}
   192  		}
   193  
   194  		// but the tracerNode should still have the otherNode1 in its mesh for topic2.
   195  		for _, peerId := range tracerNode.GetLocalMeshPeers(topic2) {
   196  			if peerId != otherNode1.ID() {
   197  				return false
   198  			}
   199  		}
   200  		return true
   201  	}, 2*time.Second, 10*time.Millisecond)
   202  }
   203  
   204  // localMeshTracerMetricsCollector is a mock metrics that can be mocked for GossipSubLocalMeshMetrics while acting as a NoopCollector for other metrics.
   205  type localMeshTracerMetricsCollector struct {
   206  	*metrics.NoopCollector
   207  	l *mockmodule.LocalGossipSubRouterMetrics
   208  }
   209  
   210  func newLocalMeshTracerMetricsCollector(t *testing.T) *localMeshTracerMetricsCollector {
   211  	return &localMeshTracerMetricsCollector{
   212  		l:             mockmodule.NewLocalGossipSubRouterMetrics(t),
   213  		NoopCollector: metrics.NewNoopCollector(),
   214  	}
   215  }
   216  
   217  func (c *localMeshTracerMetricsCollector) OnLocalMeshSizeUpdated(topic string, size int) {
   218  	// calls the mock method to assert the metrics.
   219  	c.l.OnLocalMeshSizeUpdated(topic, size)
   220  }