github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/scoring/subscription_validator_test.go (about) 1 package scoring_test 2 3 import ( 4 "context" 5 "math/rand" 6 "regexp" 7 "testing" 8 "time" 9 10 "github.com/onflow/flow-go/config" 11 "github.com/onflow/flow-go/network/message" 12 "github.com/onflow/flow-go/network/p2p" 13 p2ptest "github.com/onflow/flow-go/network/p2p/test" 14 flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" 15 16 "github.com/libp2p/go-libp2p/core/peer" 17 mocktestify "github.com/stretchr/testify/mock" 18 "github.com/stretchr/testify/require" 19 20 "github.com/onflow/flow-go/model/flow" 21 "github.com/onflow/flow-go/model/messages" 22 "github.com/onflow/flow-go/module/id" 23 "github.com/onflow/flow-go/module/irrecoverable" 24 "github.com/onflow/flow-go/module/mock" 25 "github.com/onflow/flow-go/network/channels" 26 "github.com/onflow/flow-go/network/internal/p2pfixtures" 27 mockp2p "github.com/onflow/flow-go/network/p2p/mock" 28 "github.com/onflow/flow-go/network/p2p/scoring" 29 "github.com/onflow/flow-go/utils/unittest" 30 ) 31 32 // TestSubscriptionProvider_GetSubscribedTopics tests that when a peer has not subscribed to 33 // any topic, the subscription validator returns no error. 34 func TestSubscriptionValidator_NoSubscribedTopic(t *testing.T) { 35 sp := mockp2p.NewSubscriptionProvider(t) 36 sv := scoring.NewSubscriptionValidator(unittest.Logger(), sp) 37 38 // mocks peer 1 not subscribed to any topic. 39 peer1 := unittest.PeerIdFixture(t) 40 sp.On("GetSubscribedTopics", peer1).Return([]string{}) 41 42 // as peer 1 has not subscribed to any topic, the subscription validator should return no error regardless of the 43 // role. 44 for _, role := range flow.Roles() { 45 require.NoError(t, sv.CheckSubscribedToAllowedTopics(peer1, role)) 46 } 47 } 48 49 // TestSubscriptionValidator_UnknownChannel tests that when a peer has subscribed to an unknown 50 // topic, the subscription validator returns an error. 51 func TestSubscriptionValidator_UnknownChannel(t *testing.T) { 52 sp := mockp2p.NewSubscriptionProvider(t) 53 sv := scoring.NewSubscriptionValidator(unittest.Logger(), sp) 54 55 // mocks peer 1 not subscribed to an unknown topic. 56 peer1 := unittest.PeerIdFixture(t) 57 sp.On("GetSubscribedTopics", peer1).Return([]string{"unknown-topic-1", "unknown-topic-2"}) 58 59 // as peer 1 has subscribed to unknown topics, the subscription validator should return an error 60 // regardless of the role. 61 for _, role := range flow.Roles() { 62 err := sv.CheckSubscribedToAllowedTopics(peer1, role) 63 require.Error(t, err) 64 require.True(t, p2p.IsInvalidSubscriptionError(err)) 65 } 66 } 67 68 // TestSubscriptionValidator_ValidSubscription tests that when a peer has subscribed to valid 69 // topics based on its Flow protocol role, the subscription validator returns no error. 70 func TestSubscriptionValidator_ValidSubscriptions(t *testing.T) { 71 sp := mockp2p.NewSubscriptionProvider(t) 72 sv := scoring.NewSubscriptionValidator(unittest.Logger(), sp) 73 74 for _, role := range flow.Roles() { 75 peerId := unittest.PeerIdFixture(t) 76 // allowed channels for the role excluding the test channels. 77 allowedChannels := channels.ChannelsByRole(role).ExcludePattern(regexp.MustCompile("^(test).*")) 78 sporkID := unittest.IdentifierFixture() 79 80 allowedTopics := make([]string, 0, len(allowedChannels)) 81 for _, channel := range allowedChannels { 82 allowedTopics = append(allowedTopics, channels.TopicFromChannel(channel, sporkID).String()) 83 } 84 85 // peer should pass the subscription validator as it has subscribed to any subset of its allowed topics. 86 for i := range allowedTopics { 87 sp.On("GetSubscribedTopics", peerId).Return(allowedTopics[:i+1]) 88 require.NoError(t, sv.CheckSubscribedToAllowedTopics(peerId, role)) 89 } 90 } 91 } 92 93 // TestSubscriptionValidator_SubscribeToAllTopics tests that regardless of its role when a peer has subscribed to all 94 // topics, the subscription validator returns an error. 95 // 96 // Note: this test is to ensure that the subscription validator is not bypassed by subscribing to all topics. 97 // It also based on the assumption that within the list of all topics, there are guaranteed to be some that are not allowed. 98 // If this assumption is not true, this test will fail. Hence, the test should be updated accordingly if the assumption 99 // is no longer true. 100 func TestSubscriptionValidator_SubscribeToAllTopics(t *testing.T) { 101 sp := mockp2p.NewSubscriptionProvider(t) 102 sv := scoring.NewSubscriptionValidator(unittest.Logger(), sp) 103 104 allChannels := channels.Channels().ExcludePattern(regexp.MustCompile("^(test).*")) 105 sporkID := unittest.IdentifierFixture() 106 allTopics := make([]string, 0, len(allChannels)) 107 for _, channel := range allChannels { 108 allTopics = append(allTopics, channels.TopicFromChannel(channel, sporkID).String()) 109 } 110 111 for _, role := range flow.Roles() { 112 peerId := unittest.PeerIdFixture(t) 113 sp.On("GetSubscribedTopics", peerId).Return(allTopics) 114 err := sv.CheckSubscribedToAllowedTopics(peerId, role) 115 require.Error(t, err, role) 116 require.True(t, p2p.IsInvalidSubscriptionError(err), role) 117 } 118 } 119 120 // TestSubscriptionValidator_InvalidSubscription tests that when a peer has subscribed to invalid 121 // topics based on its Flow protocol role, the subscription validator returns an error. 122 func TestSubscriptionValidator_InvalidSubscriptions(t *testing.T) { 123 sp := mockp2p.NewSubscriptionProvider(t) 124 sv := scoring.NewSubscriptionValidator(unittest.Logger(), sp) 125 126 for _, role := range flow.Roles() { 127 peerId := unittest.PeerIdFixture(t) 128 unauthorizedChannels := channels.Channels(). // all channels 129 ExcludeChannels(channels.ChannelsByRole(role)). // excluding the channels for the role 130 ExcludePattern(regexp.MustCompile("^(test).*")) // excluding the test channels. 131 sporkID := unittest.IdentifierFixture() 132 unauthorizedTopics := make([]string, 0, len(unauthorizedChannels)) 133 for _, channel := range unauthorizedChannels { 134 unauthorizedTopics = append(unauthorizedTopics, channels.TopicFromChannel(channel, sporkID).String()) 135 } 136 137 // peer should NOT pass subscription validator as it has subscribed to any subset of its unauthorized topics, 138 // regardless of the role. 139 for i := range unauthorizedTopics { 140 sp.On("GetSubscribedTopics", peerId).Return(unauthorizedTopics[:i+1]) 141 err := sv.CheckSubscribedToAllowedTopics(peerId, role) 142 require.Error(t, err, role) 143 require.True(t, p2p.IsInvalidSubscriptionError(err), role) 144 } 145 } 146 } 147 148 // TestSubscriptionValidator_Integration tests that when a peer is subscribed to an invalid topic, it is penalized 149 // by the subscription validator of other peers on that same (invalid) topic, 150 // and they prevent the peer from sending messages on that topic. 151 // This test is an integration test that tests the subscription validator in the context of the pubsub. 152 // 153 // Scenario: 154 // Part-1: 155 // 1. Two verification nodes and one consensus node are created. 156 // 2. All nodes subscribe to a legit shared topic (PushBlocks). 157 // 3. Consensus node publishes a block on this topic. 158 // 4. Test checks that all nodes receive the block. 159 // 160 // Part-2: 161 // 1. Consensus node subscribes to an invalid topic, which it is not supposed to (RequestChunks). 162 // 2. Consensus node publishes a block on the PushBlocks topic. 163 // 3. Test checks that none of the nodes receive the block. 164 // 4. Verification node also publishes a chunk request on the RequestChunks channel. 165 // 5. Test checks that consensus node does not receive the chunk request while the other verification node does. 166 func TestSubscriptionValidator_Integration(t *testing.T) { 167 unittest.SkipUnless(t, unittest.TEST_FLAKY, "flakey tests") 168 ctx, cancel := context.WithCancel(context.Background()) 169 signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) 170 171 cfg, err := config.DefaultConfig() 172 require.NoError(t, err) 173 // set a low update interval to speed up the test 174 cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond 175 cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond 176 // score tracer interval is set to 500 milliseconds to speed up the test, it should be shorter than the heartbeat interval (1 second) of gossipsub to catch the score updates in time. 177 cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 500 * time.Millisecond 178 179 sporkId := unittest.IdentifierFixture() 180 181 idProvider := mock.NewIdentityProvider(t) 182 // one consensus node. 183 conNode, conId := p2ptest.NodeFixture(t, sporkId, t.Name(), 184 idProvider, 185 p2ptest.WithLogger(unittest.Logger()), 186 p2ptest.OverrideFlowConfig(cfg), 187 p2ptest.WithRole(flow.RoleConsensus)) 188 189 // two verification node. 190 verNode1, verId1 := p2ptest.NodeFixture(t, sporkId, t.Name(), 191 idProvider, 192 p2ptest.WithLogger(unittest.Logger()), 193 p2ptest.OverrideFlowConfig(cfg), 194 p2ptest.WithRole(flow.RoleVerification)) 195 196 verNode2, verId2 := p2ptest.NodeFixture(t, sporkId, t.Name(), 197 idProvider, 198 p2ptest.WithLogger(unittest.Logger()), 199 p2ptest.OverrideFlowConfig(cfg), 200 p2ptest.WithRole(flow.RoleVerification)) 201 202 // suppress peer provider error 203 peerProvider := func() peer.IDSlice { 204 return []peer.ID{conNode.ID(), verNode1.ID(), verNode2.ID()} 205 } 206 verNode1.WithPeersProvider(peerProvider) 207 verNode2.WithPeersProvider(peerProvider) 208 conNode.WithPeersProvider(peerProvider) 209 210 ids := flow.IdentityList{&conId, &verId1, &verId2} 211 nodes := []p2p.LibP2PNode{conNode, verNode1, verNode2} 212 213 provider := id.NewFixedIdentityProvider(ids) 214 idProvider.On("ByPeerID", mocktestify.Anything).Return( 215 func(peerId peer.ID) *flow.Identity { 216 identity, _ := provider.ByPeerID(peerId) 217 return identity 218 }, func(peerId peer.ID) bool { 219 _, ok := provider.ByPeerID(peerId) 220 return ok 221 }) 222 223 p2ptest.StartNodes(t, signalerCtx, nodes) 224 defer p2ptest.StopNodes(t, nodes, cancel) 225 226 blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) 227 228 topicValidator := flowpubsub.TopicValidator(unittest.Logger(), unittest.AllowAllPeerFilter()) 229 230 // wait for the subscriptions to be established 231 p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids) 232 233 // consensus node subscribes to the block topic. 234 conSub, err := conNode.Subscribe(blockTopic, topicValidator) 235 require.NoError(t, err) 236 237 // both verification nodes subscribe to the blocks and chunks topic (because they are allowed to). 238 ver1SubBlocks, err := verNode1.Subscribe(blockTopic, topicValidator) 239 require.NoError(t, err) 240 241 ver1SubChunks, err := verNode1.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 242 require.NoError(t, err) 243 244 ver2SubBlocks, err := verNode2.Subscribe(blockTopic, topicValidator) 245 require.NoError(t, err) 246 247 ver2SubChunks, err := verNode2.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 248 require.NoError(t, err) 249 250 // let the subscriptions be established 251 time.Sleep(2 * time.Second) 252 253 outgoingMessageScope, err := message.NewOutgoingScope( 254 ids.NodeIDs(), 255 channels.TopicFromChannel(channels.PushBlocks, sporkId), 256 unittest.ProposalFixture(), 257 unittest.NetworkCodec().Encode, 258 message.ProtocolTypePubSub) 259 require.NoError(t, err) 260 require.NoError(t, conNode.Publish(ctx, outgoingMessageScope)) 261 262 // checks that the message is received by all nodes. 263 ctx1s, cancel1s := context.WithTimeout(ctx, 1*time.Second) 264 defer cancel1s() 265 266 expectedReceivedData, err := outgoingMessageScope.Proto().Marshal() 267 require.NoError(t, err) 268 269 p2pfixtures.SubsMustReceiveMessage(t, ctx1s, expectedReceivedData, []p2p.Subscription{conSub, ver1SubBlocks, ver2SubBlocks}) 270 271 // now consensus node is doing something very bad! 272 // it is subscribing to a channel that it is not supposed to subscribe to. 273 conSubChunks, err := conNode.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 274 require.NoError(t, err) 275 276 invalidSubscriptionPenalty := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty 277 require.Eventually(t, func() bool { 278 score, ok := verNode1.PeerScoreExposer().GetScore(conNode.ID()) 279 return score == invalidSubscriptionPenalty && ok 280 }, 5*time.Second, 200*time.Millisecond) 281 require.Eventually(t, func() bool { 282 score, ok := verNode2.PeerScoreExposer().GetScore(conNode.ID()) 283 return score == invalidSubscriptionPenalty && ok 284 }, 5*time.Second, 200*time.Millisecond) 285 286 // consensus node publishes another proposal, but this time, it should not reach verification node. 287 // since upon an unauthorized subscription, verification node should have slashed consensus node on 288 // the GossipSub scoring protocol. 289 outgoingMessageScope, err = message.NewOutgoingScope( 290 ids.NodeIDs(), 291 channels.TopicFromChannel(channels.PushBlocks, sporkId), 292 unittest.ProposalFixture(), 293 unittest.NetworkCodec().Encode, 294 message.ProtocolTypePubSub) 295 require.NoError(t, err) 296 297 ctx5s, cancel5s := context.WithTimeout(ctx, 5*time.Second) 298 defer cancel5s() 299 p2pfixtures.SubsMustEventuallyStopReceivingAnyMessage(t, ctx5s, []p2p.Subscription{ver1SubBlocks, ver2SubBlocks}, func(t *testing.T) { 300 require.NoError(t, conNode.Publish(ctx, outgoingMessageScope)) 301 }) 302 303 // moreover, a verification node publishing a message to the request chunk topic should not reach consensus node. 304 // however, both verification nodes should receive the message. 305 outgoingMessageScope, err = message.NewOutgoingScope( 306 ids.NodeIDs(), 307 channels.TopicFromChannel(channels.RequestChunks, sporkId), 308 &messages.ChunkDataRequest{ 309 ChunkID: unittest.IdentifierFixture(), 310 Nonce: rand.Uint64(), 311 }, 312 unittest.NetworkCodec().Encode, 313 message.ProtocolTypePubSub) 314 require.NoError(t, err) 315 316 require.NoError(t, verNode1.Publish(ctx, outgoingMessageScope)) 317 318 ctx1s, cancel1s = context.WithTimeout(ctx, 1*time.Second) 319 defer cancel1s() 320 321 expectedReceivedData, err = outgoingMessageScope.Proto().Marshal() 322 require.NoError(t, err) 323 324 p2pfixtures.SubsMustReceiveMessage(t, ctx1s, expectedReceivedData, []p2p.Subscription{ver1SubChunks, ver2SubChunks}) 325 ctx5s, cancel5s = context.WithTimeout(ctx, 5*time.Second) 326 defer cancel5s() 327 p2pfixtures.SubsMustEventuallyStopReceivingAnyMessage(t, ctx5s, []p2p.Subscription{conSubChunks}, func(t *testing.T) { 328 require.NoError(t, verNode1.Publish(ctx, outgoingMessageScope)) 329 }) 330 }