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 }