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