github.com/onflow/flow-go@v0.33.17/network/p2p/scoring/scoring_test.go (about)

     1  package scoring_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    11  	"github.com/libp2p/go-libp2p/core/peer"
    12  	"github.com/rs/zerolog"
    13  	mocktestify "github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/onflow/flow-go/config"
    17  	"github.com/onflow/flow-go/model/flow"
    18  	"github.com/onflow/flow-go/module"
    19  	"github.com/onflow/flow-go/module/component"
    20  	"github.com/onflow/flow-go/module/id"
    21  	"github.com/onflow/flow-go/module/irrecoverable"
    22  	"github.com/onflow/flow-go/module/metrics"
    23  	"github.com/onflow/flow-go/module/mock"
    24  	flownet "github.com/onflow/flow-go/network"
    25  	"github.com/onflow/flow-go/network/channels"
    26  	"github.com/onflow/flow-go/network/p2p"
    27  	p2pconfig "github.com/onflow/flow-go/network/p2p/config"
    28  	p2pmsg "github.com/onflow/flow-go/network/p2p/message"
    29  	p2ptest "github.com/onflow/flow-go/network/p2p/test"
    30  	"github.com/onflow/flow-go/utils/unittest"
    31  )
    32  
    33  // mockInspectorSuite is a mock implementation of the GossipSubInspectorSuite interface.
    34  // It is used to test the impact of invalid control messages on the scoring and connectivity of nodes in a network.
    35  type mockInspectorSuite struct {
    36  	component.Component
    37  	t        *testing.T
    38  	consumer p2p.GossipSubInvCtrlMsgNotifConsumer
    39  }
    40  
    41  // ensures that mockInspectorSuite implements the GossipSubInspectorSuite interface.
    42  var _ p2p.GossipSubInspectorSuite = (*mockInspectorSuite)(nil)
    43  
    44  func (m *mockInspectorSuite) AddInvalidControlMessageConsumer(consumer p2p.GossipSubInvCtrlMsgNotifConsumer) {
    45  	require.Nil(m.t, m.consumer)
    46  	m.consumer = consumer
    47  }
    48  func (m *mockInspectorSuite) ActiveClustersChanged(_ flow.ChainIDList) {
    49  	// no-op
    50  }
    51  
    52  // newMockInspectorSuite creates a new mockInspectorSuite.
    53  // Args:
    54  // - t: the test object used for assertions.
    55  // Returns:
    56  // - a new mockInspectorSuite.
    57  func newMockInspectorSuite(t *testing.T) *mockInspectorSuite {
    58  	i := &mockInspectorSuite{
    59  		t: t,
    60  	}
    61  
    62  	builder := component.NewComponentManagerBuilder()
    63  	builder.AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
    64  		ready()
    65  		<-ctx.Done()
    66  	})
    67  
    68  	i.Component = builder.Build()
    69  	return i
    70  }
    71  
    72  // InspectFunc returns a function that is called when a node receives a control message.
    73  // In this mock implementation, the function does nothing.
    74  func (m *mockInspectorSuite) InspectFunc() func(peer.ID, *pubsub.RPC) error {
    75  	return nil
    76  }
    77  
    78  // TestInvalidCtrlMsgScoringIntegration tests the impact of invalid control messages on the scoring and connectivity of nodes in a network.
    79  // It creates a network of 2 nodes, and sends a set of control messages with invalid topic IDs to one of the nodes.
    80  // It then checks that the node receiving the invalid control messages decreases its score for the peer spamming the invalid messages, and
    81  // eventually disconnects from the spamming peer on the gossipsub layer, i.e., messages sent by the spamming peer are no longer
    82  // received by the node.
    83  func TestInvalidCtrlMsgScoringIntegration(t *testing.T) {
    84  	ctx, cancel := context.WithCancel(context.Background())
    85  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
    86  	sporkId := unittest.IdentifierFixture()
    87  	idProvider := mock.NewIdentityProvider(t)
    88  
    89  	inspectorSuite1 := newMockInspectorSuite(t)
    90  	factory := func(
    91  		irrecoverable.SignalerContext,
    92  		zerolog.Logger,
    93  		flow.Identifier,
    94  		*p2pconfig.RpcInspectorParameters,
    95  		module.GossipSubMetrics,
    96  		metrics.HeroCacheMetricsFactory,
    97  		flownet.NetworkingType,
    98  		module.IdentityProvider,
    99  		func() p2p.TopicProvider) (p2p.GossipSubInspectorSuite, error) {
   100  		// override the gossipsub rpc inspector suite factory to return the mock inspector suite
   101  		return inspectorSuite1, nil
   102  	}
   103  
   104  	cfg, err := config.DefaultConfig()
   105  	require.NoError(t, err)
   106  
   107  	cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond // speed up the test
   108  
   109  	node1, id1 := p2ptest.NodeFixture(
   110  		t,
   111  		sporkId,
   112  		t.Name(),
   113  		idProvider,
   114  		p2ptest.WithRole(flow.RoleConsensus),
   115  		p2ptest.OverrideFlowConfig(cfg),
   116  		p2ptest.OverrideGossipSubRpcInspectorSuiteFactory(factory))
   117  
   118  	node2, id2 := p2ptest.NodeFixture(
   119  		t,
   120  		sporkId,
   121  		t.Name(),
   122  		idProvider,
   123  		p2ptest.WithRole(flow.RoleConsensus),
   124  		p2ptest.OverrideFlowConfig(cfg))
   125  
   126  	ids := flow.IdentityList{&id1, &id2}
   127  	nodes := []p2p.LibP2PNode{node1, node2}
   128  
   129  	provider := id.NewFixedIdentityProvider(ids)
   130  	idProvider.On("ByPeerID", mocktestify.Anything).Return(
   131  		func(peerId peer.ID) *flow.Identity {
   132  			identity, _ := provider.ByPeerID(peerId)
   133  			return identity
   134  		}, func(peerId peer.ID) bool {
   135  			_, ok := provider.ByPeerID(peerId)
   136  			return ok
   137  		})
   138  	p2ptest.StartNodes(t, signalerCtx, nodes)
   139  	defer p2ptest.StopNodes(t, nodes, cancel)
   140  
   141  	p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids)
   142  	blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId)
   143  	// checks end-to-end message delivery works on GossipSub.
   144  	p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} {
   145  		return unittest.ProposalFixture()
   146  	})
   147  
   148  	// simulates node2 spamming node1 with invalid gossipsub control messages until node2 gets dissallow listed.
   149  	// since the decay will start lower than .99 and will only be incremented by default .01, we need to spam a lot of messages so that the node gets disallow listed
   150  	for i := 0; i < 750; i++ {
   151  		inspectorSuite1.consumer.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{
   152  			PeerID:  node2.ID(),
   153  			MsgType: p2pmsg.ControlMessageTypes()[rand.Intn(len(p2pmsg.ControlMessageTypes()))],
   154  			Error:   fmt.Errorf("invalid control message"),
   155  		})
   156  	}
   157  
   158  	time.Sleep(1 * time.Second) // wait for app-specific score to be updated in the cache (remember that we need at least 100 ms for the score to be updated (ScoreTTL))
   159  
   160  	// checks no GossipSub message exchange should no longer happen between node1 and node2.
   161  	p2ptest.EnsureNoPubsubExchangeBetweenGroups(
   162  		t,
   163  		ctx,
   164  		[]p2p.LibP2PNode{node1},
   165  		flow.IdentifierList{id1.NodeID},
   166  		[]p2p.LibP2PNode{node2},
   167  		flow.IdentifierList{id2.NodeID},
   168  		blockTopic,
   169  		1,
   170  		func() interface{} {
   171  			return unittest.ProposalFixture()
   172  		})
   173  }