github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/tracer/gossipSubScoreTracer_test.go (about) 1 package tracer_test 2 3 import ( 4 "context" 5 "io" 6 "testing" 7 "time" 8 9 pubsub "github.com/libp2p/go-libp2p-pubsub" 10 "github.com/libp2p/go-libp2p/core/peer" 11 "github.com/rs/zerolog" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/mock" 14 "github.com/stretchr/testify/require" 15 "go.uber.org/atomic" 16 17 "github.com/onflow/flow-go/config" 18 "github.com/onflow/flow-go/model/flow" 19 "github.com/onflow/flow-go/module" 20 "github.com/onflow/flow-go/module/irrecoverable" 21 "github.com/onflow/flow-go/module/metrics" 22 mockmodule "github.com/onflow/flow-go/module/mock" 23 "github.com/onflow/flow-go/network/channels" 24 "github.com/onflow/flow-go/network/p2p" 25 p2ptest "github.com/onflow/flow-go/network/p2p/test" 26 "github.com/onflow/flow-go/network/p2p/tracer" 27 validator "github.com/onflow/flow-go/network/validator/pubsub" 28 "github.com/onflow/flow-go/utils/unittest" 29 ) 30 31 // TestGossipSubScoreTracer tests the functionality of the GossipSubScoreTracer, which logs the scores 32 // of the libp2p nodes using the GossipSub protocol. The test sets up three nodes with the same role, 33 // and subscribes them to a common topic. One of these three nodes is furnished with the score tracer, and the test 34 // examines whether the tracer node is able to trace the local score of other two nodes properly. 35 // The test also checks that the correct metrics are being called for each score update. 36 // 37 // The test performs the following steps: 38 // 1. Creates a logger hook to count the number of times the score logs at the interval specified. 39 // 2. Creates a mockPeerScoreMetrics object and sets it as a metrics collector for the tracer node. 40 // 3. Creates three nodes with same roles and sets their roles as consensus, access, and tracer, respectively. 41 // 4. Sets some fixed scores for the nodes for the sake of testing based on their roles. 42 // 5. Starts the nodes and lets them discover each other. 43 // 6. Subscribes the nodes to a common topic. 44 // 7. Expects the tracer node to have the correct app scores, a non-zero score, an existing behaviour score, an existing 45 // IP score, and an existing mesh score. 46 // 8. Expects the score tracer to log the scores at least once. 47 // 9. Checks that the correct metrics are being called for each score update. 48 func TestGossipSubScoreTracer(t *testing.T) { 49 ctx, cancel := context.WithCancel(context.Background()) 50 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 51 sporkId := unittest.IdentifierFixture() 52 idProvider := mockmodule.NewIdentityProvider(t) 53 defer cancel() 54 55 loggerCycle := atomic.NewInt32(0) 56 57 // 1. Creates a logger hook to count the number of times the score logs at the interval specified. 58 hook := zerolog.HookFunc(func(e *zerolog.Event, level zerolog.Level, message string) { 59 if level == zerolog.DebugLevel { 60 if message == tracer.PeerScoreLogMessage { 61 loggerCycle.Inc() 62 } 63 } 64 }) 65 logger := zerolog.New(io.Discard).Level(zerolog.DebugLevel).Hook(hook) 66 67 // sets some fixed scores for the nodes for sake of testing based on their roles. 68 consensusScore := float64(87) 69 accessScore := float64(77) 70 71 // 2. Creates a mockPeerScoreMetrics object and sets it as a metrics collector for the tracer node. 72 scoreMetrics := mockmodule.NewGossipSubScoringMetrics(t) 73 topic1 := channels.TopicFromChannel(channels.PushBlocks, sporkId) 74 75 // 3. Creates three nodes with different roles and sets their roles as consensus, access, and tracer, respectively. 76 cfg, err := config.DefaultConfig() 77 require.NoError(t, err) 78 // tracer will update the score and local mesh every 1 second (for testing purposes) 79 cfg.NetworkConfig.GossipSub.RpcTracer.LocalMeshLogInterval = 1 * time.Second 80 cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second 81 // the libp2p node updates the subscription list as well as the app-specific score every 10 milliseconds (for testing purposes) 82 cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond 83 cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond 84 tracerNode, tracerId := p2ptest.NodeFixture( 85 t, 86 sporkId, 87 t.Name(), 88 idProvider, 89 p2ptest.WithMetricsCollector(&mockPeerScoreMetrics{ 90 NoopCollector: metrics.NoopCollector{}, 91 c: scoreMetrics, 92 }), 93 p2ptest.WithLogger(logger), 94 p2ptest.OverrideFlowConfig(cfg), 95 p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ 96 AppSpecificScoreParams: func(pid peer.ID) float64 { 97 id, ok := idProvider.ByPeerID(pid) 98 require.True(t, ok) 99 100 switch id.Role { 101 case flow.RoleConsensus: 102 return consensusScore 103 case flow.RoleAccess: 104 return accessScore 105 default: 106 t.Fatalf("unexpected role: %s", id.Role) 107 } 108 return 0 109 }, 110 TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ 111 topic1: { 112 // set the topic score params to some fixed values for sake of testing. 113 // Note that these values are not realistic and should not be used in production. 114 TopicWeight: 1, 115 TimeInMeshQuantum: 1 * time.Second, 116 TimeInMeshWeight: 1, 117 TimeInMeshCap: 1000, 118 FirstMessageDeliveriesWeight: 1, 119 FirstMessageDeliveriesDecay: 0.999, 120 FirstMessageDeliveriesCap: 1000, 121 MeshMessageDeliveriesWeight: -1, 122 MeshMessageDeliveriesDecay: 0.999, 123 MeshMessageDeliveriesThreshold: 100, 124 MeshMessageDeliveriesActivation: 1 * time.Second, 125 MeshMessageDeliveriesCap: 1000, 126 MeshFailurePenaltyWeight: -1, 127 MeshFailurePenaltyDecay: 0.999, 128 InvalidMessageDeliveriesWeight: -1, 129 InvalidMessageDeliveriesDecay: 0.999, 130 }, 131 }, 132 }), 133 p2ptest.WithRole(flow.RoleConsensus)) 134 135 idProvider.On("ByPeerID", tracerNode.ID()).Return(&tracerId, true).Maybe() 136 137 consensusNode, consensusId := p2ptest.NodeFixture( 138 t, 139 sporkId, 140 t.Name(), 141 idProvider, 142 p2ptest.WithRole(flow.RoleConsensus)) 143 idProvider.On("ByPeerID", consensusNode.ID()).Return(&consensusId, true).Maybe() 144 145 accessNode, accessId := p2ptest.NodeFixture( 146 t, 147 sporkId, 148 t.Name(), 149 idProvider, 150 p2ptest.WithRole(flow.RoleAccess)) 151 idProvider.On("ByPeerID", accessNode.ID()).Return(&accessId, true).Maybe() 152 153 nodes := []p2p.LibP2PNode{tracerNode, consensusNode, accessNode} 154 ids := flow.IdentityList{&tracerId, &consensusId, &accessId} 155 156 // 5. Starts the nodes and lets them discover each other. 157 p2ptest.StartNodes(t, signalerCtx, nodes) 158 defer p2ptest.StopNodes(t, nodes, cancel) 159 160 p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids) 161 162 // 9. Checks that the correct metrics are being called for each score update. 163 scoreMetrics.On("OnOverallPeerScoreUpdated", mock.Anything).Return() 164 scoreMetrics.On("OnAppSpecificScoreUpdated", mock.Anything).Return() 165 scoreMetrics.On("OnIPColocationFactorUpdated", mock.Anything).Return() 166 scoreMetrics.On("OnBehaviourPenaltyUpdated", mock.Anything).Return() 167 scoreMetrics.On("OnTimeInMeshUpdated", topic1, mock.Anything).Return() 168 scoreMetrics.On("OnFirstMessageDeliveredUpdated", topic1, mock.Anything).Return() 169 scoreMetrics.On("OnMeshMessageDeliveredUpdated", topic1, mock.Anything).Return() 170 scoreMetrics.On("OnMeshMessageDeliveredUpdated", topic1, mock.Anything).Return() 171 scoreMetrics.On("OnInvalidMessageDeliveredUpdated", topic1, mock.Anything).Return() 172 scoreMetrics.On("SetWarningStateCount", uint(0)).Return() 173 174 // 6. Subscribes the nodes to a common topic. 175 _, err = tracerNode.Subscribe( 176 topic1, 177 validator.TopicValidator( 178 unittest.Logger(), 179 unittest.AllowAllPeerFilter())) 180 require.NoError(t, err) 181 182 _, err = consensusNode.Subscribe( 183 topic1, 184 validator.TopicValidator( 185 unittest.Logger(), 186 unittest.AllowAllPeerFilter())) 187 require.NoError(t, err) 188 189 _, err = accessNode.Subscribe( 190 topic1, 191 validator.TopicValidator( 192 unittest.Logger(), 193 unittest.AllowAllPeerFilter())) 194 require.NoError(t, err) 195 196 // 7. Expects the tracer node to have the correct app scores, a non-zero score, an existing behaviour score, an existing 197 // IP score, and an existing mesh score. 198 require.Eventually(t, func() bool { 199 // we expect the tracerNode to have the consensusNodes and accessNodes with the correct app scores. 200 exposer := tracerNode.PeerScoreExposer() 201 score, ok := exposer.GetAppScore(consensusNode.ID()) 202 if !ok || score != consensusScore { 203 return false 204 } 205 206 score, ok = exposer.GetAppScore(accessNode.ID()) 207 if !ok || score != accessScore { 208 return false 209 } 210 211 // we expect the tracerNode to have the consensusNodes and accessNodes with a non-zero score. 212 score, ok = exposer.GetScore(consensusNode.ID()) 213 if !ok || score == 0 { 214 return false 215 } 216 217 score, ok = exposer.GetScore(accessNode.ID()) 218 if !ok || score == 0 { 219 return false 220 } 221 222 // we expect the tracerNode to have the consensusNodes and accessNodes with an existing behaviour score and ip score. 223 _, ok = exposer.GetBehaviourPenalty(consensusNode.ID()) 224 if !ok { 225 return false 226 } 227 228 _, ok = exposer.GetIPColocationFactor(consensusNode.ID()) 229 if !ok { 230 return false 231 } 232 233 _, ok = exposer.GetBehaviourPenalty(accessNode.ID()) 234 if !ok { 235 return false 236 } 237 238 _, ok = exposer.GetIPColocationFactor(accessNode.ID()) 239 if !ok { 240 return false 241 } 242 243 // we expect the tracerNode to have the consensusNodes and accessNodes with an existing mesh score. 244 consensusMeshScores, ok := exposer.GetTopicScores(consensusNode.ID()) 245 if !ok { 246 return false 247 } 248 _, ok = consensusMeshScores[topic1.String()] 249 if !ok { 250 return false 251 } 252 253 accessMeshScore, ok := exposer.GetTopicScores(accessNode.ID()) 254 if !ok { 255 return false 256 } 257 _, ok = accessMeshScore[topic1.String()] 258 return ok 259 }, 2*time.Second, 10*time.Millisecond) 260 261 time.Sleep(2 * time.Second) 262 263 // 8. Expects the score tracer to log the scores at least once. 264 assert.Eventually(t, func() bool { 265 return loggerCycle.Load() > 0 266 }, 2*time.Second, 10*time.Millisecond) 267 } 268 269 type mockPeerScoreMetrics struct { 270 metrics.NoopCollector 271 c module.GossipSubScoringMetrics 272 } 273 274 func (m *mockPeerScoreMetrics) OnOverallPeerScoreUpdated(f float64) { 275 m.c.OnOverallPeerScoreUpdated(f) 276 } 277 278 func (m *mockPeerScoreMetrics) OnAppSpecificScoreUpdated(f float64) { 279 m.c.OnAppSpecificScoreUpdated(f) 280 } 281 282 func (m *mockPeerScoreMetrics) OnIPColocationFactorUpdated(f float64) { 283 m.c.OnIPColocationFactorUpdated(f) 284 } 285 286 func (m *mockPeerScoreMetrics) OnBehaviourPenaltyUpdated(f float64) { 287 m.c.OnBehaviourPenaltyUpdated(f) 288 } 289 290 func (m *mockPeerScoreMetrics) OnTimeInMeshUpdated(topic channels.Topic, duration time.Duration) { 291 m.c.OnTimeInMeshUpdated(topic, duration) 292 } 293 294 func (m *mockPeerScoreMetrics) OnFirstMessageDeliveredUpdated(topic channels.Topic, f float64) { 295 m.c.OnFirstMessageDeliveredUpdated(topic, f) 296 } 297 298 func (m *mockPeerScoreMetrics) OnMeshMessageDeliveredUpdated(topic channels.Topic, f float64) { 299 m.c.OnMeshMessageDeliveredUpdated(topic, f) 300 } 301 302 func (m *mockPeerScoreMetrics) OnInvalidMessageDeliveredUpdated(topic channels.Topic, f float64) { 303 m.c.OnInvalidMessageDeliveredUpdated(topic, f) 304 } 305 306 func (m *mockPeerScoreMetrics) SetWarningStateCount(u uint) { 307 m.c.SetWarningStateCount(u) 308 } 309 310 var _ module.GossipSubScoringMetrics = (*mockPeerScoreMetrics)(nil)