github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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  	"github.com/libp2p/go-libp2p/core/peer"
    11  	"github.com/rs/zerolog"
    12  	mocktestify "github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/onflow/flow-go/config"
    16  	"github.com/onflow/flow-go/model/flow"
    17  	"github.com/onflow/flow-go/module"
    18  	"github.com/onflow/flow-go/module/id"
    19  	"github.com/onflow/flow-go/module/irrecoverable"
    20  	"github.com/onflow/flow-go/module/metrics"
    21  	"github.com/onflow/flow-go/module/mock"
    22  	flownet "github.com/onflow/flow-go/network"
    23  	"github.com/onflow/flow-go/network/channels"
    24  	"github.com/onflow/flow-go/network/p2p"
    25  	p2pconfig "github.com/onflow/flow-go/network/p2p/config"
    26  	p2pmsg "github.com/onflow/flow-go/network/p2p/message"
    27  	mockp2p "github.com/onflow/flow-go/network/p2p/mock"
    28  	p2ptest "github.com/onflow/flow-go/network/p2p/test"
    29  	"github.com/onflow/flow-go/utils/unittest"
    30  )
    31  
    32  // TestInvalidCtrlMsgScoringIntegration tests the impact of invalid control messages on the scoring and connectivity of nodes in a network.
    33  // It creates a network of 2 nodes, and sends a set of control messages with invalid topic IDs to one of the nodes.
    34  // It then checks that the node receiving the invalid control messages decreases its score for the peer spamming the invalid messages, and
    35  // eventually disconnects from the spamming peer on the gossipsub layer, i.e., messages sent by the spamming peer are no longer
    36  // received by the node.
    37  func TestInvalidCtrlMsgScoringIntegration(t *testing.T) {
    38  	ctx, cancel := context.WithCancel(context.Background())
    39  	signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)
    40  	sporkId := unittest.IdentifierFixture()
    41  	idProvider := mock.NewIdentityProvider(t)
    42  
    43  	cfg, err := config.DefaultConfig()
    44  	require.NoError(t, err)
    45  
    46  	cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond // speed up the test
    47  
    48  	var notificationConsumer p2p.GossipSubInvCtrlMsgNotifConsumer
    49  	inspector := mockp2p.NewGossipSubRPCInspector(t)
    50  	inspector.On("Inspect", mocktestify.Anything, mocktestify.Anything).Return(nil) // no-op for the inspector
    51  	inspector.On("ActiveClustersChanged", mocktestify.Anything).Return().Maybe()    // no-op for the inspector
    52  	inspector.On("Start", mocktestify.Anything).Return(nil)                         // no-op for the inspector
    53  
    54  	// mocking the Ready and Done channels to be closed
    55  	done := make(chan struct{})
    56  	close(done)
    57  	f := func() <-chan struct{} {
    58  		return done
    59  	}
    60  	inspector.On("Ready").Return(f()) // no-op for the inspector
    61  	inspector.On("Done").Return(f())  // no-op for the inspector
    62  	node1, id1 := p2ptest.NodeFixture(
    63  		t,
    64  		sporkId,
    65  		t.Name(),
    66  		idProvider,
    67  		p2ptest.WithRole(flow.RoleConsensus),
    68  		p2ptest.OverrideFlowConfig(cfg),
    69  		p2ptest.OverrideGossipSubRpcInspectorFactory(func(logger zerolog.Logger,
    70  			_ flow.Identifier,
    71  			_ *p2pconfig.RpcInspectorParameters,
    72  			_ module.GossipSubMetrics,
    73  			_ metrics.HeroCacheMetricsFactory,
    74  			_ flownet.NetworkingType,
    75  			_ module.IdentityProvider,
    76  			_ func() p2p.TopicProvider,
    77  			consumer p2p.GossipSubInvCtrlMsgNotifConsumer) (p2p.GossipSubRPCInspector, error) {
    78  			// short-wire the consumer
    79  			notificationConsumer = consumer
    80  			return inspector, nil
    81  		}))
    82  
    83  	node2, id2 := p2ptest.NodeFixture(
    84  		t,
    85  		sporkId,
    86  		t.Name(),
    87  		idProvider,
    88  		p2ptest.WithRole(flow.RoleConsensus),
    89  		p2ptest.OverrideFlowConfig(cfg))
    90  
    91  	ids := flow.IdentityList{&id1, &id2}
    92  	nodes := []p2p.LibP2PNode{node1, node2}
    93  	// suppressing "peers provider not set error"
    94  	p2ptest.RegisterPeerProviders(t, nodes)
    95  
    96  	provider := id.NewFixedIdentityProvider(ids)
    97  	idProvider.On("ByPeerID", mocktestify.Anything).Return(
    98  		func(peerId peer.ID) *flow.Identity {
    99  			identity, _ := provider.ByPeerID(peerId)
   100  			return identity
   101  		}, func(peerId peer.ID) bool {
   102  			_, ok := provider.ByPeerID(peerId)
   103  			return ok
   104  		})
   105  	p2ptest.StartNodes(t, signalerCtx, nodes)
   106  	defer p2ptest.StopNodes(t, nodes, cancel)
   107  
   108  	p2ptest.LetNodesDiscoverEachOther(t, ctx, nodes, ids)
   109  	blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId)
   110  	// checks end-to-end message delivery works on GossipSub.
   111  	p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} {
   112  		return unittest.ProposalFixture()
   113  	})
   114  
   115  	// simulates node2 spamming node1 with invalid gossipsub control messages until node2 gets dissallow listed.
   116  	// 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
   117  	for i := 0; i < 750; i++ {
   118  		notificationConsumer.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{
   119  			PeerID:  node2.ID(),
   120  			MsgType: p2pmsg.ControlMessageTypes()[rand.Intn(len(p2pmsg.ControlMessageTypes()))],
   121  			Error:   fmt.Errorf("invalid control message"),
   122  		})
   123  	}
   124  
   125  	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))
   126  
   127  	// checks no GossipSub message exchange should no longer happen between node1 and node2.
   128  	p2ptest.EnsureNoPubsubExchangeBetweenGroups(
   129  		t,
   130  		ctx,
   131  		[]p2p.LibP2PNode{node1},
   132  		flow.IdentifierList{id1.NodeID},
   133  		[]p2p.LibP2PNode{node2},
   134  		flow.IdentifierList{id2.NodeID},
   135  		blockTopic,
   136  		1,
   137  		func() interface{} {
   138  			return unittest.ProposalFixture()
   139  		})
   140  }