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  }