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 }