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 }