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  }