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 }