From 2c7a59107698a51eaf9aa9bfcc3868f5292f1c6b Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Tue, 12 Dec 2023 14:50:16 -0800 Subject: [PATCH 01/12] replaces identity provider --- .../test/gossipsub/scoring/ihave_spam_test.go | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 37524922907..77477e88492 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -16,7 +16,6 @@ import ( "github.com/onflow/flow-go/insecure/corruptlibp2p" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module/irrecoverable" - "github.com/onflow/flow-go/module/mock" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" "github.com/onflow/flow-go/network/p2p/scoring" @@ -34,15 +33,12 @@ import ( // Also, per hearbeat (i.e., decay interval), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter) on aggregate, and // excess messages are dropped (without being counted as broken promises). func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { - - unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky test") - role := flow.RoleConsensus sporkId := unittest.IdentifierFixture() blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) receivedIWants := unittest.NewProtectedMap[string, struct{}]() - idProvider := mock.NewIdentityProvider(t) + idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) spammer := corruptlibp2p.NewGossipSubRouterSpammerWithRpcInspector(t, sporkId, role, idProvider, func(id peer.ID, rpc *corrupt.RPC) error { // override rpc inspector of the spammer node to keep track of the iwants it has received. if rpc.RPC.Control == nil || rpc.RPC.Control.Iwant == nil { @@ -80,9 +76,8 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { }), ) - idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() - idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} + idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} p2ptest.StartNodes(t, signalerCtx, nodes) @@ -168,13 +163,12 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // Second round of attack makes spammers broken promises above the threshold of 10 RPCs, hence a degradation of the spammers score. // Third round of attack makes spammers broken promises to around 20 RPCs above the threshold, which causes the graylisting of the spammer node. func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { - unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky in CI") role := flow.RoleConsensus sporkId := unittest.IdentifierFixture() blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) receivedIWants := unittest.NewProtectedMap[string, struct{}]() - idProvider := mock.NewIdentityProvider(t) + idProvider := unittest.NewUpdatableIDProvider(flow.IdentityList{}) spammer := corruptlibp2p.NewGossipSubRouterSpammerWithRpcInspector(t, sporkId, role, idProvider, func(id peer.ID, rpc *corrupt.RPC) error { // override rpc inspector of the spammer node to keep track of the iwants it has received. if rpc.RPC.Control == nil || rpc.RPC.Control.Iwant == nil { @@ -221,9 +215,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { }), ) - idProvider.On("ByPeerID", victimNode.ID()).Return(&victimIdentity, true).Maybe() - idProvider.On("ByPeerID", spammer.SpammerNode.ID()).Return(&spammer.SpammerId, true).Maybe() ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} + idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} p2ptest.StartNodes(t, signalerCtx, nodes) @@ -253,6 +246,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. // note that we intentionally override the decay speed to be 60-times faster in this test. if behavioralPenalty < 7.5 { + fmt.Println("behavioral penalty", behavioralPenalty) return false } @@ -280,6 +274,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // ideally we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 2 to account for decays and floating point errors. // note that we intentionally override the decay speed to be 60-times faster in this test. if behavioralPenalty < 18 { + fmt.Println("behavioral penalty", behavioralPenalty) return false } From 4347c0a6d130224f35c19f66a309e1149f74a9d9 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Wed, 13 Dec 2023 14:54:31 -0800 Subject: [PATCH 02/12] fixes above threshold test --- .../test/gossipsub/scoring/ihave_spam_test.go | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 77477e88492..a9bee2fb9fc 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -206,7 +206,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { idProvider, p2ptest.OverrideFlowConfig(conf), p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.WithPeerScoreTracerInterval(10*time.Millisecond), // to speed up the test p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, @@ -218,6 +218,13 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} + // to suppress the logs of peer provider has not set + victimNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{spammer.SpammerNode.ID()} + }) + spammer.SpammerNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{victimNode.ID()} + }) p2ptest.StartNodes(t, signalerCtx, nodes) defer p2ptest.StopNodes(t, nodes, cancel) @@ -235,6 +242,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // FIRST ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) + t.Log("first round of attack finished") // wait till victim counts the spam iHaves as broken promises for the second round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { @@ -246,14 +254,15 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. // note that we intentionally override the decay speed to be 60-times faster in this test. if behavioralPenalty < 7.5 { - fmt.Println("behavioral penalty", behavioralPenalty) + t.Logf("[first round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[first round] success on behavioral penalty %f", behavioralPenalty) return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + }, 10*time.Second, 1*time.Second) scoreAfterFirstRound, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "score for spammer node must be present") @@ -263,7 +272,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // SECOND ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) - + t.Log("second round of attack finished") // wait till victim counts the spam iHaves as broken promises for the second round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { behavioralPenalty, ok := victimNode.PeerScoreExposer().GetBehaviourPenalty(spammer.SpammerNode.ID()) @@ -271,27 +280,28 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return false } - // ideally we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 2 to account for decays and floating point errors. + // ideally we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 5 to account for decays and floating point errors. // note that we intentionally override the decay speed to be 60-times faster in this test. - if behavioralPenalty < 18 { - fmt.Println("behavioral penalty", behavioralPenalty) + if behavioralPenalty < 15 { + t.Logf("[second round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[second round] success on behavioral penalty %f", behavioralPenalty) return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + }, 10*time.Second, 1*time.Second) spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") // with the second round of the attack, the spammer is about 10 broken promises above the threshold (total ~20 broken promises, but the first 10 are not counted). - // we expect the score to be dropped to initScore - 10 * 10 * 0.01 * scoring.MaxAppSpecificReward, however, instead of 10, we consider 8 about the threshold, to account for decays. + // we expect the score to be dropped to initScore - 10 * 10 * 0.01 * scoring.MaxAppSpecificReward, however, instead of 10, we consider 5 about the threshold, to account for decays. require.LessOrEqual(t, spammerScore, - initScore-8*8*0.01*scoring.MaxAppSpecificReward, + initScore-5*5*0.01*scoring.MaxAppSpecificReward, "sanity check failed, the score of the spammer node must be less than the initial score minus 8 * 8 * 0.01 * scoring.MaxAppSpecificReward: %f, actual: %f", - initScore-10*10*10-2*scoring.MaxAppSpecificReward, + initScore-5*5*0.1*scoring.MaxAppSpecificReward, spammerScore) require.Greaterf(t, spammerScore, @@ -319,23 +329,25 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // THIRD ROUND OF ATTACK: spammer sends 10 RPCs to the victim node, each containing 500 iHave messages, we expect spammer to be graylisted. spamIHaveBrokenPromise(t, spammer, blockTopic.String(), receivedIWants, victimNode) - + t.Log("third round of attack finished") // wait till victim counts the spam iHaves as broken promises for the third round of attack (one per RPC for a total of 10). require.Eventually(t, func() bool { behavioralPenalty, ok := victimNode.PeerScoreExposer().GetBehaviourPenalty(spammer.SpammerNode.ID()) if !ok { return false } - // ideally we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 3 to account for decays and floating point errors. + // ideally we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 5 to account for decays and floating point errors. // note that we intentionally override the decay speed to be 60-times faster in this test. - if behavioralPenalty < 27 { + if behavioralPenalty < 25 { + t.Logf("[third round] pending on behavioral penalty %f", behavioralPenalty) return false } + t.Logf("[third round] success on behavioral penalty %f", behavioralPenalty) return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + }, 10*time.Second, 1*time.Second) spammerScore, ok = victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") From 6f12ff69baf399e5dcce74e159c29df2f54909bb Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Wed, 13 Dec 2023 15:20:29 -0800 Subject: [PATCH 03/12] adds more description to fixing below threshold --- .../test/gossipsub/scoring/ihave_spam_test.go | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index a9bee2fb9fc..55229f80f7b 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -67,7 +67,7 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { t.Name(), idProvider, p2ptest.WithRole(role), - p2ptest.WithPeerScoreTracerInterval(1*time.Second), + p2ptest.WithPeerScoreTracerInterval(10*time.Millisecond), // to speed up the test p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ TopicScoreParams: map[channels.Topic]*pubsub.TopicScoreParams{ blockTopic: blockTopicOverrideParams, @@ -79,6 +79,13 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} + // to suppress the logs of "peer provider has not set" + victimNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{spammer.SpammerNode.ID()} + }) + spammer.SpammerNode.WithPeersProvider(func() peer.IDSlice { + return peer.IDSlice{victimNode.ID()} + }) p2ptest.StartNodes(t, signalerCtx, nodes) defer p2ptest.StopNodes(t, nodes, cancel) @@ -101,17 +108,19 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { if !ok { return false } - // We set 7.5 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector - // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. - if behavioralPenalty < 7.5 { + // We set 7 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector + // ideally it must be 10 (one per RPC), but we give it a buffer of 3 to account for decays and floating point errors. + if behavioralPenalty < 7 { + t.Logf("pending on behavioral penalty %f", behavioralPenalty) return false } - + t.Logf("success on behavioral penalty %f", behavioralPenalty) initialBehavioralPenalty = behavioralPenalty return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 100*time.Millisecond) + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 1 second. + }, 10*time.Second, 1*time.Second) spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") @@ -218,7 +227,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { ids := flow.IdentityList{&spammer.SpammerId, &victimIdentity} idProvider.SetIdentities(ids) nodes := []p2p.LibP2PNode{spammer.SpammerNode, victimNode} - // to suppress the logs of peer provider has not set + // to suppress the logs of "peer provider has not set" victimNode.WithPeersProvider(func() peer.IDSlice { return peer.IDSlice{spammer.SpammerNode.ID()} }) From 1752fa0587930bc4d0c33100e84627f1889922d3 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Wed, 13 Dec 2023 16:57:56 -0800 Subject: [PATCH 04/12] fixing typo in an error message --- network/p2p/inspector/validation/errors.go | 2 +- network/p2p/inspector/validation/errors_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/network/p2p/inspector/validation/errors.go b/network/p2p/inspector/validation/errors.go index 99d4dad2b61..b08af405a23 100644 --- a/network/p2p/inspector/validation/errors.go +++ b/network/p2p/inspector/validation/errors.go @@ -59,7 +59,7 @@ type DuplicateTopicErr struct { } func (e DuplicateTopicErr) Error() string { - return fmt.Sprintf("duplicate topic foud in %s control message type: %s", e.msgType, e.topic) + return fmt.Sprintf("duplicate topic found in %s control message type: %s", e.msgType, e.topic) } // NewDuplicateTopicErr returns a new DuplicateTopicErr. diff --git a/network/p2p/inspector/validation/errors_test.go b/network/p2p/inspector/validation/errors_test.go index bc43ca38771..b042dd98bcf 100644 --- a/network/p2p/inspector/validation/errors_test.go +++ b/network/p2p/inspector/validation/errors_test.go @@ -29,7 +29,7 @@ func TestErrActiveClusterIDsNotSetRoundTrip(t *testing.T) { // TestErrDuplicateTopicRoundTrip ensures correct error formatting for DuplicateTopicErr. func TestDuplicateTopicErrRoundTrip(t *testing.T) { - expectedErrorMsg := fmt.Sprintf("duplicate topic foud in %s control message type: %s", p2pmsg.CtrlMsgGraft, channels.TestNetworkChannel) + expectedErrorMsg := fmt.Sprintf("duplicate topic found in %s control message type: %s", p2pmsg.CtrlMsgGraft, channels.TestNetworkChannel) err := NewDuplicateTopicErr(channels.TestNetworkChannel.String(), p2pmsg.CtrlMsgGraft) assert.Equal(t, expectedErrorMsg, err.Error(), "the error message should be correctly formatted") // tests the IsDuplicateTopicErr function. From 0215e3a91372d8c3ebd02559cf61ac913b0bf4e6 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Wed, 13 Dec 2023 16:58:06 -0800 Subject: [PATCH 05/12] fixes bug in test --- .../test/gossipsub/scoring/ihave_spam_test.go | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 55229f80f7b..cb38ea60c8a 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -8,6 +8,7 @@ import ( "time" pubsub "github.com/libp2p/go-libp2p-pubsub" + pb "github.com/libp2p/go-libp2p-pubsub/pb" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" corrupt "github.com/yhassanzadeh13/go-libp2p-pubsub" @@ -397,7 +398,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { } // spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises tests. -// It creates and sends 10 RPCs each with 10 iHave messages, each iHave message has 50 message ids, hence overall, we have 5000 iHave message ids. +// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 500 message ids, hence overall, we have 5000 iHave message ids. // It then sends those iHave spams to the victim node and waits till the victim node receives them. // Args: // - t: the test instance. @@ -410,10 +411,23 @@ func spamIHaveBrokenPromise(t *testing.T, topic string, receivedIWants *unittest.ProtectedMap[string, struct{}], victimNode p2p.LibP2PNode) { - spamMsgs := spammer.GenerateCtlMessages(1, p2ptest.WithIHave(1, 500, topic)) + rpcCount := 10 + // we can't send more than one iHave per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic. + // when the node does not have a topic subscription, it will discard the iHave message. + iHavesPerRPC := 1 + // there is a cap on the max iHaves a gossipsub node processes per heartbeat (1 sec), we don't want to exceed that (currently 5000 iHave messages per heartbeat). + messageIdsPerIHave := 100 + spamCtrlMsgs := spammer.GenerateCtlMessages(rpcCount, p2ptest.WithIHave(iHavesPerRPC, messageIdsPerIHave, topic)) + // sanity check + require.Len(t, spamCtrlMsgs, rpcCount) // 10 RPCs var sentIHaves []string - for _, msg := range spamMsgs { + // checks that iHave message ids are not duplicated + for _, msg := range spamCtrlMsgs { + // sanity check + require.Len(t, msg.Ihave, iHavesPerRPC) // 1 iHave message per RPC for _, iHave := range msg.Ihave { + // sanity check + require.Len(t, iHave.MessageIDs, messageIdsPerIHave) // 50 message ids per iHave message for _, msgId := range iHave.MessageIDs { require.NotContains(t, sentIHaves, msgId) sentIHaves = append(sentIHaves, msgId) @@ -424,16 +438,20 @@ func spamIHaveBrokenPromise(t *testing.T, // spams the victim node with spam iHave messages, since iHave messages are for junk message ids, there will be no // reply from spammer to victim over the iWants. Hence, the victim must count this towards 10 broken promises eventually. // This sums up to 10 broken promises (1 per RPC). - var wg sync.WaitGroup - for i := 0; i < 50; i++ { + wg := sync.WaitGroup{} + for i := 0; i < len(spamCtrlMsgs); i++ { wg.Add(1) + i := i // capture the loop variable go func() { defer wg.Done() - spammer.SpamControlMessage(t, victimNode, spamMsgs, p2ptest.PubsubMessageFixture(t, p2ptest.WithTopic(topic))) + spammer.SpamControlMessage(t, victimNode, []pb.ControlMessage{spamCtrlMsgs[i]}) }() + // we wait 100 milliseconds between each RPC to avoid overwhelming the victim node (it may throttle the spammer node). + time.Sleep(100 * time.Millisecond) } - unittest.AssertReturnsBefore(t, wg.Wait, 3*time.Second, "could not send RPCs on time") + unittest.RequireReturnsBefore(t, wg.Wait, 1*time.Second, "sanity check failed, we should have sent all the spam iHaves to the victim node") + // wait till all the spam iHaves are responded with iWants. require.Eventually(t, func() bool { @@ -444,8 +462,7 @@ func spamIHaveBrokenPromise(t *testing.T, } return true - }, - 5*time.Second, + }, 10*time.Second, 100*time.Millisecond, fmt.Sprintf("sanity check failed, we should have received all the iWants for the spam iHaves, expected: %d, actual: %d", len(sentIHaves), receivedIWants.Size())) } From a2f1c00c41e6ba23841cde6cb32083735c86c1ae Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 10:55:44 -0800 Subject: [PATCH 06/12] extends comments --- .../test/gossipsub/scoring/ihave_spam_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index cb38ea60c8a..df284e36883 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -27,11 +27,11 @@ import ( // TestGossipSubIHaveBrokenPromises_Below_Threshold tests that as long as the spammer stays below the ihave spam thresholds, it is not caught and // penalized by the victim node. // The thresholds are: -// Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter). +// Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter; GossipSubMaxIHaveMessages). // Threshold for broken promises of iHave per heartbeat is: 10 (Flow-specific) parameter. It means that GossipSub samples one iHave id out of the -// entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter), then the promise is considered broken. We set +// entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), then the promise is considered broken. We set // this threshold to 10 meaning that the first 10 broken promises are ignored. This is to allow for some network churn. -// Also, per hearbeat (i.e., decay interval), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter) on aggregate, and +// Also, per hearbeat (GossipSubHeartbeatInterval, 1 second ), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter; GossipSubMaxIHaveLength) on aggregate, and // excess messages are dropped (without being counted as broken promises). func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { role := flow.RoleConsensus @@ -397,9 +397,13 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { }) } -// spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises tests. -// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 500 message ids, hence overall, we have 5000 iHave message ids. +// spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises_.* tests. +// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 400 message ids, hence overall, we have 4000 iHave message ids. // It then sends those iHave spams to the victim node and waits till the victim node receives them. +// There are some notes to consider: +// - we can't send more than one iHave per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic in the TestGossipSubIHaveBrokenPromises_.* tests. +// - we can't send more than 10 RPCs containing iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveMessages). Hence, we choose 9 RPCs to always stay below the threshold. +// - we can't send more than 5000 iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveLength). Hence, we choose 400 message ids per iHave message to always stay below the threshold (9 * 400 = 3600). // Args: // - t: the test instance. // - spammer: the spammer node. From 4f2ff3699351e33c9daa24b8e404e67ab5d4ad58 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 16:06:55 -0800 Subject: [PATCH 07/12] fixes spam messages fixture --- .../test/gossipsub/scoring/ihave_spam_test.go | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index df284e36883..5df94cc2c3d 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -398,12 +398,13 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { } // spamIHaveBrokenPromises is a test utility function that is exclusive for the TestGossipSubIHaveBrokenPromises_.* tests. -// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 400 message ids, hence overall, we have 4000 iHave message ids. -// It then sends those iHave spams to the victim node and waits till the victim node receives them. +// It creates and sends 10 RPCs each with 1 iHave message, each iHave message has 500 message ids, hence overall, we have 5000 iHave message ids. +// It then sends those iHave spams to the victim node and waits till the victim node responds with iWants for all the spam iHaves. // There are some notes to consider: -// - we can't send more than one iHave per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic in the TestGossipSubIHaveBrokenPromises_.* tests. -// - we can't send more than 10 RPCs containing iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveMessages). Hence, we choose 9 RPCs to always stay below the threshold. -// - we can't send more than 5000 iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveLength). Hence, we choose 400 message ids per iHave message to always stay below the threshold (9 * 400 = 3600). +// - we can't send more than one iHave message per RPC in this test, as each iHave should have a distinct topic, and we only have one subscribed topic in the TestGossipSubIHaveBrokenPromises_.* tests. +// - we can't send more than 10 RPCs containing iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveMessages). Hence, we choose 10 RPCs to always stay at the threshold. +// - we can't send more than 5000 iHave messages per heartbeat (1 sec). This is a gossipsub parameter (GossipSubMaxIHaveLength). Hence, we choose 500 message ids per iHave message to always stay at the threshold (10 * 500 = 5000). +// - Note that victim nodes picks one iHave id out of the entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), then the promise is considered broken. Hence, broken promises are counted per RPC (not per iHave message). // Args: // - t: the test instance. // - spammer: the spammer node. @@ -420,18 +421,20 @@ func spamIHaveBrokenPromise(t *testing.T, // when the node does not have a topic subscription, it will discard the iHave message. iHavesPerRPC := 1 // there is a cap on the max iHaves a gossipsub node processes per heartbeat (1 sec), we don't want to exceed that (currently 5000 iHave messages per heartbeat). - messageIdsPerIHave := 100 + messageIdsPerIHave := 500 spamCtrlMsgs := spammer.GenerateCtlMessages(rpcCount, p2ptest.WithIHave(iHavesPerRPC, messageIdsPerIHave, topic)) + // sanity check - require.Len(t, spamCtrlMsgs, rpcCount) // 10 RPCs + require.Len(t, spamCtrlMsgs, rpcCount) var sentIHaves []string + // checks that iHave message ids are not duplicated for _, msg := range spamCtrlMsgs { // sanity check - require.Len(t, msg.Ihave, iHavesPerRPC) // 1 iHave message per RPC + require.Len(t, msg.Ihave, iHavesPerRPC) for _, iHave := range msg.Ihave { // sanity check - require.Len(t, iHave.MessageIDs, messageIdsPerIHave) // 50 message ids per iHave message + require.Len(t, iHave.MessageIDs, messageIdsPerIHave) for _, msgId := range iHave.MessageIDs { require.NotContains(t, sentIHaves, msgId) sentIHaves = append(sentIHaves, msgId) @@ -441,6 +444,8 @@ func spamIHaveBrokenPromise(t *testing.T, // spams the victim node with spam iHave messages, since iHave messages are for junk message ids, there will be no // reply from spammer to victim over the iWants. Hence, the victim must count this towards 10 broken promises eventually. + // Note that victim nodes picks one iHave id out of the entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), + // then the promise is considered broken. Hence, broken promises are counted per RPC (not per iHave message). // This sums up to 10 broken promises (1 per RPC). wg := sync.WaitGroup{} for i := 0; i < len(spamCtrlMsgs); i++ { @@ -450,7 +455,8 @@ func spamIHaveBrokenPromise(t *testing.T, defer wg.Done() spammer.SpamControlMessage(t, victimNode, []pb.ControlMessage{spamCtrlMsgs[i]}) }() - // we wait 100 milliseconds between each RPC to avoid overwhelming the victim node (it may throttle the spammer node). + // we wait 100 milliseconds between each RPC to add an artificial delay between RPCs; this is to reduce the chance that all RPCs arrive in the same heartbeat, hence + // victim node dropping some. time.Sleep(100 * time.Millisecond) } From b21a8670096f1b42a36d1af00daec5fa476887ac Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 16:48:26 -0800 Subject: [PATCH 08/12] extends a comment and makes the spammer loop sequential --- .../test/gossipsub/scoring/ihave_spam_test.go | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 5df94cc2c3d..4c0c28da62a 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -3,7 +3,6 @@ package scoring import ( "context" "fmt" - "sync" "testing" "time" @@ -260,9 +259,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { if !ok { return false } - // We set 7.5 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector - // ideally it must be 10 (one per RPC), but we give it a buffer of 1 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. + + // ideally, we should have 10, but we give it a buffer of 2.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. if behavioralPenalty < 7.5 { t.Logf("[first round] pending on behavioral penalty %f", behavioralPenalty) return false @@ -290,8 +288,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return false } - // ideally we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 5 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. + // ideally, we should have 20 (10 from the first round, 10 from the second round), but we give it a buffer of 5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. if behavioralPenalty < 15 { t.Logf("[second round] pending on behavioral penalty %f", behavioralPenalty) return false @@ -346,9 +343,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { if !ok { return false } - // ideally we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 5 to account for decays and floating point errors. - // note that we intentionally override the decay speed to be 60-times faster in this test. - if behavioralPenalty < 25 { + // ideally, we should have 30 (10 from the first round, 10 from the second round, 10 from the third round), but we give it a buffer of 7.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. + if behavioralPenalty < 22.5 { t.Logf("[third round] pending on behavioral penalty %f", behavioralPenalty) return false } @@ -447,21 +443,13 @@ func spamIHaveBrokenPromise(t *testing.T, // Note that victim nodes picks one iHave id out of the entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), // then the promise is considered broken. Hence, broken promises are counted per RPC (not per iHave message). // This sums up to 10 broken promises (1 per RPC). - wg := sync.WaitGroup{} for i := 0; i < len(spamCtrlMsgs); i++ { - wg.Add(1) - i := i // capture the loop variable - go func() { - defer wg.Done() - spammer.SpamControlMessage(t, victimNode, []pb.ControlMessage{spamCtrlMsgs[i]}) - }() - // we wait 100 milliseconds between each RPC to add an artificial delay between RPCs; this is to reduce the chance that all RPCs arrive in the same heartbeat, hence + spammer.SpamControlMessage(t, victimNode, []pb.ControlMessage{spamCtrlMsgs[i]}) + // we wait 50 milliseconds between each RPC to add an artificial delay between RPCs; this is to reduce the chance that all RPCs arrive in the same heartbeat, hence // victim node dropping some. - time.Sleep(100 * time.Millisecond) + time.Sleep(50 * time.Millisecond) } - unittest.RequireReturnsBefore(t, wg.Wait, 1*time.Second, "sanity check failed, we should have sent all the spam iHaves to the victim node") - // wait till all the spam iHaves are responded with iWants. require.Eventually(t, func() bool { From 5b33695a40680aa9bf3293ce8281dd400511b71b Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 16:59:08 -0800 Subject: [PATCH 09/12] extends comments --- .../functional/test/gossipsub/scoring/ihave_spam_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 4c0c28da62a..73f98b36158 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -108,9 +108,8 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { if !ok { return false } - // We set 7 as the threshold to compensate for the scoring decay in between RPC's being processed by the inspector - // ideally it must be 10 (one per RPC), but we give it a buffer of 3 to account for decays and floating point errors. - if behavioralPenalty < 7 { + // ideally, we should have 10, but we give it a buffer of 2.5 to account for 25% discrepancy due to scoring decays, timer asynchrony, and floating point errors. + if behavioralPenalty < 7.5 { t.Logf("pending on behavioral penalty %f", behavioralPenalty) return false } From 69e4539d1f565d8210525f13dccae307d58e419d Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 17:04:11 -0800 Subject: [PATCH 10/12] extends godocs --- .../functional/test/gossipsub/scoring/ihave_spam_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 73f98b36158..bf5d1c55067 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -26,11 +26,11 @@ import ( // TestGossipSubIHaveBrokenPromises_Below_Threshold tests that as long as the spammer stays below the ihave spam thresholds, it is not caught and // penalized by the victim node. // The thresholds are: -// Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter; GossipSubMaxIHaveMessages). -// Threshold for broken promises of iHave per heartbeat is: 10 (Flow-specific) parameter. It means that GossipSub samples one iHave id out of the +// - Maximum messages that include iHave per heartbeat is: 10 (gossipsub parameter; GossipSubMaxIHaveMessages), after which iHave messages are dropped. +// - Threshold for broken promises of iHave per heartbeat is: 10 (Flow parameter; defaultBehaviourPenaltyThreshold). It means that GossipSub samples one iHave id out of the // entire RPC and if that iHave id is not eventually delivered within 3 seconds (gossipsub parameter, GossipSubIWantFollowupTime), then the promise is considered broken. We set // this threshold to 10 meaning that the first 10 broken promises are ignored. This is to allow for some network churn. -// Also, per hearbeat (GossipSubHeartbeatInterval, 1 second ), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter; GossipSubMaxIHaveLength) on aggregate, and +// - Per hearbeat (gossipsub parameter GossipSubHeartbeatInterval, 1 second ), the spammer is allowed to send at most 5000 ihave messages (gossip sub parameter; GossipSubMaxIHaveLength) on aggregate, and // excess messages are dropped (without being counted as broken promises). func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { role := flow.RoleConsensus From 3c5fb1f98a4499eda972e2f1826cfe5238400ba6 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 17:10:12 -0800 Subject: [PATCH 11/12] fixes merge conflicts --- .../test/gossipsub/scoring/ihave_spam_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index 625a41489bf..b370a1af822 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -66,7 +66,8 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { require.NoError(t, err) // we override the decay interval to 1 second so that the score is updated within 1 second intervals. conf.NetworkConfig.GossipSub.ScoringParameters.DecayInterval = 1 * time.Second - conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + // score tracer interval is set to 500 milliseconds to speed up the test, it should be shorter than the heartbeat interval (1 second) of gossipsub to catch the score updates in time. + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 500 * time.Millisecond victimNode, victimIdentity := p2ptest.NodeFixture( t, sporkId, @@ -123,8 +124,8 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 1 second. - }, 10*time.Second, 1*time.Second) + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. + }, 10*time.Second, 500*time.Millisecond) spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") @@ -159,7 +160,7 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { } return true - }, 2*time.Second, 100*time.Millisecond, "sanity check failed, the spammer behavioral counter must be decayed after a heartbeat") + }, 2*time.Second, 500*time.Millisecond, "sanity check failed, the spammer behavioral counter must be decayed after a heartbeat") // since spammer stays below the threshold, it should be able to exchange messages with the victim node over pubsub. p2ptest.EnsurePubsubMessageExchange(t, ctx, nodes, blockTopic, 1, func() interface{} { @@ -204,7 +205,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MaxMessageIDSampleSize = 10000 // we override the decay interval to 1 second so that the score is updated within 1 second intervals. conf.NetworkConfig.GossipSub.ScoringParameters.DecayInterval = 1 * time.Second - conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second + // score tracer interval is set to 500 milliseconds to speed up the test, it should be shorter than the heartbeat interval (1 second) of gossipsub to catch the score updates in time. + conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 500 * time.Millisecond ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) @@ -275,7 +277,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 1*time.Second) + }, 10*time.Second, 500*time.Millisecond) scoreAfterFirstRound, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "score for spammer node must be present") @@ -303,7 +305,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 1*time.Second) + }, 10*time.Second, 500*time.Millisecond) spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") @@ -358,7 +360,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { return true // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 // seconds to be on the safe side. - }, 10*time.Second, 1*time.Second) + }, 10*time.Second, 500*time.Millisecond) spammerScore, ok = victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") From 2dea740594b1e26ad0561f867819f677fc37cc63 Mon Sep 17 00:00:00 2001 From: Yahya Hassanzadeh Date: Thu, 14 Dec 2023 17:30:46 -0800 Subject: [PATCH 12/12] extends comments --- .../test/gossipsub/scoring/ihave_spam_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go index b370a1af822..e11951cc7dd 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -171,8 +171,8 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // TestGossipSubIHaveBrokenPromises_Above_Threshold tests that a continuous stream of spam iHave broken promises will // eventually cause the spammer node to be graylisted (i.e., no incoming RPCs from the spammer node will be accepted, and // no outgoing RPCs to the spammer node will be sent). -// The test performs 3 rounds of attacks: each round with 10 RPCs, each RPC with 10 iHave messages, each iHave message with 50 message ids, hence overall, we have 5000 iHave message ids. -// Note that based on GossipSub parameters 5000 iHave is the most one can send within one decay interval. +// The test performs 3 rounds of attacks: each round with 10 RPCs, each RPC with 1 iHave messages, each iHave message with 500 message ids, hence overall, we have 5000 iHave message ids. +// Note that based on GossipSub parameters 5000 iHave is the most one can send within one heart beat. // First round of attack makes spammers broken promises still below the threshold of 10 RPCs (broken promises are counted per RPC), hence no degradation of the spammers score. // Second round of attack makes spammers broken promises above the threshold of 10 RPCs, hence a degradation of the spammers score. // Third round of attack makes spammers broken promises to around 20 RPCs above the threshold, which causes the graylisting of the spammer node. @@ -275,8 +275,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { t.Logf("[first round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. }, 10*time.Second, 500*time.Millisecond) scoreAfterFirstRound, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) @@ -303,8 +303,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { t.Logf("[second round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. }, 10*time.Second, 500*time.Millisecond) spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) @@ -358,8 +358,8 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { t.Logf("[third round] success on behavioral penalty %f", behavioralPenalty) return true - // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 - // seconds to be on the safe side. + // Note: we have to wait at least 3 seconds for an iHave to be considered as broken promise (gossipsub parameters), we set it to 10 seconds to be on the safe side. + // Also, the internal heartbeat of GossipSub is 1 second, hence, there is no need to have ticks shorter than 500 milliseconds. }, 10*time.Second, 500*time.Millisecond) spammerScore, ok = victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID())