github.com/onflow/flow-go@v0.33.17/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, "flaky test") 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 177 sporkId := unittest.IdentifierFixture() 178 179 idProvider := mock.NewIdentityProvider(t) 180 // one consensus node. 181 conNode, conId := p2ptest.NodeFixture(t, sporkId, t.Name(), 182 idProvider, 183 p2ptest.WithLogger(unittest.Logger()), 184 p2ptest.OverrideFlowConfig(cfg), 185 p2ptest.WithRole(flow.RoleConsensus)) 186 187 // two verification node. 188 verNode1, verId1 := p2ptest.NodeFixture(t, sporkId, t.Name(), 189 idProvider, 190 p2ptest.WithLogger(unittest.Logger()), 191 p2ptest.OverrideFlowConfig(cfg), 192 p2ptest.WithRole(flow.RoleVerification)) 193 194 verNode2, verId2 := p2ptest.NodeFixture(t, sporkId, t.Name(), 195 idProvider, 196 p2ptest.WithLogger(unittest.Logger()), 197 p2ptest.OverrideFlowConfig(cfg), 198 p2ptest.WithRole(flow.RoleVerification)) 199 200 // suppress peer provider error 201 peerProvider := func() peer.IDSlice { 202 return []peer.ID{conNode.ID(), verNode1.ID(), verNode2.ID()} 203 } 204 verNode1.WithPeersProvider(peerProvider) 205 verNode2.WithPeersProvider(peerProvider) 206 conNode.WithPeersProvider(peerProvider) 207 208 ids := flow.IdentityList{&conId, &verId1, &verId2} 209 nodes := []p2p.LibP2PNode{conNode, verNode1, verNode2} 210 211 provider := id.NewFixedIdentityProvider(ids) 212 idProvider.On("ByPeerID", mocktestify.Anything).Return( 213 func(peerId peer.ID) *flow.Identity { 214 identity, _ := provider.ByPeerID(peerId) 215 return identity 216 }, func(peerId peer.ID) bool { 217 _, ok := provider.ByPeerID(peerId) 218 return ok 219 }) 220 221 p2ptest.StartNodes(t, signalerCtx, nodes) 222 defer p2ptest.StopNodes(t, nodes, cancel) 223 224 blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) 225 226 topicValidator := flowpubsub.TopicValidator(unittest.Logger(), unittest.AllowAllPeerFilter()) 227 228 // wait for the subscriptions to be established 229 p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids) 230 231 // consensus node subscribes to the block topic. 232 conSub, err := conNode.Subscribe(blockTopic, topicValidator) 233 require.NoError(t, err) 234 235 // both verification nodes subscribe to the blocks and chunks topic (because they are allowed to). 236 ver1SubBlocks, err := verNode1.Subscribe(blockTopic, topicValidator) 237 require.NoError(t, err) 238 239 ver1SubChunks, err := verNode1.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 240 require.NoError(t, err) 241 242 ver2SubBlocks, err := verNode2.Subscribe(blockTopic, topicValidator) 243 require.NoError(t, err) 244 245 ver2SubChunks, err := verNode2.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 246 require.NoError(t, err) 247 248 // let the subscriptions be established 249 time.Sleep(2 * time.Second) 250 251 outgoingMessageScope, err := message.NewOutgoingScope( 252 ids.NodeIDs(), 253 channels.TopicFromChannel(channels.PushBlocks, sporkId), 254 unittest.ProposalFixture(), 255 unittest.NetworkCodec().Encode, 256 message.ProtocolTypePubSub) 257 require.NoError(t, err) 258 require.NoError(t, conNode.Publish(ctx, outgoingMessageScope)) 259 260 // checks that the message is received by all nodes. 261 ctx1s, cancel1s := context.WithTimeout(ctx, 1*time.Second) 262 defer cancel1s() 263 264 expectedReceivedData, err := outgoingMessageScope.Proto().Marshal() 265 require.NoError(t, err) 266 267 p2pfixtures.SubsMustReceiveMessage(t, ctx1s, expectedReceivedData, []p2p.Subscription{conSub, ver1SubBlocks, ver2SubBlocks}) 268 269 // now consensus node is doing something very bad! 270 // it is subscribing to a channel that it is not supposed to subscribe to. 271 conSubChunks, err := conNode.Subscribe(channels.TopicFromChannel(channels.RequestChunks, sporkId), topicValidator) 272 require.NoError(t, err) 273 274 // let's wait for a bit to subscription propagate. 275 time.Sleep(5 * time.Second) 276 277 // consensus node publishes another proposal, but this time, it should not reach verification node. 278 // since upon an unauthorized subscription, verification node should have slashed consensus node on 279 // the GossipSub scoring protocol. 280 outgoingMessageScope, err = message.NewOutgoingScope( 281 ids.NodeIDs(), 282 channels.TopicFromChannel(channels.PushBlocks, sporkId), 283 unittest.ProposalFixture(), 284 unittest.NetworkCodec().Encode, 285 message.ProtocolTypePubSub) 286 require.NoError(t, err) 287 require.NoError(t, conNode.Publish(ctx, outgoingMessageScope)) 288 289 ctx5s, cancel5s := context.WithTimeout(ctx, 5*time.Second) 290 defer cancel5s() 291 p2pfixtures.SubsMustNeverReceiveAnyMessage(t, ctx5s, []p2p.Subscription{ver1SubBlocks, ver2SubBlocks}) 292 293 // moreover, a verification node publishing a message to the request chunk topic should not reach consensus node. 294 // however, both verification nodes should receive the message. 295 outgoingMessageScope, err = message.NewOutgoingScope( 296 ids.NodeIDs(), 297 channels.TopicFromChannel(channels.RequestChunks, sporkId), 298 &messages.ChunkDataRequest{ 299 ChunkID: unittest.IdentifierFixture(), 300 Nonce: rand.Uint64(), 301 }, 302 unittest.NetworkCodec().Encode, 303 message.ProtocolTypePubSub) 304 require.NoError(t, err) 305 require.NoError(t, verNode1.Publish(ctx, outgoingMessageScope)) 306 307 ctx1s, cancel1s = context.WithTimeout(ctx, 1*time.Second) 308 defer cancel1s() 309 310 expectedReceivedData, err = outgoingMessageScope.Proto().Marshal() 311 require.NoError(t, err) 312 313 p2pfixtures.SubsMustReceiveMessage(t, ctx1s, expectedReceivedData, []p2p.Subscription{ver1SubChunks, ver2SubChunks}) 314 315 ctx5s, cancel5s = context.WithTimeout(ctx, 5*time.Second) 316 defer cancel5s() 317 p2pfixtures.SubsMustNeverReceiveAnyMessage(t, ctx5s, []p2p.Subscription{conSubChunks}) 318 }