From 6aba145fd158cdbd18737de8ad5eeb81c769f84e Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 5 Jan 2024 00:56:26 -0500 Subject: [PATCH 01/19] add config and cli flags for all score option configurations --- config/default-config.yml | 242 +++++++++++++++++++++++ network/netconf/flags.go | 123 ++++++++++++ network/p2p/config/gossipsub.go | 1 + network/p2p/config/score_option.go | 296 +++++++++++++++++++++++++++++ 4 files changed, 662 insertions(+) create mode 100644 network/p2p/config/score_option.go diff --git a/config/default-config.yml b/config/default-config.yml index 85a1ba18c02..2993060cb6e 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -227,6 +227,248 @@ network-config: penalty-decay-evaluation-period: 10m # the intervals at which counters associated with a peer behavior in gossipsub system are decayed. decay-interval: 1m + score-option: + # The weight for app-specific scores. + # It is used to scale the app-specific scores to the same range as the other scores. + # At the current version, we don't distinguish between the app-specific scores + # and the other scores, so we set it to 1. + app-specific-score-weight: 1 + # The max number of debug/trace log events per second. + # Logs emitted above this threshold are dropped. + max-debug-logs: 50 + # The default decay interval for the overall score of a peer at the GossipSub scoring + # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + # and is not too long so that a well-behaved node can't recover from a penalty. + decay-interval: 1m + # The default decay to zero for the overall score of a peer at the GossipSub scoring system. + # It defines the maximum value below which a peer scoring counter is reset to zero. + # This is to prevent the counter from decaying to a very small value. + # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + # for a long time, and we can reset the counter. + decay-to-zero: 0.01 + penalties: + # This is the maximum penalty for severe offenses that we apply + # to a remote node score. The score mechanism of GossipSub in Flow is designed + # in a way that all other infractions are penalized with a fraction of this value. + # We have also set the other parameters such as GraylistThreshold, + # GossipThreshold, and PublishThreshold to be a bit higher than this, + # i.e., -100 + 1. This ensures that a node with a score of + # -100 will be graylisted (i.e., all incoming and outgoing RPCs + # are rejected) and will not be able to publish or gossip any messages. + max-app-specific-penalty: -100 + min-app-specific-penalty: -1 + # This is the penalty for unknown identity. It is + # applied to the peer's score when the peer is not in the identity list. + unknown-identity-penalty: -100 + # This is the penalty for invalid subscription. + # It is applied to the peer's score when the peer subscribes to a topic that it is + # not authorized to subscribe to. + invalid-subscription-penalty: -100 + rewards: + # This is the reward for well-behaving staked peers. + # If a peer does not have any misbehavior record, e.g., invalid subscription, + # invalid message, etc., it will be rewarded with this score. + max-app-specific-reward: 100 + # This is the reward for staking peers. It is applied + # to the peer's score when the peer does not have any misbehavior record, e.g., + # invalid subscription, invalid message, etc. The purpose is to reward the staking + # peers for their contribution to the network and prioritize them in neighbor selection. + staked-identity-reward: 100 + thresholds: + # This is the threshold when a peer's penalty drops below this threshold, no gossip + # is emitted towards that peer and gossip from that peer is ignored. + # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all gossips to and from peers with penalty -100 are ignored. + gossip-threshold: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # self-published messages are not propagated towards this peer. + # Validation Constraint: + # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are deprived of receiving any published messages. + publish-threshold: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. + # Validation Constraint: + # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are graylisted. + graylist-threshold: -99 + # This is the threshold when a peer sends us PX information with a prune, + # we only accept it and connect to the supplied peers if the originating peer's + # penalty exceeds this threshold. + # Validation Constraint: must be non-negative. + # How we use it: As the current max reward is 100, we set the threshold to 99 + # so that we only receive supplied peers from well-behaved peers. + accept-px-threshold: 99 + # This is the threshold when the median peer penalty in the mesh drops + # below this value, the peer may select more peers with penalty above the median + # to opportunistically graft on the mesh. + # Validation Constraint: must be non-negative. + # How we use it: We set it to the -100 + 1 so that we only + # opportunistically graft peers that are not access nodes (i.e., with -1), + # or penalized peers (i.e., with -100). + opportunistic-graft-threshold: 101 + behaviour: + # The threshold when the behavior of a peer is considered as bad by GossipSub. + # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every + # excess RPC containing iHave broken promises. + # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system). + penalty-threshold: 10 + # The weight for applying penalty when a peer misbehavior goes beyond the threshold. + # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. + # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose + # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. + # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + # + # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty + # if misbehavior > threshold + sqrt(2) * 10. + # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that + # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. + # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of + # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality + # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that are reported by the engines). + penalty-weight: -1 + # The decay interval for the misbehavior counter of a peer. The misbehavior counter is + # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + # + # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. + # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. + # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. + # + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that is based on the engines report). + penalty-decay: 0.99 + topic: + # This is the default value for the skip atomic validation flag for topics. + # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the + # topic parameters at their default values, i.e., zero. This is because we are not setting all + # topic parameters at the current implementation. + skip-atomic-validation: true + # This value is applied to the square of the number of invalid message deliveries on a topic. + # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + # publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid + # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. + # The supporting math is as follows: + # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). + # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. + # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). + # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. + invalid-message-deliveries-weight: -1.0 + # The decay factor used to decay the number of invalid message deliveries. + # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + # sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will + # decay by 1% at each heartbeat interval. + # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). + invalid-message-deliveries-decay: 0.99 + # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge + # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is + # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. + # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that + # are churners, i.e., peers that join and leave a topic mesh frequently. + time-in-mesh-quantum: 1h + # The default weight of a topic in the GossipSub scoring system. + # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. + topic-weight: 1.0 + # This is applied to the number of actual message deliveries in a topic mesh + # at each decay interval (i.e., defaultDecayInterval). + # It is used to decay the number of actual message deliveries, and prevents past message + # deliveries from affecting the current score of the peer. + # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message + # deliveries will decay by 50% at each decay interval. + mesh-message-deliveries-decay: 0.5 + # The maximum number of actual message deliveries in a topic + # mesh that is used to calculate the score of a peer in that topic mesh. + # We set it to 1000, which means that the maximum number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. + # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual + # message deliveries and also affect the score of the peer in other topic meshes. + # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic + # mesh will not be affected by the actual message deliveries in that topic mesh. + # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh + # and then start under-performing in that topic mesh without being penalized. + mesh-message-deliveries-cap: 1000 + # The threshold for the number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh. + # If the number of actual message deliveries in a topic mesh is less than this value, + # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). + # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the + # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer + # in that topic mesh. + mesh-message-deliveries-threshold: 100 + # The weight for applying penalty when a peer is under-performing in a topic mesh. + # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer + # is not delivering any message in a topic mesh, it will not be disconnected. + # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked + # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh + # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. + # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose + # its MaxAppSpecificReward that is awarded by our app-specific scoring function. + # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be + # causing a false positive network partition. + mesh-deliveries-weight: -0.0005 + # The window size is time interval that we count a delivery of an already + # seen message towards the score of a peer in a topic mesh. The delivery is counted + # by GossipSub only if the previous sender of the message is different from the current sender. + # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. + # It means that if a peer delivers a message that it has already seen less than one minute ago, + # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. + # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not + # be counted towards the actual message deliveries of a peer in a topic mesh. + mesh-message-deliveries-window: 1m + # The time interval that we wait for a new peer that joins a topic mesh + # till start counting the number of actual message deliveries of that peer in that topic mesh. + # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting + # the number of actual message deliveries of a peer in a topic mesh. + # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the + # number of actual message deliveries of a peer in a topic mesh. This is to account for + # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. + mesh-message-delivery-activation: 2m subscription-provider: # The interval for updating the list of subscribed peers to all topics in gossipsub. This is used to keep track of subscriptions # violations and penalize peers accordingly. Recommended value is in the order of a few minutes to avoid contentions; as the operation diff --git a/network/netconf/flags.go b/network/netconf/flags.go index f782e4e6eaf..a0920af60e3 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -109,6 +109,36 @@ func AllFlagNames() []string { BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayRateReductionFactorKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), } for _, scope := range []string{systemScope, transientScope, protocolScope, peerScope, peerProtocolScope} { @@ -279,6 +309,99 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), config.GossipSub.ScoringParameters.DecayInterval, "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), + config.GossipSub.ScoringParameters.ScoreOption.AppSpecificScoreWeight, + "the weight for app-specific scores") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), + config.GossipSub.ScoringParameters.ScoreOption.MaxDebugLogs, + "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), + config.GossipSub.ScoringParameters.ScoreOption.DecayInterval, + "the decay interval for the overall score of a peer at the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), + config.GossipSub.ScoringParameters.ScoreOption.DecayToZero, + "the decay to zero for the overall score of a peer at the GossipSub scoring system") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), + config.GossipSub.ScoringParameters.ScoreOption.Penalties.MaxAppSpecificPenalty, + "the maximum penalty for sever offenses that we apply to a remote node score") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), + config.GossipSub.ScoringParameters.ScoreOption.Penalties.MinAppSpecificPenalty, + "the minimum penalty for sever offenses that we apply to a remote node score") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), + config.GossipSub.ScoringParameters.ScoreOption.Penalties.UnknownIdentityPenalty, + "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), + config.GossipSub.ScoringParameters.ScoreOption.Penalties.InvalidSubscriptionPenalty, + "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), + config.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward, + "the reward for well-behaving staked peers") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), + config.GossipSub.ScoringParameters.ScoreOption.Rewards.StakedIdentityReward, + "the reward for staking peers") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.GossipThreshold, + "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.PublishThreshold, + "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.GraylistThreshold, + "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.AcceptPXThreshold, + "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.OpportunisticGraftThreshold, + "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") + + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyThreshold, + "the threshold when the behavior of a peer is considered as bad by GossipSub") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyWeight, + "the weight for applying penalty when a peer misbehavior goes beyond the threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyDecay, + "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") + + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.SkipAtomicValidation, + "the default value for the skip atomic validation flag for topics") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, + "this value is applied to the square of the number of invalid message deliveries on a topic") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, + "the decay factor used to decay the number of invalid message deliveries") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TimeInMeshQuantum, + "the time in mesh quantum for the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TopicWeight, + "the weight of a topic in the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, + "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, + "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, + "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshDeliveriesWeight, + "the weight for applying penalty when a peer is under-performing in a topic mesh") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, + "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } // LoadLibP2PResourceManagerFlags loads all CLI flags for the libp2p resource manager configuration on the provided pflag set. diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index 95b6f48e8f6..b0a85d7938f 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -76,6 +76,7 @@ type GossipSubParameters struct { PeerScoringEnabled bool `mapstructure:"peer-scoring-enabled"` SubscriptionProvider SubscriptionProviderParameters `mapstructure:"subscription-provider"` ScoringParameters ScoringParameters `mapstructure:"scoring-parameters"` + ScoreOption ScoreOption `mapstructure:"score-option"` } const ( diff --git a/network/p2p/config/score_option.go b/network/p2p/config/score_option.go new file mode 100644 index 00000000000..15bc5f300a2 --- /dev/null +++ b/network/p2p/config/score_option.go @@ -0,0 +1,296 @@ +package p2pconfig + +import "time" + +const ( + ScoreOptionKey = "score-option" + AppScoreWeightKey = "app-specific-score-weight" + MaxDebugLogsKey = "max-debug-logs" + ScoreOptionDecayIntervalKey = "decay-interval" + ScoreOptionDecayToZeroKey = "decay-to-zero" + ScoreOptionPenaltiesKey = "penalties" + ScoreOptionRewardsKey = "rewards" + ScoreOptionThresholdsKey = "thresholds" + ScoreOptionBehaviourKey = "behaviour" + ScoreOptionTopicKey = "topic" +) + +// ScoreOption gossipsub scoring option configuration parameters. +type ScoreOption struct { + // AppSpecificScoreWeight is the weight for app-specific scores. It is used to scale the app-specific + // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific + // scores and the other scores, so we set it to 1. + AppSpecificScoreWeight float64 `validate:"gt=0,lte=1" mapstructure:"app-specific-score-weight"` + // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above + // this threshold are dropped. + MaxDebugLogs int `validate:"lte=50" mapstructure:"max-debug-logs"` + // DecayInterval is the decay interval for the overall score of a peer at the GossipSub scoring + // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + // and is not too long so that a well-behaved node can't recover from a penalty. + DecayInterval time.Duration `validate:"gte=1m" mapstructure:"decay-interval"` + // DecayToZero is the decay to zero for the overall score of a peer at the GossipSub scoring system. + // It defines the maximum value below which a peer scoring counter is reset to zero. + // This is to prevent the counter from decaying to a very small value. + // The value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + // for a long time, and we can reset the counter. + DecayToZero float64 `validate:"required" mapstructure:"decay-to-zero"` + Penalties ScoreOptionPenalties `validate:"required" mapstructure:"penalties"` + Rewards ScoreOptionRewards `validate:"required" mapstructure:"rewards"` + Thresholds ScoreOptionThresholds `validate:"required" mapstructure:"thresholds"` + Behaviour ScoreOptionBehaviour `validate:"required" mapstructure:"behaviour"` + TopicValidation ScoreOptionTopicValidation `validate:"required" mapstructure:"topic-validation"` +} + +const ( + MaxAppSpecificPenaltyKey = "max-app-specific-penalty" + MinAppSpecificPenaltyKey = "min-app-specific-penalty" + UnknownIdentityPenaltyKey = "unknown-identity-penalty" + InvalidSubscriptionPenaltyKey = "invalid-subscription-penalty" +) + +// ScoreOptionPenalties score option penalty configuration parameters. +type ScoreOptionPenalties struct { + // MaxAppSpecificPenalty the maximum penalty for sever offenses that we apply to a remote node score. The score + // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of + // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to + // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty + // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. + MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` + // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. + MinAppSpecificPenalty int `validate:"lt=0" mapstructure:"min-app-specific-penalty"` + // UnknownIdentityPenalty is the penalty for unknown identity. It is applied to the peer's score when + // the peer is not in the identity list. + UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity-penalty"` + // InvalidSubscriptionPenalty is the penalty for invalid subscription. It is applied to the peer's score when + // the peer subscribes to a topic that it is not authorized to subscribe to. + InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription-penalty"` +} + +const ( + MaxAppSpecificRewardKey = "max-app-specific-reward" + StakedIdentityRewardKey = "staked-identity-reward" +) + +// ScoreOptionRewards score option rewards configuration parameters. +type ScoreOptionRewards struct { + // MaxAppSpecificReward is the reward for well-behaving staked peers. If a peer does not have + // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. + MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific-reward"` + // StakedIdentityReward is the reward for staking peers. It is applied to the peer's score when + // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. + // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. + StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity-reward"` +} + +const ( + GossipThresholdKey = "gossip-threshold" + PublishThresholdKey = "publish-threshold" + GraylistThresholdKey = "graylist-threshold" + AcceptPXThresholdKey = "accept-px-threshold" + OpportunisticGraftThresholdKey = "opportunistic-graft-threshold" +) + +// ScoreOptionThresholds score option threshold configuration parameters. +type ScoreOptionThresholds struct { + // GossipThreshold when a peer's penalty drops below this threshold, + // no gossip is emitted towards that peer and gossip from that peer is ignored. + GossipThreshold float64 `validate:"lt=0" mapstructure:"gossip-threshold"` + // PublishThreshold when a peer's penalty drops below this threshold, + // self-published messages are not propagated towards this peer. + PublishThreshold float64 `validate:"lt=0" mapstructure:"publish-threshold"` + // GraylistThreshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., + // incoming RPCs from the peer are ignored. + GraylistThreshold float64 `validate:"lt=0" mapstructure:"graylist-threshold"` + // AcceptPXThreshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied + // peers if the originating peer's penalty exceeds this threshold. + AcceptPXThreshold float64 `validate:"gt=0" mapstructure:"accept-px-threshold"` + // OpportunisticGraftThreshold when the median peer penalty in the mesh drops below this value, + // the peer may select more peers with penalty above the median to opportunistically graft on the mesh. + OpportunisticGraftThreshold float64 `validate:"gt=0" mapstructure:"opportunistic-graft-threshold"` +} + +const ( + BehaviourPenaltyThresholdKey = "penalty-threshold" + BehaviourPenaltyWeightKey = "penalty-weight" + BehaviourPenaltyDecayKey = "penalty-decay" +) + +// ScoreOptionBehaviour score option behaviour configuration parameters. +type ScoreOptionBehaviour struct { + // BehaviourPenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. + // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + // for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + // When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by BehaviorPenaltyWeight (see below) for the excess misbehavior. + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + // + // We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every + // excess RPC containing iHave broken promises. + // The counter is also decayed by (0.99) every decay interval (DecayInterval) i.e., every minute. + // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + // the ALSP system). + BehaviourPenaltyThreshold int `validate:"gt=0" mapstructure:"penalty-threshold"` + // BehaviourPenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. + // Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + // This is detected by the GossipSub scoring system, and the peer is penalized by BehaviorPenaltyWeight. + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + // + // The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. + // We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose + // its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by . + // Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + // + // The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty + // if misbehavior > threshold + sqrt(2) * 10. + // As shown above, with this choice of BehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + // is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that + // the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. + // Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of + // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + // where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality + // to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. + // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + // the ALSP system that are reported by the engines). + BehaviourPenaltyWeight float64 `validate:"lt=0" mapstructure:"penalty-weight"` + // BehaviourPenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is + // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + // + // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + // For iHave broken promises, the gossipsub scoring works as follows: + // It samples ONLY A SINGLE iHave out of the entire RPC. + // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + // This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + // That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + // + // The misbehavior counter is decayed per decay interval (i.e., DecayInterval = 1 minute) by GossipSub. + // We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. + // With the generous threshold that we set (i.e., BehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + // We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. + // + // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + // the ALSP system that is based on the engines report). + BehaviourPenaltyDecay float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay"` +} + +const ( + SkipAtomicValidationKey = "skip-atomic-validation" + InvalidMessageDeliveriesWeightKey = "invalid-message-deliveries-weight" + InvalidMessageDeliveriesDecayKey = "invalid-message-deliveries-decay" + TimeInMeshQuantumKey = "time-in-mesh-quantum" + TopicWeightKey = "topic-weight" + MeshMessageDeliveriesDecayKey = "mesh-message-deliveries-decay" + MeshMessageDeliveriesCapKey = "mesh-message-deliveries-cap" + MeshMessageDeliveryThresholdKey = "mesh-message-deliveries-threshold" + MeshDeliveriesWeightKey = "mesh-deliveries-weight" + MeshMessageDeliveriesWindowKey = "mesh-message-deliveries-window" + MeshMessageDeliveryActivationKey = "mesh-message-delivery-activation" +) + +// ScoreOptionTopicValidation score option topic validation configuration parameters. +type ScoreOptionTopicValidation struct { + // SkipAtomicValidation is the value for the skip atomic validation flag for topics. + // We set it to true, which means gossipsub parameter validation will not fail if we leave some of the + // topic parameters at their values, i.e., zero. This is because we are not setting all + // topic parameters at the current implementation. + SkipAtomicValidation bool `validate:"required" mapstructure:"skip-atomic-validation"` + // InvalidMessageDeliveriesWeight this value is applied to the square of the number of invalid message deliveries on a topic. + // It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + // publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid + // message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. + // The supporting math is as follows: + // - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., StakedIdentityReward). + // - x invalid message deliveries will result in a penalty of x^2 * InvalidMessageDeliveriesWeight, i.e., -x^2. + // - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). + // - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/InvalidMessageDeliveriesWeight) ~ 14. + InvalidMessageDeliveriesWeight float64 `validate:"lt=0" mapstructure:"invalid-message-deliveries-weight"` + // InvalidMessageDeliveriesDecay decay factor used to decay the number of invalid message deliveries. + // The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + // decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + // sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will + // decay by 1% at each heartbeat interval. + // The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (DecayInterval). + InvalidMessageDeliveriesDecay float64 `validate:"gt=0,lt=1" mapstructure:"invalid-message-deliveries-decay"` + // TimeInMeshQuantum is the time in mesh quantum for the GossipSub scoring system. It is used to gauge + // a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is + // in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. + // The reason of setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that + // are churners, i.e., peers that join and leave a topic mesh frequently. + TimeInMeshQuantum time.Duration `validate:"gte=1h" mapstructure:"time-in-mesh-quantum"` + // Weight is the weight of a topic in the GossipSub scoring system. + // The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + // We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. + TopicWeight float64 `validate:"gt=0" mapstructure:"topic-weight"` + // MeshMessageDeliveriesDecay is applied to the number of actual message deliveries in a topic mesh + // at each decay interval (i.e., DecayInterval). + // It is used to decay the number of actual message deliveries, and prevents past message + // deliveries from affecting the current score of the peer. + // As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message + // deliveries will decay by 50% at each decay interval. + MeshMessageDeliveriesDecay float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-decay"` + // MeshMessageDeliveriesCap is the maximum number of actual message deliveries in a topic + // mesh that is used to calculate the score of a peer in that topic mesh. + // We set it to 1000, which means that the maximum number of actual message deliveries in a + // topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. + // This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual + // message deliveries and also affect the score of the peer in other topic meshes. + // When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic + // mesh will not be affected by the actual message deliveries in that topic mesh. + // Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh + // and then start under-performing in that topic mesh without being penalized. + MeshMessageDeliveriesCap int `validate:"gt=0" mapstructure:"mesh-message-deliveries-cap"` + // MeshMessageDeliveryThreshold is the threshold for the number of actual message deliveries in a + // topic mesh that is used to calculate the score of a peer in that topic mesh. + // If the number of actual message deliveries in a topic mesh is less than this value, + // the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + // i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + // threshold, respectively, and `w` is the weight (i.e., MeshMessageDeliveriesWeight). + // We set it to 0.1 * MeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the + // maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer + // in that topic mesh. + MeshMessageDeliveryThreshold float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-threshold"` + // MeshDeliveriesWeight is the weight for applying penalty when a peer is under-performing in a topic mesh. + // Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + // (i.e., MeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + // message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + // `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + // We set this value to be - 0.05 MaxAppSpecificReward / (MeshMessageDeliveriesThreshold^2). This guarantees that even if a peer + // is not delivering any message in a topic mesh, it will not be disconnected. + // Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked + // nodes by will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh + // will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. + // It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose + // its MaxAppSpecificReward that is awarded by our app-specific scoring function. + // At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be + // causing a false positive network partition. + // TODO: we must increase the penalty for under-performing in a topic mesh in the future, and disconnect the peer if it is under-performing. + MeshDeliveriesWeight float64 `validate:"lt=0" mapstructure:"mesh-deliveries-weight"` + // MeshMessageDeliveriesWindow is the window size is time interval that we count a delivery of an already + // seen message towards the score of a peer in a topic mesh. The delivery is counted + // by GossipSub only if the previous sender of the message is different from the current sender. + // We set it to the decay interval of the GossipSub scoring system, which is 1 minute. + // It means that if a peer delivers a message that it has already seen less than one minute ago, + // the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. + // This also prevents replay attacks of messages that are older than one minute. As replayed messages will not + // be counted towards the actual message deliveries of a peer in a topic mesh. + MeshMessageDeliveriesWindow time.Duration `validate:"gte=1m" mapstructure:"mesh-message-deliveries-window"` + // MeshMessageDeliveryActivation is the time interval that we wait for a new peer that joins a topic mesh + // till start counting the number of actual message deliveries of that peer in that topic mesh. + // We set it to 2 * DecayInterval, which means that we wait for 2 decay intervals before start counting + // the number of actual message deliveries of a peer in a topic mesh. + // With a decay interval of 1 minute, it means that we wait for 2 minutes before start counting the + // number of actual message deliveries of a peer in a topic mesh. This is to account for + // the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. + MeshMessageDeliveryActivation time.Duration `validate:"gte=2m" mapstructure:"mesh-message-delivery-activation"` +} From 1d69e88673be13720fb86df0c95e1b853d6f2fa6 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 5 Jan 2024 14:11:40 -0500 Subject: [PATCH 02/19] Update flags.go --- network/netconf/flags.go | 58 ++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/network/netconf/flags.go b/network/netconf/flags.go index a0920af60e3..cbfd09d8169 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -311,96 +311,96 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.AppSpecificScoreWeight, + config.GossipSub.ScoreOption.AppSpecificScoreWeight, "the weight for app-specific scores") flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - config.GossipSub.ScoringParameters.ScoreOption.MaxDebugLogs, + config.GossipSub.ScoreOption.MaxDebugLogs, "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - config.GossipSub.ScoringParameters.ScoreOption.DecayInterval, + config.GossipSub.ScoreOption.DecayInterval, "the decay interval for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - config.GossipSub.ScoringParameters.ScoreOption.DecayToZero, + config.GossipSub.ScoreOption.DecayToZero, "the decay to zero for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.MaxAppSpecificPenalty, + config.GossipSub.ScoreOption.Penalties.MaxAppSpecificPenalty, "the maximum penalty for sever offenses that we apply to a remote node score") flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.MinAppSpecificPenalty, + config.GossipSub.ScoreOption.Penalties.MinAppSpecificPenalty, "the minimum penalty for sever offenses that we apply to a remote node score") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.UnknownIdentityPenalty, + config.GossipSub.ScoreOption.Penalties.UnknownIdentityPenalty, "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.InvalidSubscriptionPenalty, + config.GossipSub.ScoreOption.Penalties.InvalidSubscriptionPenalty, "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - config.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward, + config.GossipSub.ScoreOption.Rewards.MaxAppSpecificReward, "the reward for well-behaving staked peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - config.GossipSub.ScoringParameters.ScoreOption.Rewards.StakedIdentityReward, + config.GossipSub.ScoreOption.Rewards.StakedIdentityReward, "the reward for staking peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.GossipThreshold, + config.GossipSub.ScoreOption.Thresholds.GossipThreshold, "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.PublishThreshold, + config.GossipSub.ScoreOption.Thresholds.PublishThreshold, "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.GraylistThreshold, + config.GossipSub.ScoreOption.Thresholds.GraylistThreshold, "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.AcceptPXThreshold, + config.GossipSub.ScoreOption.Thresholds.AcceptPXThreshold, "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.OpportunisticGraftThreshold, + config.GossipSub.ScoreOption.Thresholds.OpportunisticGraftThreshold, "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyThreshold, + config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyThreshold, "the threshold when the behavior of a peer is considered as bad by GossipSub") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyWeight, + config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyWeight, "the weight for applying penalty when a peer misbehavior goes beyond the threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.BehaviourPenaltyDecay, + config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyDecay, "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.SkipAtomicValidation, + config.GossipSub.ScoreOption.TopicValidation.SkipAtomicValidation, "the default value for the skip atomic validation flag for topics") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, + config.GossipSub.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, "this value is applied to the square of the number of invalid message deliveries on a topic") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, + config.GossipSub.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, "the decay factor used to decay the number of invalid message deliveries") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TimeInMeshQuantum, + config.GossipSub.ScoreOption.TopicValidation.TimeInMeshQuantum, "the time in mesh quantum for the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TopicWeight, + config.GossipSub.ScoreOption.TopicValidation.TopicWeight, "the weight of a topic in the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, + config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, + config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, + config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshDeliveriesWeight, + config.GossipSub.ScoreOption.TopicValidation.MeshDeliveriesWeight, "the weight for applying penalty when a peer is under-performing in a topic mesh") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, + config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } From b81d545a926009aada38817c2c95a37224b2cb94 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 5 Jan 2024 20:26:08 -0500 Subject: [PATCH 03/19] move registry constants to config file - remove all constants - update constant references in tests to use new config values - update scoring components to use new config values --- config/default-config.yml | 532 ++++++++++-------- .../validation_inspector_test.go | 5 +- .../test/gossipsub/scoring/ihave_spam_test.go | 49 +- .../test/gossipsub/scoring/scoring_test.go | 76 ++- network/netconf/flags.go | 162 ++++-- network/p2p/config/gossipsub.go | 5 +- network/p2p/config/score_option.go | 58 +- network/p2p/config/score_registry.go | 34 ++ network/p2p/scoring/app_score_test.go | 10 +- network/p2p/scoring/decay_test.go | 10 +- network/p2p/scoring/registry.go | 140 ++--- network/p2p/scoring/registry_test.go | 108 ++-- network/p2p/scoring/score_option.go | 422 +++----------- 13 files changed, 750 insertions(+), 861 deletions(-) create mode 100644 network/p2p/config/score_registry.go diff --git a/config/default-config.yml b/config/default-config.yml index 2993060cb6e..7cf1769978c 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -1,4 +1,8 @@ config-file: "./default-config.yml" +# WARNING: Only modify the gossipsub configurations below if you fully understand their implications. +# Incorrect settings may lead to system instability, security vulnerabilities, or degraded performance. +# Make changes with caution and refer to the documentation for guidance. +# Network configuration. network-config: # Network Configuration # Connection pruning determines whether connections to nodes @@ -227,248 +231,292 @@ network-config: penalty-decay-evaluation-period: 10m # the intervals at which counters associated with a peer behavior in gossipsub system are decayed. decay-interval: 1m - score-option: - # The weight for app-specific scores. - # It is used to scale the app-specific scores to the same range as the other scores. - # At the current version, we don't distinguish between the app-specific scores - # and the other scores, so we set it to 1. - app-specific-score-weight: 1 - # The max number of debug/trace log events per second. - # Logs emitted above this threshold are dropped. - max-debug-logs: 50 - # The default decay interval for the overall score of a peer at the GossipSub scoring - # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty - # and is not too long so that a well-behaved node can't recover from a penalty. - decay-interval: 1m - # The default decay to zero for the overall score of a peer at the GossipSub scoring system. - # It defines the maximum value below which a peer scoring counter is reset to zero. - # This is to prevent the counter from decaying to a very small value. - # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. - # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - # for a long time, and we can reset the counter. - decay-to-zero: 0.01 - penalties: - # This is the maximum penalty for severe offenses that we apply - # to a remote node score. The score mechanism of GossipSub in Flow is designed - # in a way that all other infractions are penalized with a fraction of this value. - # We have also set the other parameters such as GraylistThreshold, - # GossipThreshold, and PublishThreshold to be a bit higher than this, - # i.e., -100 + 1. This ensures that a node with a score of - # -100 will be graylisted (i.e., all incoming and outgoing RPCs - # are rejected) and will not be able to publish or gossip any messages. - max-app-specific-penalty: -100 - min-app-specific-penalty: -1 - # This is the penalty for unknown identity. It is - # applied to the peer's score when the peer is not in the identity list. - unknown-identity-penalty: -100 - # This is the penalty for invalid subscription. - # It is applied to the peer's score when the peer subscribes to a topic that it is - # not authorized to subscribe to. - invalid-subscription-penalty: -100 - rewards: - # This is the reward for well-behaving staked peers. - # If a peer does not have any misbehavior record, e.g., invalid subscription, - # invalid message, etc., it will be rewarded with this score. - max-app-specific-reward: 100 - # This is the reward for staking peers. It is applied - # to the peer's score when the peer does not have any misbehavior record, e.g., - # invalid subscription, invalid message, etc. The purpose is to reward the staking - # peers for their contribution to the network and prioritize them in neighbor selection. - staked-identity-reward: 100 - thresholds: - # This is the threshold when a peer's penalty drops below this threshold, no gossip - # is emitted towards that peer and gossip from that peer is ignored. - # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all gossips to and from peers with penalty -100 are ignored. - gossip-threshold: -99 - # This is the threshold when a peer's penalty drops below this threshold, - # self-published messages are not propagated towards this peer. - # Validation Constraint: - # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all penalized peers are deprived of receiving any published messages. - publish-threshold: -99 - # This is the threshold when a peer's penalty drops below this threshold, - # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. - # Validation Constraint: - # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all penalized peers are graylisted. - graylist-threshold: -99 - # This is the threshold when a peer sends us PX information with a prune, - # we only accept it and connect to the supplied peers if the originating peer's - # penalty exceeds this threshold. - # Validation Constraint: must be non-negative. - # How we use it: As the current max reward is 100, we set the threshold to 99 - # so that we only receive supplied peers from well-behaved peers. - accept-px-threshold: 99 - # This is the threshold when the median peer penalty in the mesh drops - # below this value, the peer may select more peers with penalty above the median - # to opportunistically graft on the mesh. - # Validation Constraint: must be non-negative. - # How we use it: We set it to the -100 + 1 so that we only - # opportunistically graft peers that are not access nodes (i.e., with -1), - # or penalized peers (i.e., with -100). - opportunistic-graft-threshold: 101 - behaviour: - # The threshold when the behavior of a peer is considered as bad by GossipSub. - # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting - # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). - # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # - # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every - # excess RPC containing iHave broken promises. - # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system). - penalty-threshold: 10 - # The weight for applying penalty when a peer misbehavior goes beyond the threshold. - # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting - # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # - # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. - # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose - # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. - # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). - # - # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty - # if misbehavior > threshold + sqrt(2) * 10. - # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight - # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that - # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. - # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold - # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality - # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system that are reported by the engines). - penalty-weight: -1 - # The decay interval for the misbehavior counter of a peer. The misbehavior counter is - # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. - # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. - # - # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. - # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, - # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. - # - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system that is based on the engines report). - penalty-decay: 0.99 - topic: - # This is the default value for the skip atomic validation flag for topics. - # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the - # topic parameters at their default values, i.e., zero. This is because we are not setting all - # topic parameters at the current implementation. - skip-atomic-validation: true - # This value is applied to the square of the number of invalid message deliveries on a topic. - # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the - # publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid - # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. - # The supporting math is as follows: - # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). - # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. - # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). - # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. - invalid-message-deliveries-weight: -1.0 - # The decay factor used to decay the number of invalid message deliveries. - # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to - # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops - # sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will - # decay by 1% at each heartbeat interval. - # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). - invalid-message-deliveries-decay: 0.99 - # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge - # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is - # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. - # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that - # are churners, i.e., peers that join and leave a topic mesh frequently. - time-in-mesh-quantum: 1h - # The default weight of a topic in the GossipSub scoring system. - # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. - # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. - topic-weight: 1.0 - # This is applied to the number of actual message deliveries in a topic mesh - # at each decay interval (i.e., defaultDecayInterval). - # It is used to decay the number of actual message deliveries, and prevents past message - # deliveries from affecting the current score of the peer. - # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message - # deliveries will decay by 50% at each decay interval. - mesh-message-deliveries-decay: 0.5 - # The maximum number of actual message deliveries in a topic - # mesh that is used to calculate the score of a peer in that topic mesh. - # We set it to 1000, which means that the maximum number of actual message deliveries in a - # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. - # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual - # message deliveries and also affect the score of the peer in other topic meshes. - # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic - # mesh will not be affected by the actual message deliveries in that topic mesh. - # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh - # and then start under-performing in that topic mesh without being penalized. - mesh-message-deliveries-cap: 1000 - # The threshold for the number of actual message deliveries in a - # topic mesh that is used to calculate the score of a peer in that topic mesh. - # If the number of actual message deliveries in a topic mesh is less than this value, - # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, - # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the - # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). - # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the - # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer - # in that topic mesh. - mesh-message-deliveries-threshold: 100 - # The weight for applying penalty when a peer is under-performing in a topic mesh. - # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold - # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual - # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and - # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. - # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer - # is not delivering any message in a topic mesh, it will not be disconnected. - # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked - # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh - # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. - # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose - # its MaxAppSpecificReward that is awarded by our app-specific scoring function. - # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be - # causing a false positive network partition. - mesh-deliveries-weight: -0.0005 - # The window size is time interval that we count a delivery of an already - # seen message towards the score of a peer in a topic mesh. The delivery is counted - # by GossipSub only if the previous sender of the message is different from the current sender. - # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. - # It means that if a peer delivers a message that it has already seen less than one minute ago, - # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. - # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not - # be counted towards the actual message deliveries of a peer in a topic mesh. - mesh-message-deliveries-window: 1m - # The time interval that we wait for a new peer that joins a topic mesh - # till start counting the number of actual message deliveries of that peer in that topic mesh. - # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting - # the number of actual message deliveries of a peer in a topic mesh. - # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the - # number of actual message deliveries of a peer in a topic mesh. This is to account for - # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. - mesh-message-delivery-activation: 2m + score-option: + # The weight for app-specific scores. + # It is used to scale the app-specific scores to the same range as the other scores. + # At the current version, we don't distinguish between the app-specific scores + # and the other scores, so we set it to 1. + app-specific-score-weight: 1 + # The max number of debug/trace log events per second. + # Logs emitted above this threshold are dropped. + max-debug-logs: 50 + # The default decay interval for the overall score of a peer at the GossipSub scoring + # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + # and is not too long so that a well-behaved node can't recover from a penalty. + decay-interval: 1m + # The default decay to zero for the overall score of a peer at the GossipSub scoring system. + # It defines the maximum value below which a peer scoring counter is reset to zero. + # This is to prevent the counter from decaying to a very small value. + # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + # for a long time, and we can reset the counter. + decay-to-zero: 0.01 + penalties: + # This is the maximum penalty for severe offenses that we apply + # to a remote node score. The score mechanism of GossipSub in Flow is designed + # in a way that all other infractions are penalized with a fraction of this value. + # We have also set the other parameters such as GraylistThreshold, + # GossipThreshold, and PublishThreshold to be a bit higher than this, + # i.e., -100 + 1. This ensures that a node with a score of + # -100 will be graylisted (i.e., all incoming and outgoing RPCs + # are rejected) and will not be able to publish or gossip any messages. + max-app-specific-penalty: -100 + min-app-specific-penalty: -1 + # This is the penalty for unknown identity. It is + # applied to the peer's score when the peer is not in the identity list. + unknown-identity-penalty: -100 + # This is the penalty for invalid subscription. + # It is applied to the peer's score when the peer subscribes to a topic that it is + # not authorized to subscribe to. + invalid-subscription-penalty: -100 + rewards: + # This is the reward for well-behaving staked peers. + # If a peer does not have any misbehavior record, e.g., invalid subscription, + # invalid message, etc., it will be rewarded with this score. + max-app-specific-reward: 100 + # This is the reward for staking peers. It is applied + # to the peer's score when the peer does not have any misbehavior record, e.g., + # invalid subscription, invalid message, etc. The purpose is to reward the staking + # peers for their contribution to the network and prioritize them in neighbor selection. + staked-identity-reward: 100 + thresholds: + # This is the threshold when a peer's penalty drops below this threshold, no gossip + # is emitted towards that peer and gossip from that peer is ignored. + # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all gossips to and from peers with penalty -100 are ignored. + gossip: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # self-published messages are not propagated towards this peer. + # Validation Constraint: + # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are deprived of receiving any published messages. + publish: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. + # Validation Constraint: + # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are graylisted. + graylist: -99 + # This is the threshold when a peer sends us PX information with a prune, + # we only accept it and connect to the supplied peers if the originating peer's + # penalty exceeds this threshold. + # Validation Constraint: must be non-negative. + # How we use it: As the current max reward is 100, we set the threshold to 99 + # so that we only receive supplied peers from well-behaved peers. + accept-px: 99 + # This is the threshold when the median peer penalty in the mesh drops + # below this value, the peer may select more peers with penalty above the median + # to opportunistically graft on the mesh. + # Validation Constraint: must be non-negative. + # How we use it: We set it to the -100 + 1 so that we only + # opportunistically graft peers that are not access nodes (i.e., with -1), + # or penalized peers (i.e., with -100). + opportunistic-graft: 101 + behaviour: + # The threshold when the behavior of a peer is considered as bad by GossipSub. + # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every + # excess RPC containing iHave broken promises. + # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system). + penalty-threshold: 10 + # The weight for applying penalty when a peer misbehavior goes beyond the threshold. + # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. + # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose + # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. + # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + # + # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty + # if misbehavior > threshold + sqrt(2) * 10. + # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that + # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. + # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of + # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality + # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that are reported by the engines). + penalty-weight: -1 + # The decay interval for the misbehavior counter of a peer. The misbehavior counter is + # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + # + # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. + # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. + # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. + # + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that is based on the engines report). + penalty-decay: 0.99 + topic: + # This is the default value for the skip atomic validation flag for topics. + # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the + # topic parameters at their default values, i.e., zero. This is because we are not setting all + # topic parameters at the current implementation. + skip-atomic-validation: true + # This value is applied to the square of the number of invalid message deliveries on a topic. + # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + # publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid + # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. + # The supporting math is as follows: + # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). + # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. + # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). + # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. + invalid-message-deliveries-weight: -1.0 + # The decay factor used to decay the number of invalid message deliveries. + # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + # sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will + # decay by 1% at each heartbeat interval. + # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). + invalid-message-deliveries-decay: 0.99 + # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge + # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is + # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. + # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that + # are churners, i.e., peers that join and leave a topic mesh frequently. + time-in-mesh-quantum: 1h + # The default weight of a topic in the GossipSub scoring system. + # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. + topic-weight: 1.0 + # This is applied to the number of actual message deliveries in a topic mesh + # at each decay interval (i.e., defaultDecayInterval). + # It is used to decay the number of actual message deliveries, and prevents past message + # deliveries from affecting the current score of the peer. + # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message + # deliveries will decay by 50% at each decay interval. + mesh-message-deliveries-decay: 0.5 + # The maximum number of actual message deliveries in a topic + # mesh that is used to calculate the score of a peer in that topic mesh. + # We set it to 1000, which means that the maximum number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. + # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual + # message deliveries and also affect the score of the peer in other topic meshes. + # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic + # mesh will not be affected by the actual message deliveries in that topic mesh. + # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh + # and then start under-performing in that topic mesh without being penalized. + mesh-message-deliveries-cap: 1000 + # The threshold for the number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh. + # If the number of actual message deliveries in a topic mesh is less than this value, + # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). + # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the + # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer + # in that topic mesh. + mesh-message-deliveries-threshold: 100 + # The weight for applying penalty when a peer is under-performing in a topic mesh. + # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer + # is not delivering any message in a topic mesh, it will not be disconnected. + # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked + # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh + # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. + # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose + # its MaxAppSpecificReward that is awarded by our app-specific scoring function. + # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be + # causing a false positive network partition. + mesh-deliveries-weight: -0.0005 + # The window size is time interval that we count a delivery of an already + # seen message towards the score of a peer in a topic mesh. The delivery is counted + # by GossipSub only if the previous sender of the message is different from the current sender. + # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. + # It means that if a peer delivers a message that it has already seen less than one minute ago, + # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. + # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not + # be counted towards the actual message deliveries of a peer in a topic mesh. + mesh-message-deliveries-window: 1m + # The time interval that we wait for a new peer that joins a topic mesh + # till start counting the number of actual message deliveries of that peer in that topic mesh. + # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting + # the number of actual message deliveries of a peer in a topic mesh. + # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the + # number of actual message deliveries of a peer in a topic mesh. This is to account for + # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. + mesh-message-delivery-activation: 2m + scoring-registry: + # The minimum speed at which the spam penalty value of a peer is decayed. + # Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, + # The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value + # indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. + # assume: + # penalty = -100 (the maximum application specific penalty is -100) + # skipDecayThreshold = -0.1 + # it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. + # x * 0.99 ^ n > -0.1 (assuming negative x). + # 0.99 ^ n > -0.1 / x + # Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). + # log( 0.99 ^ n ) < log( 0.1 / x ) + # Using the properties of logarithms, we can bring down the exponent: + # n * log( 0.99 ) < log( -0.1 / x ) + # And finally, we can solve for n: + # n > log( -0.1 / x ) / log( 0.99 ) + # We can plug in x = -100: + # n > log( -0.1 / -100 ) / log( 0.99 ) + # n > log( 0.001 ) / log( 0.99 ) + # n > -3 / log( 0.99 ) + # n > 458.22 + minimum-spam-penalty-decay-factor: 0.99 + # The maximum rate at which the spam penalty value of a peer decays. Decay speeds increase + # during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, + # decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. + maximum-spam-penalty-decay-factor: 0.8 + misbehaviour-penalties: + # The threshold for which when the negative penalty is above this value, the decay function will not be called. + # instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. + skip-decay-threshold: -0.1 + # The penalty applied to the application specific penalty when a peer conducts a graft misbehaviour. + graft: -10 + # The penalty applied to the application specific penalty when a peer conducts a prune misbehaviour. + prune: -10 + # The penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour. + ihave: -10 + # The penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour. + iwant: -10 + # The penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour. + publish: -10 + # The factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This allows a more lenient punishment for nodes + # that fall behind and may need to request old data. + cluster-prefixed-reduction-factor: 0.2 subscription-provider: # The interval for updating the list of subscribed peers to all topics in gossipsub. This is used to keep track of subscriptions # violations and penalize peers accordingly. Recommended value is in the order of a few minutes to avoid contentions; as the operation diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go index b66b6259535..e147605de37 100644 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go +++ b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go @@ -28,7 +28,6 @@ import ( "github.com/onflow/flow-go/network/p2p/inspector/validation" p2pmsg "github.com/onflow/flow-go/network/p2p/message" mockp2p "github.com/onflow/flow-go/network/p2p/mock" - "github.com/onflow/flow-go/network/p2p/scoring" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -1105,11 +1104,11 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsWithMalformedTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsInvalidSporkIDTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsDuplicateTopic) - + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption // wait for three GossipSub heartbeat intervals to ensure that the victim node has penalized the spammer node. require.Eventually(t, func() bool { score, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) - return ok && score < 2*scoring.DefaultGraylistThreshold + return ok && score < 2*scoreOptParameters.Thresholds.Graylist }, 5*time.Second, 100*time.Millisecond, "expected victim node to penalize spammer node") // now we expect the detection and mitigation to kick in and the victim node to disconnect from the spammer node. 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 e11951cc7dd..e9d0ef3558b 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -18,7 +18,6 @@ import ( "github.com/onflow/flow-go/module/irrecoverable" "github.com/onflow/flow-go/network/channels" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/scoring" p2ptest "github.com/onflow/flow-go/network/p2p/test" "github.com/onflow/flow-go/utils/unittest" ) @@ -55,7 +54,7 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. // we disable invalid message delivery parameters, as the way we implement spammer, when it spams ihave messages, it does not sign them. Hence, without decaying the invalid message deliveries, // the node would be penalized for invalid message delivery way sooner than it can mount an ihave broken-promises spam attack. @@ -127,26 +126,28 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // 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) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters + spammerScore, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) require.True(t, ok, "sanity check failed, we should have a score for the spammer node") // since spammer is not yet considered to be penalized, its score must be greater than the gossipsub health thresholds. require.Greaterf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, spammerScore) // eventually, after a heartbeat the spammer behavioral counter must be decayed @@ -211,7 +212,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. // we disable invalid message delivery parameters, as the way we implement spammer, when it spams ihave messages, it does not sign them. Hence, without decaying the invalid message deliveries, // the node would be penalized for invalid message delivery way sooner than it can mount an ihave broken-promises spam attack. @@ -307,33 +308,35 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // 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) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters + 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 5 about the threshold, to account for decays. require.LessOrEqual(t, spammerScore, - initScore-5*5*0.01*scoring.MaxAppSpecificReward, + initScore-5*5*0.01*scoreParams.ScoreOption.Rewards.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-5*5*0.1*scoring.MaxAppSpecificReward, + initScore-5*5*0.1*scoreParams.ScoreOption.Rewards.MaxAppSpecificReward, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, spammerScore) // since the spammer score is above the gossip, graylist and publish thresholds, it should be still able to exchange messages with victim. @@ -368,21 +371,21 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // victim will not exchange messages with it anymore, and also that it will be graylisted meaning all incoming and outgoing RPCs to and from the spammer will be dropped by the victim. require.Lessf(t, spammerScore, - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be less than gossip threshold: %f, actual: %f", - scoring.DefaultGossipThreshold, + scoreParams.ScoreOption.Thresholds.Gossip, spammerScore) require.Lessf(t, spammerScore, - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, "sanity check failed, the score of the spammer node must be less than publish threshold: %f, actual: %f", - scoring.DefaultPublishThreshold, + scoreParams.ScoreOption.Thresholds.Publish, spammerScore) require.Lessf(t, spammerScore, - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be less than graylist threshold: %f, actual: %f", - scoring.DefaultGraylistThreshold, + scoreParams.ScoreOption.Thresholds.Graylist, spammerScore) // since the spammer score is below the gossip, graylist and publish thresholds, it should not be able to exchange messages with victim anymore. diff --git a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go index 0197f219960..173b49f4b54 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go @@ -18,7 +18,6 @@ import ( "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" p2ptest "github.com/onflow/flow-go/network/p2p/test" validator "github.com/onflow/flow-go/network/validator/pubsub" "github.com/onflow/flow-go/utils/unittest" @@ -142,6 +141,8 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun spamMsgFactory(spammer.SpammerNode.ID(), victimNode.ID(), blockTopic)) } + scoreParams := cfg.NetworkConfig.GossipSub.ScoringParameters + // wait for at most 3 seconds for the victim node to penalize the spammer node. // Each heartbeat is 1 second, so 3 heartbeats should be enough to penalize the spammer node. // Ideally, we should wait for 1 heartbeat, but the score may not be updated immediately after the heartbeat. @@ -150,15 +151,15 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun if !ok { return false } - if spammerScore >= scoring.DefaultGossipThreshold { + if spammerScore >= scoreParams.ScoreOption.Thresholds.Gossip { // ensure the score is low enough so that no gossip is routed by victim node to spammer node. return false } - if spammerScore >= scoring.DefaultPublishThreshold { + if spammerScore >= scoreParams.ScoreOption.Thresholds.Publish { // ensure the score is low enough so that non of the published messages of the victim node are routed to the spammer node. return false } - if spammerScore >= scoring.DefaultGraylistThreshold { + if spammerScore >= scoreParams.ScoreOption.Thresholds.Graylist { // ensure the score is low enough so that the victim node does not accept RPC messages from the spammer node. return false } @@ -209,7 +210,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { blockTopic := channels.TopicFromChannel(channels.PushBlocks, sporkId) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. conf, err := config.DefaultConfig() @@ -256,6 +257,8 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { return unittest.ProposalFixture() }) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // Also initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked // as it is in the id provider of thisNode. @@ -264,7 +267,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoring.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -279,17 +282,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.96*scoring.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.96*scoreParams.Rewards.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. // 0.96 is to account for floating point errors. return false } - if underPerformingNodeScore < scoring.DefaultGossipThreshold { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < scoreParams.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoring.DefaultPublishThreshold { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < scoreParams.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoring.DefaultGraylistThreshold { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < scoreParams.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -317,9 +320,9 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { dkgTopic := channels.TopicFromChannel(channels.DKGCommittee, sporkId) // we override some of the default scoring parameters in order to speed up the test in a time-efficient manner. - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. - dkgTopicOverrideParams := scoring.DefaultTopicScoreParams() + dkgTopicOverrideParams := defaultTopicScoreParams(t) dkgTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. conf, err := config.DefaultConfig() @@ -370,6 +373,8 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { } } + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // Initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked // as it is in the id provider of thisNode. @@ -378,7 +383,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoring.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -394,17 +399,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.91*scoring.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.91*scoreParams.Rewards.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. // 0.91 is to account for the floating point errors. return false } - if underPerformingNodeScore < scoring.DefaultGossipThreshold { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < scoreParams.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoring.DefaultPublishThreshold { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < scoreParams.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoring.DefaultGraylistThreshold { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < scoreParams.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -439,7 +444,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { // 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 - blockTopicOverrideParams := scoring.DefaultTopicScoreParams() + blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, @@ -480,6 +485,8 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // Initially the replaying node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and initially every node is considered staked // as it is in the id provider of thisNode. @@ -489,7 +496,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoring.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -516,7 +523,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoring.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -556,22 +563,22 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return false } - if replayingNodeScore >= scoring.MaxAppSpecificReward { + if replayingNodeScore >= scoreParams.Rewards.MaxAppSpecificReward { // node must be penalized for just replaying the same messages. return false } // following if-statements check that even though the node is penalized, it is not penalized too much, and // can still participate in the network. We don't desire to disallow list a node for just under-performing. - if replayingNodeScore < scoring.DefaultGossipThreshold { + if replayingNodeScore < scoreParams.Thresholds.Gossip { return false } - if replayingNodeScore < scoring.DefaultPublishThreshold { + if replayingNodeScore < scoreParams.Thresholds.Publish { return false } - if replayingNodeScore < scoring.DefaultGraylistThreshold { + if replayingNodeScore < scoreParams.Thresholds.Graylist { return false } @@ -584,3 +591,24 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) } + +// defaultTopicScoreParams returns the default score params for topics. +func defaultTopicScoreParams(t *testing.T) *pubsub.TopicScoreParams { + defaultConfig, err := config.DefaultConfig() + require.NoError(t, err) + topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.ScoreOption.TopicValidation + p := &pubsub.TopicScoreParams{ + TopicWeight: topicScoreParams.TopicWeight, + SkipAtomicValidation: topicScoreParams.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: topicScoreParams.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: topicScoreParams.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: topicScoreParams.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: topicScoreParams.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: topicScoreParams.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: topicScoreParams.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: topicScoreParams.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: topicScoreParams.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: topicScoreParams.MeshMessageDeliveryActivation, + } + return p +} diff --git a/network/netconf/flags.go b/network/netconf/flags.go index cbfd09d8169..8191faca891 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -110,35 +110,45 @@ func AllFlagNames() []string { BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneMisbehaviourKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveMisbehaviourKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantMisbehaviourKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishMisbehaviourKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), } for _, scope := range []string{systemScope, transientScope, protocolScope, peerScope, peerProtocolScope} { @@ -311,96 +321,124 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - config.GossipSub.ScoreOption.AppSpecificScoreWeight, + config.GossipSub.ScoringParameters.ScoreOption.AppSpecificScoreWeight, "the weight for app-specific scores") - flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - config.GossipSub.ScoreOption.MaxDebugLogs, + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), + config.GossipSub.ScoringParameters.ScoreOption.MaxDebugLogs, "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - config.GossipSub.ScoreOption.DecayInterval, + config.GossipSub.ScoringParameters.ScoreOption.DecayInterval, "the decay interval for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - config.GossipSub.ScoreOption.DecayToZero, + config.GossipSub.ScoringParameters.ScoreOption.DecayToZero, "the decay to zero for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - config.GossipSub.ScoreOption.Penalties.MaxAppSpecificPenalty, + config.GossipSub.ScoringParameters.ScoreOption.Penalties.MaxAppSpecificPenalty, "the maximum penalty for sever offenses that we apply to a remote node score") - flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - config.GossipSub.ScoreOption.Penalties.MinAppSpecificPenalty, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), + config.GossipSub.ScoringParameters.ScoreOption.Penalties.MinAppSpecificPenalty, "the minimum penalty for sever offenses that we apply to a remote node score") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - config.GossipSub.ScoreOption.Penalties.UnknownIdentityPenalty, + config.GossipSub.ScoringParameters.ScoreOption.Penalties.UnknownIdentityPenalty, "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - config.GossipSub.ScoreOption.Penalties.InvalidSubscriptionPenalty, + config.GossipSub.ScoringParameters.ScoreOption.Penalties.InvalidSubscriptionPenalty, "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - config.GossipSub.ScoreOption.Rewards.MaxAppSpecificReward, + config.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward, "the reward for well-behaving staked peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - config.GossipSub.ScoreOption.Rewards.StakedIdentityReward, + config.GossipSub.ScoringParameters.ScoreOption.Rewards.StakedIdentityReward, "the reward for staking peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - config.GossipSub.ScoreOption.Thresholds.GossipThreshold, + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Gossip, "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - config.GossipSub.ScoreOption.Thresholds.PublishThreshold, + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Publish, "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - config.GossipSub.ScoreOption.Thresholds.GraylistThreshold, + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Graylist, "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - config.GossipSub.ScoreOption.Thresholds.AcceptPXThreshold, + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.AcceptPX, "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - config.GossipSub.ScoreOption.Thresholds.OpportunisticGraftThreshold, + config.GossipSub.ScoringParameters.ScoreOption.Thresholds.OpportunisticGraft, "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") - flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyThreshold, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + "the minimum speed at which the spam penalty value of a peer is decayed") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + "the maximum rate at which the spam penalty value of a peer decays") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + "the threshold for which when the negative penalty is above this value, the decay function will not be called") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.GraftMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PruneMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IHaveMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IWantMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PublishMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.ClusterPrefixedReductionFactor, + "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyThreshold, "the threshold when the behavior of a peer is considered as bad by GossipSub") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyWeight, + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyWeight, "the weight for applying penalty when a peer misbehavior goes beyond the threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - config.GossipSub.ScoreOption.Behaviour.BehaviourPenaltyDecay, + config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyDecay, "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - config.GossipSub.ScoreOption.TopicValidation.SkipAtomicValidation, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.SkipAtomicValidation, "the default value for the skip atomic validation flag for topics") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - config.GossipSub.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, "this value is applied to the square of the number of invalid message deliveries on a topic") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - config.GossipSub.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, "the decay factor used to decay the number of invalid message deliveries") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - config.GossipSub.ScoreOption.TopicValidation.TimeInMeshQuantum, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TimeInMeshQuantum, "the time in mesh quantum for the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - config.GossipSub.ScoreOption.TopicValidation.TopicWeight, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TopicWeight, "the weight of a topic in the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") - flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - config.GossipSub.ScoreOption.TopicValidation.MeshDeliveriesWeight, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshDeliveriesWeight, "the weight for applying penalty when a peer is under-performing in a topic mesh") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), - config.GossipSub.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index b0a85d7938f..750257fed6c 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -76,7 +76,6 @@ type GossipSubParameters struct { PeerScoringEnabled bool `mapstructure:"peer-scoring-enabled"` SubscriptionProvider SubscriptionProviderParameters `mapstructure:"subscription-provider"` ScoringParameters ScoringParameters `mapstructure:"scoring-parameters"` - ScoreOption ScoreOption `mapstructure:"score-option"` } const ( @@ -91,7 +90,9 @@ type ScoringParameters struct { AppSpecificScore AppSpecificScoreParameters `validate:"required" mapstructure:"app-specific-score"` SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` // DecayInterval is the interval at which the counters associated with a peer behavior in GossipSub system are decayed. - DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` + DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` + ScoreOption ScoreOption `mapstructure:"score-option"` + ScoringRegistryParameters ScoringRegistryParameters `mapstructure:"scoring-registry"` } const ( diff --git a/network/p2p/config/score_option.go b/network/p2p/config/score_option.go index 15bc5f300a2..2f608dae79e 100644 --- a/network/p2p/config/score_option.go +++ b/network/p2p/config/score_option.go @@ -23,7 +23,7 @@ type ScoreOption struct { AppSpecificScoreWeight float64 `validate:"gt=0,lte=1" mapstructure:"app-specific-score-weight"` // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above // this threshold are dropped. - MaxDebugLogs int `validate:"lte=50" mapstructure:"max-debug-logs"` + MaxDebugLogs uint32 `validate:"lte=50" mapstructure:"max-debug-logs"` // DecayInterval is the decay interval for the overall score of a peer at the GossipSub scoring // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty // and is not too long so that a well-behaved node can't recover from a penalty. @@ -39,7 +39,7 @@ type ScoreOption struct { Rewards ScoreOptionRewards `validate:"required" mapstructure:"rewards"` Thresholds ScoreOptionThresholds `validate:"required" mapstructure:"thresholds"` Behaviour ScoreOptionBehaviour `validate:"required" mapstructure:"behaviour"` - TopicValidation ScoreOptionTopicValidation `validate:"required" mapstructure:"topic-validation"` + TopicValidation ScoreOptionTopicValidation `validate:"required" mapstructure:"topic"` } const ( @@ -58,7 +58,7 @@ type ScoreOptionPenalties struct { // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. - MinAppSpecificPenalty int `validate:"lt=0" mapstructure:"min-app-specific-penalty"` + MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific-penalty"` // UnknownIdentityPenalty is the penalty for unknown identity. It is applied to the peer's score when // the peer is not in the identity list. UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity-penalty"` @@ -84,30 +84,30 @@ type ScoreOptionRewards struct { } const ( - GossipThresholdKey = "gossip-threshold" - PublishThresholdKey = "publish-threshold" - GraylistThresholdKey = "graylist-threshold" - AcceptPXThresholdKey = "accept-px-threshold" - OpportunisticGraftThresholdKey = "opportunistic-graft-threshold" + GossipThresholdKey = "gossip" + PublishThresholdKey = "publish" + GraylistThresholdKey = "graylist" + AcceptPXThresholdKey = "accept-px" + OpportunisticGraftThresholdKey = "opportunistic-graft" ) // ScoreOptionThresholds score option threshold configuration parameters. type ScoreOptionThresholds struct { - // GossipThreshold when a peer's penalty drops below this threshold, + // Gossip when a peer's penalty drops below this threshold, // no gossip is emitted towards that peer and gossip from that peer is ignored. - GossipThreshold float64 `validate:"lt=0" mapstructure:"gossip-threshold"` - // PublishThreshold when a peer's penalty drops below this threshold, + Gossip float64 `validate:"lt=0" mapstructure:"gossip"` + // Publish when a peer's penalty drops below this threshold, // self-published messages are not propagated towards this peer. - PublishThreshold float64 `validate:"lt=0" mapstructure:"publish-threshold"` - // GraylistThreshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., + Publish float64 `validate:"lt=0" mapstructure:"publish"` + // Graylist when a peer's penalty drops below this threshold, the peer is graylisted, i.e., // incoming RPCs from the peer are ignored. - GraylistThreshold float64 `validate:"lt=0" mapstructure:"graylist-threshold"` - // AcceptPXThreshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied + Graylist float64 `validate:"lt=0" mapstructure:"graylist"` + // AcceptPX when a peer sends us PX information with a prune, we only accept it and connect to the supplied // peers if the originating peer's penalty exceeds this threshold. - AcceptPXThreshold float64 `validate:"gt=0" mapstructure:"accept-px-threshold"` - // OpportunisticGraftThreshold when the median peer penalty in the mesh drops below this value, + AcceptPX float64 `validate:"gt=0" mapstructure:"accept-px"` + // OpportunisticGraft when the median peer penalty in the mesh drops below this value, // the peer may select more peers with penalty above the median to opportunistically graft on the mesh. - OpportunisticGraftThreshold float64 `validate:"gt=0" mapstructure:"opportunistic-graft-threshold"` + OpportunisticGraft float64 `validate:"gt=0" mapstructure:"opportunistic-graft"` } const ( @@ -118,7 +118,7 @@ const ( // ScoreOptionBehaviour score option behaviour configuration parameters. type ScoreOptionBehaviour struct { - // BehaviourPenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. + // PenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh // for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). @@ -134,8 +134,8 @@ type ScoreOptionBehaviour struct { // The counter is also decayed by (0.99) every decay interval (DecayInterval) i.e., every minute. // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through // the ALSP system). - BehaviourPenaltyThreshold int `validate:"gt=0" mapstructure:"penalty-threshold"` - // BehaviourPenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. + PenaltyThreshold float64 `validate:"gt=0" mapstructure:"penalty-threshold"` + // PenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. // Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh // This is detected by the GossipSub scoring system, and the peer is penalized by BehaviorPenaltyWeight. @@ -149,22 +149,22 @@ type ScoreOptionBehaviour struct { // We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose // its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by . // Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + // to be dropped below the MaxAppSpecificPenalty, which is also below the Graylist, and the peer will be graylisted (i.e., disconnected). // // The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty // if misbehavior > threshold + sqrt(2) * 10. // As shown above, with this choice of BehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + // to be dropped below the MaxAppSpecificPenalty, which is also below the Graylist, and the peer will be graylisted (i.e., disconnected). This weight // is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that // the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. // Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > Graylist // where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality // to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through // the ALSP system that are reported by the engines). - BehaviourPenaltyWeight float64 `validate:"lt=0" mapstructure:"penalty-weight"` - // BehaviourPenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is + PenaltyWeight float64 `validate:"lt=0" mapstructure:"penalty-weight"` + // PenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). // // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. @@ -176,12 +176,12 @@ type ScoreOptionBehaviour struct { // // The misbehavior counter is decayed per decay interval (i.e., DecayInterval = 1 minute) by GossipSub. // We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - // With the generous threshold that we set (i.e., BehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + // With the generous threshold that we set (i.e., PenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, // We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. // // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through // the ALSP system that is based on the engines report). - BehaviourPenaltyDecay float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay"` + PenaltyDecay float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay"` } const ( @@ -249,7 +249,7 @@ type ScoreOptionTopicValidation struct { // mesh will not be affected by the actual message deliveries in that topic mesh. // Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh // and then start under-performing in that topic mesh without being penalized. - MeshMessageDeliveriesCap int `validate:"gt=0" mapstructure:"mesh-message-deliveries-cap"` + MeshMessageDeliveriesCap float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-cap"` // MeshMessageDeliveryThreshold is the threshold for the number of actual message deliveries in a // topic mesh that is used to calculate the score of a peer in that topic mesh. // If the number of actual message deliveries in a topic mesh is less than this value, diff --git a/network/p2p/config/score_registry.go b/network/p2p/config/score_registry.go new file mode 100644 index 00000000000..ea5709fde5c --- /dev/null +++ b/network/p2p/config/score_registry.go @@ -0,0 +1,34 @@ +package p2pconfig + +const ( + ScoringRegistryKey = "scoring-registry" + MinimumSpamPenaltyDecayFactorKey = "minimum-spam-penalty-decay-factor" + MaximumSpamPenaltyDecayFactorKey = "maximum-spam-penalty-decay-factor" +) + +type ScoringRegistryParameters struct { + MinimumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"minimum-spam-penalty-decay-factor"` + MaximumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"maximum-spam-penalty-decay-factor"` + MisbehaviourPenalties MisbehaviourPenalties `validate:"required" mapstructure:"misbehaviour-penalties"` +} + +const ( + MisbehaviourPenaltiesKey = "misbehaviour-penalties" + SkipDecayThresholdKey = "skip-decay-threshold" + GraftMisbehaviourKey = "graft" + PruneMisbehaviourKey = "prune" + IHaveMisbehaviourKey = "ihave" + IWantMisbehaviourKey = "iwant" + PublishMisbehaviourKey = "publish" + ClusterPrefixedReductionFactorKey = "cluster-prefixed-reduction-factor" +) + +type MisbehaviourPenalties struct { + SkipDecayThreshold float64 `validate:"gt=0,lte=1" mapstructure:"skip-decay-threshold"` + GraftMisbehaviour float64 `validate:"gt=0" mapstructure:"graft"` + PruneMisbehaviour float64 `validate:"gt=0" mapstructure:"prune"` + IHaveMisbehaviour float64 `validate:"gt=0" mapstructure:"ihave"` + IWantMisbehaviour float64 `validate:"gt=0" mapstructure:"iwant"` + PublishMisbehaviour float64 `validate:"gt=0" mapstructure:"publish"` + ClusterPrefixedReductionFactor float64 `validate:"gt=0,lte=1" mapstructure:"cluster-prefixed-reduction-factor"` +} diff --git a/network/p2p/scoring/app_score_test.go b/network/p2p/scoring/app_score_test.go index d693b77207c..9b75bb272a4 100644 --- a/network/p2p/scoring/app_score_test.go +++ b/network/p2p/scoring/app_score_test.go @@ -18,7 +18,7 @@ import ( "github.com/onflow/flow-go/network/internal/p2pfixtures" "github.com/onflow/flow-go/network/message" "github.com/onflow/flow-go/network/p2p" - "github.com/onflow/flow-go/network/p2p/scoring" + p2pconfig "github.com/onflow/flow-go/network/p2p/config" p2ptest "github.com/onflow/flow-go/network/p2p/test" flowpubsub "github.com/onflow/flow-go/network/validator/pubsub" "github.com/onflow/flow-go/utils/unittest" @@ -142,7 +142,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi p2ptest.WithRole(flow.RoleAccess), // overrides the default peer scoring parameters to mute GossipSub traffic from/to honest nodes. p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ - AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}), + AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.ScoreOption), }), ) @@ -223,14 +223,14 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi // maliciousAppSpecificScore returns a malicious app specific penalty function that rewards the malicious node and // punishes the honest nodes. -func maliciousAppSpecificScore(honestIds flow.IdentityList) func(peer.ID) float64 { +func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.ScoreOption) func(peer.ID) float64 { honestIdProvider := id.NewFixedIdentityProvider(honestIds) return func(p peer.ID) float64 { _, isHonest := honestIdProvider.ByPeerID(p) if isHonest { - return scoring.MaxAppSpecificPenalty + return optionCfg.Penalties.MaxAppSpecificPenalty } - return scoring.MaxAppSpecificReward + return optionCfg.Rewards.MaxAppSpecificReward } } diff --git a/network/p2p/scoring/decay_test.go b/network/p2p/scoring/decay_test.go index 2a15b60b754..3abcd3b7ac5 100644 --- a/network/p2p/scoring/decay_test.go +++ b/network/p2p/scoring/decay_test.go @@ -300,7 +300,15 @@ func TestDefaultDecayFunction(t *testing.T) { }, } scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.SpamRecordCache - decayFunc := scoring.DefaultDecayFunction(scoringRegistryConfig.PenaltyDecaySlowdownThreshold, scoringRegistryConfig.DecayRateReductionFactor, scoringRegistryConfig.PenaltyDecayEvaluationPeriod) + decayFunc := scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ + SlowerDecayPenaltyThreshold: scoringRegistryConfig.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: scoringRegistryConfig.DecayRateReductionFactor, + DecayAdjustInterval: scoringRegistryConfig.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + }) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := decayFunc(tt.args.record, tt.args.lastUpdated) diff --git a/network/p2p/scoring/registry.go b/network/p2p/scoring/registry.go index 750e1384915..f1167ef55e2 100644 --- a/network/p2p/scoring/registry.go +++ b/network/p2p/scoring/registry.go @@ -25,51 +25,6 @@ import ( "github.com/onflow/flow-go/utils/logging" ) -const ( - // MinimumSpamPenaltyDecayFactor is minimum speed at which the spam penalty value of a peer is decayed. - // Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, - // The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value - // indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. - // assume: - // penalty = -100 (the maximum application specific penalty is -100) - // skipDecayThreshold = -0.1 - // it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. - // x * 0.99 ^ n > -0.1 (assuming negative x). - // 0.99 ^ n > -0.1 / x - // Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). - // log( 0.99 ^ n ) < log( 0.1 / x ) - // Using the properties of logarithms, we can bring down the exponent: - // n * log( 0.99 ) < log( -0.1 / x ) - // And finally, we can solve for n: - // n > log( -0.1 / x ) / log( 0.99 ) - // We can plug in x = -100: - // n > log( -0.1 / -100 ) / log( 0.99 ) - // n > log( 0.001 ) / log( 0.99 ) - // n > -3 / log( 0.99 ) - // n > 458.22 - MinimumSpamPenaltyDecayFactor = 0.99 - // MaximumSpamPenaltyDecayFactor represents the maximum rate at which the spam penalty value of a peer decays. Decay speeds increase - // during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, - // decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. - MaximumSpamPenaltyDecayFactor = 0.8 - // skipDecayThreshold is the threshold for which when the negative penalty is above this value, the decay function will not be called. - // instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. - skipDecayThreshold = -0.1 - // graftMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour. - graftMisbehaviourPenalty = -10 - // pruneMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour. - pruneMisbehaviourPenalty = -10 - // iHaveMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour. - iHaveMisbehaviourPenalty = -10 - // iWantMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour. - iWantMisbehaviourPenalty = -10 - // clusterPrefixedPenaltyReductionFactor factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This allows a more lenient punishment for nodes - // that fall behind and may need to request old data. - clusterPrefixedPenaltyReductionFactor = .5 - // rpcPublishMessageMisbehaviourPenalty is the penalty applied to the application specific penalty when a peer conducts a RpcPublishMessageMisbehaviourPenalty misbehaviour. - rpcPublishMessageMisbehaviourPenalty = -10 -) - type SpamRecordInitFunc func() p2p.GossipSubSpamRecord // GossipSubCtrlMsgPenaltyValue is the penalty value for each control message type. @@ -84,15 +39,15 @@ type GossipSubCtrlMsgPenaltyValue struct { RpcPublishMessage float64 // penalty value for an individual RpcPublishMessage message misbehaviour. } -// DefaultGossipSubCtrlMsgPenaltyValue returns the default penalty value for each control message type. -func DefaultGossipSubCtrlMsgPenaltyValue() GossipSubCtrlMsgPenaltyValue { +// GossipSubCtrlMsgPenaltyValues returns the default penalty value for each control message type. +func GossipSubCtrlMsgPenaltyValues(penalties p2pconfig.MisbehaviourPenalties) GossipSubCtrlMsgPenaltyValue { return GossipSubCtrlMsgPenaltyValue{ - Graft: graftMisbehaviourPenalty, - Prune: pruneMisbehaviourPenalty, - IHave: iHaveMisbehaviourPenalty, - IWant: iWantMisbehaviourPenalty, - ClusterPrefixedPenaltyReductionFactor: clusterPrefixedPenaltyReductionFactor, - RpcPublishMessage: rpcPublishMessageMisbehaviourPenalty, + Graft: penalties.GraftMisbehaviour, + Prune: penalties.PruneMisbehaviour, + IHave: penalties.IHaveMisbehaviour, + IWant: penalties.IWantMisbehaviour, + ClusterPrefixedPenaltyReductionFactor: penalties.ClusterPrefixedReductionFactor, + RpcPublishMessage: penalties.PublishMisbehaviour, } } @@ -129,6 +84,11 @@ type GossipSubAppSpecificScoreRegistry struct { // appScoreUpdateWorkerPool is the worker pool for handling the application specific score update of peers in a non-blocking way. appScoreUpdateWorkerPool *worker.Pool[peer.ID] + + unknownIdentityPenalty float64 + minAppSpecificPenalty float64 + stakedIdentityReward float64 + invalidSubscriptionPenalty float64 } // GossipSubAppSpecificScoreRegistryConfig is the configuration for the GossipSubAppSpecificScoreRegistry. @@ -164,6 +124,15 @@ type GossipSubAppSpecificScoreRegistryConfig struct { HeroCacheMetricsFactory metrics.HeroCacheMetricsFactory `validate:"required"` NetworkingType network.NetworkingType `validate:"required"` + + // UnknownIdentityPenalty This is the penalty for unknown identity. + UnknownIdentityPenalty float64 `validate:"required"` + // MinAppSpecificPenalty This is the minimum penalty for severe offenses that we apply to a remote node score + MinAppSpecificPenalty float64 `validate:"required"` + // StakedIdentityReward This is the reward for staking peers. + StakedIdentityReward float64 `validate:"required"` + // InvalidSubscriptionPenalty This is the penalty for invalid subscription. + InvalidSubscriptionPenalty float64 `validate:"required"` } // NewGossipSubAppSpecificScoreRegistry returns a new GossipSubAppSpecificScoreRegistry. @@ -187,14 +156,18 @@ func NewGossipSubAppSpecificScoreRegistry(config *GossipSubAppSpecificScoreRegis metrics.GossipSubAppSpecificScoreUpdateQueueMetricFactory(config.HeroCacheMetricsFactory, config.NetworkingType)) reg := &GossipSubAppSpecificScoreRegistry{ - logger: config.Logger.With().Str("module", "app_score_registry").Logger(), - spamScoreCache: config.SpamRecordCacheFactory(), - appScoreCache: config.AppScoreCacheFactory(), - penalty: config.Penalty, - init: config.Init, - validator: config.Validator, - idProvider: config.IdProvider, - scoreTTL: config.Parameters.ScoreTTL, + logger: config.Logger.With().Str("module", "app_score_registry").Logger(), + spamScoreCache: config.SpamRecordCacheFactory(), + appScoreCache: config.AppScoreCacheFactory(), + penalty: config.Penalty, + init: config.Init, + validator: config.Validator, + idProvider: config.IdProvider, + scoreTTL: config.Parameters.ScoreTTL, + unknownIdentityPenalty: config.UnknownIdentityPenalty, + minAppSpecificPenalty: config.MinAppSpecificPenalty, + stakedIdentityReward: config.StakedIdentityReward, + invalidSubscriptionPenalty: config.InvalidSubscriptionPenalty, } reg.appScoreUpdateWorkerPool = worker.NewWorkerPoolBuilder[peer.ID](lg.With().Str("component", "app_specific_score_update_worker_pool").Logger(), @@ -364,7 +337,7 @@ func (r *GossipSubAppSpecificScoreRegistry) stakingScore(pid peer.ID) (float64, Err(err). Bool(logging.KeySuspicious, true). Msg("invalid peer identity, penalizing peer") - return DefaultUnknownIdentityPenalty, flow.Identifier{}, 0 + return r.unknownIdentityPenalty, flow.Identifier{}, 0 } lg = lg.With(). @@ -377,13 +350,13 @@ func (r *GossipSubAppSpecificScoreRegistry) stakingScore(pid peer.ID) (float64, if flowId.Role == flow.RoleAccess { lg.Trace(). Msg("pushing access node to edge by penalizing with minimum penalty value") - return MinAppSpecificPenalty, flowId.NodeID, flowId.Role + return r.minAppSpecificPenalty, flowId.NodeID, flowId.Role } lg.Trace(). Msg("rewarding well-behaved non-access node peer with maximum reward value") - return DefaultStakedIdentityReward, flowId.NodeID, flowId.Role + return r.stakedIdentityReward, flowId.NodeID, flowId.Role } func (r *GossipSubAppSpecificScoreRegistry) subscriptionPenalty(pid peer.ID, flowId flow.Identifier, role flow.Role) float64 { @@ -395,7 +368,7 @@ func (r *GossipSubAppSpecificScoreRegistry) subscriptionPenalty(pid peer.ID, flo Hex("flow_id", logging.ID(flowId)). Bool(logging.KeySuspicious, true). Msg("invalid subscription detected, penalizing peer") - return DefaultInvalidSubscriptionPenalty + return r.invalidSubscriptionPenalty } return 0 @@ -458,10 +431,19 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( Msg("applied misbehaviour penalty and updated application specific penalty") } +type DecayFunctionConfig struct { + SlowerDecayPenaltyThreshold float64 + DecayRateReductionFactor float64 + DecayAdjustInterval time.Duration + MaximumSpamPenaltyDecayFactor float64 + MinimumSpamPenaltyDecayFactor float64 + SkipDecayThreshold float64 +} + // DefaultDecayFunction is the default decay function that is used to decay the application specific penalty of a peer. // It is used if no decay function is provided in the configuration. // It decays the application specific penalty of a peer if it is negative. -func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float64, decayAdjustInterval time.Duration) netcache.PreprocessorFunc { +func DefaultDecayFunction(cfg *DecayFunctionConfig) netcache.PreprocessorFunc { return func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { if record.Penalty >= 0 { // no need to decay the penalty if it is positive, the reason is currently the app specific penalty @@ -470,10 +452,10 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 return record, nil } - if record.Penalty > skipDecayThreshold { + if record.Penalty > cfg.SkipDecayThreshold { // penalty is negative but greater than the threshold, we set it to 0. record.Penalty = 0 - record.Decay = MaximumSpamPenaltyDecayFactor + record.Decay = cfg.MaximumSpamPenaltyDecayFactor record.LastDecayAdjustment = time.Time{} return record, nil } @@ -485,10 +467,10 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 } record.Penalty = penalty - if record.Penalty <= slowerDecayPenaltyThreshold { - if time.Since(record.LastDecayAdjustment) > decayAdjustInterval || record.LastDecayAdjustment.IsZero() { + if record.Penalty <= cfg.SlowerDecayPenaltyThreshold { + if time.Since(record.LastDecayAdjustment) > cfg.DecayAdjustInterval || record.LastDecayAdjustment.IsZero() { // reduces the decay speed flooring at MinimumSpamRecordDecaySpeed - record.Decay = math.Min(record.Decay+decayRateDecrement, MinimumSpamPenaltyDecayFactor) + record.Decay = math.Min(record.Decay+cfg.DecayRateReductionFactor, cfg.MinimumSpamPenaltyDecayFactor) record.LastDecayAdjustment = time.Now() } } @@ -496,13 +478,15 @@ func DefaultDecayFunction(slowerDecayPenaltyThreshold, decayRateDecrement float6 } } -// InitAppScoreRecordState initializes the gossipsub spam record state for a peer. +// InitAppScoreRecordStateFunc returns a callback that initializes the gossipsub spam record state for a peer. // Returns: -// - a gossipsub spam record with the default decay value and 0 penalty. -func InitAppScoreRecordState() p2p.GossipSubSpamRecord { - return p2p.GossipSubSpamRecord{ - Decay: MaximumSpamPenaltyDecayFactor, - Penalty: 0, - LastDecayAdjustment: time.Now(), +// - a func that returns a gossipsub spam record with the default decay value and 0 penalty. +func InitAppScoreRecordStateFunc(maximumSpamPenaltyDecayFactor float64) func() p2p.GossipSubSpamRecord { + return func() p2p.GossipSubSpamRecord { + return p2p.GossipSubSpamRecord{ + Decay: maximumSpamPenaltyDecayFactor, + Penalty: 0, + LastDecayAdjustment: time.Now(), + } } } diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index 1a454ee5312..162d98d22d1 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -57,13 +57,15 @@ func TestScoreRegistry_FreshStart(t *testing.T) { require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) + maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward + queryTime := time.Now() require.Eventually(t, func() bool { // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - return score == scoring.MaxAppSpecificReward + return score == maxAppSpecificReward }, 5*time.Second, 100*time.Millisecond) // still the spamRecords should not have the peer id (as there is no spam record for the peer id). @@ -73,7 +75,7 @@ func TestScoreRegistry_FreshStart(t *testing.T) { score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. require.True(t, exists) require.True(t, updated.After(queryTime)) - require.Equal(t, scoring.MaxAppSpecificReward, score) + require.Equal(t, maxAppSpecificReward, score) // stop the registry. cancel() @@ -139,13 +141,15 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // eventually, the app specific score should be updated in the cache. require.Eventually(t, func() bool { // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - return scoring.MaxAppSpecificReward == score + return scoreOptParameters.Rewards.MaxAppSpecificReward == score }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -158,8 +162,8 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. assert.True(t, ok) assert.NoError(t, err) - assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. + assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -239,12 +243,14 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // eventually the app specific score should be updated in the cache to the penalty value for unknown identity. require.Eventually(t, func() bool { // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // peer does not have spam record, but has an unknown identity. Hence, the app specific score should be the staking penalty. - return scoring.DefaultUnknownIdentityPenalty == score + return scoreOptParameters.Penalties.UnknownIdentityPenalty == score }, 5*time.Second, 100*time.Millisecond) // queryTime := time.Now() @@ -258,8 +264,8 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. require.True(t, ok) require.NoError(t, err) - require.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. - require.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. + require.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. + require.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -269,7 +275,7 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty // and the staking penalty. // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. - return unittest.AreNumericallyClose(expectedPenalty+scoring.DefaultUnknownIdentityPenalty, score, 0.01) + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.Penalties.UnknownIdentityPenalty, score, 0.01) }, 5*time.Second, 10*time.Millisecond) // the app specific score should now be updated in the cache. @@ -277,8 +283,8 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 require.True(t, exists) require.True(t, updated.After(queryTime)) - unittest.RequireNumericallyClose(t, expectedPenalty+scoring.DefaultUnknownIdentityPenalty, score, 0.01) - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.Penalties.UnknownIdentityPenalty, score, 0.01) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. // stop the registry. cancel() @@ -341,13 +347,15 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // peer does not have spam record, but has invalid subscription. Hence, the app specific score should be subscription penalty. // eventually the app specific score should be updated in the cache to the penalty value for subscription penalty. require.Eventually(t, func() bool { // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // peer does not have spam record, but has an invalid subscription penalty. - return scoring.DefaultInvalidSubscriptionPenalty == score + return scoreOptParameters.Penalties.InvalidSubscriptionPenalty == score }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -361,7 +369,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) // decay should be initialized to the initial state. + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -371,14 +379,14 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty // and the staking penalty. // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. - return unittest.AreNumericallyClose(expectedPenalty+scoring.DefaultInvalidSubscriptionPenalty, score, 0.01) + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.Penalties.InvalidSubscriptionPenalty, score, 0.01) }, 5*time.Second, 10*time.Millisecond) // the app specific score should now be updated in the cache. score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. require.True(t, exists) require.True(t, updated.After(queryTime)) - unittest.RequireNumericallyClose(t, expectedPenalty+scoring.DefaultInvalidSubscriptionPenalty, score, 0.01) + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.Penalties.InvalidSubscriptionPenalty, score, 0.01) // stop the registry. cancel() @@ -446,7 +454,7 @@ func TestScoreRegistry_SpamPenaltyDecaysInCache(t *testing.T) { penaltyValueFixtures().RpcPublishMessage // the lower bound is the sum of the penalties with decay assuming the decay is applied 4 times to the sum of the penalties. // in reality, the decay is applied 4 times to the first penalty, then 3 times to the second penalty, and so on. - r := scoring.InitAppScoreRecordState() + r := scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)() scoreLowerBound := scoreUpperBound * math.Pow(r.Decay, 4) // eventually, the app specific score should be updated in the cache. @@ -488,6 +496,8 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ PeerID: peerID, @@ -511,7 +521,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should reset back to default staking reward. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultStakedIdentityReward + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Rewards.StakedIdentityReward }, 5*time.Second, 100*time.Millisecond) // the penalty should now be zero. @@ -550,10 +560,12 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // initially, the app specific score should be the default unknown identity penalty. require.Eventually(t, func() bool { score := reg.AppSpecificScoreFunc()(peerID) - return score == scoring.DefaultUnknownIdentityPenalty + return score == scoreOptParameters.Penalties.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -571,7 +583,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. // (scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().Graft, scoring.DefaultUnknownIdentityPenalty). - return score < scoring.DefaultUnknownIdentityPenalty && score > scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().Graft + return score < scoreOptParameters.Penalties.UnknownIdentityPenalty && score > scoreOptParameters.Penalties.UnknownIdentityPenalty+penaltyValueFixtures().Graft }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -582,7 +594,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultUnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Penalties.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -621,10 +633,12 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + // initially, the app specific score should be the default invalid subscription penalty. require.Eventually(t, func() bool { score := reg.AppSpecificScoreFunc()(peerID) - return score == scoring.DefaultInvalidSubscriptionPenalty + return score == scoreOptParameters.Penalties.InvalidSubscriptionPenalty }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -639,7 +653,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. // (DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().Graft, scoring.DefaultInvalidSubscriptionPenalty). - return score < scoring.DefaultInvalidSubscriptionPenalty && score > scoring.DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().Graft + return score < scoreOptParameters.Penalties.InvalidSubscriptionPenalty && score > scoreOptParameters.Penalties.InvalidSubscriptionPenalty+penaltyValueFixtures().Graft }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -650,7 +664,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the default invalid subscription penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoring.DefaultUnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Penalties.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -690,16 +704,19 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { // initially, the spamRecords should not have the peer ids. assert.False(t, spamRecords.Has(peer1)) assert.False(t, spamRecords.Has(peer2)) + + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoringRegistryParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters // since the both peers do not have a spam record, their app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. - return scoring.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer1) && scoring.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer2) + return scoreOptParameters.Rewards.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer1) && scoreOptParameters.Rewards.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer2) }, 5*time.Second, 100*time.Millisecond) // simulate sustained malicious activity from peer1, eventually the decay speed // for a spam record should be reduced to the MinimumSpamPenaltyDecayFactor - prevDecay := scoring.MaximumSpamPenaltyDecayFactor + prevDecay := scoringRegistryParameters.MaximumSpamPenaltyDecayFactor tolerance := 0.1 require.Eventually(t, func() bool { reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -711,7 +728,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { require.True(t, ok) assert.Less(t, math.Abs(prevDecay-record.Decay), tolerance) prevDecay = record.Decay - return record.Decay == scoring.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) // initialize a spam record for peer2 @@ -719,24 +736,24 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { PeerID: peer2, MsgType: p2pmsg.CtrlMsgPrune, }) - // reduce penalty and increase Decay to scoring.MinimumSpamPenaltyDecayFactor + // reduce penalty and increase Decay to scoringRegistryParameters.MinimumSpamPenaltyDecayFactor record, err := spamRecords.Update(peer2, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty = -.1 - record.Decay = scoring.MinimumSpamPenaltyDecayFactor + record.Decay = scoringRegistryParameters.MinimumSpamPenaltyDecayFactor return record }) require.NoError(t, err) - require.True(t, record.Decay == scoring.MinimumSpamPenaltyDecayFactor) + require.True(t, record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor) require.True(t, record.Penalty == -.1) // simulate sustained good behavior from peer 2, each time the spam record is read from the cache // using Get method the record penalty will be decayed until it is eventually reset to // 0 at this point the decay speed for the record should be reset to MaximumSpamPenaltyDecayFactor - // eventually after penalty reaches the skipDecaThreshold the record decay will be reset to scoring.MaximumSpamPenaltyDecayFactor + // eventually after penalty reaches the skipDecaThreshold the record decay will be reset to scoringRegistryParameters.MaximumSpamPenaltyDecayFactor require.Eventually(t, func() bool { record, err, ok := spamRecords.Get(peer2) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoring.MaximumSpamPenaltyDecayFactor && + return record.Decay == scoringRegistryParameters.MaximumSpamPenaltyDecayFactor && record.Penalty == 0 && record.LastDecayAdjustment.IsZero() }, 5*time.Second, time.Second) @@ -750,7 +767,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { record, err, ok := spamRecords.Get(peer1) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoring.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) // stop the registry. @@ -782,6 +799,8 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + for _, peerID := range peerIds { // initially, the spamRecords should not have the peer id. assert.False(t, spamRecords.Has(peerID)) @@ -792,7 +811,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { score := reg.AppSpecificScoreFunc()(peerID) // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - return score == scoring.MaxAppSpecificReward + return score == scoreOptParameters.Rewards.MaxAppSpecificReward }, 5*time.Second, 100*time.Millisecond) } @@ -830,7 +849,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordState().Decay, record.Decay) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // this peer has a spam record, with no subscription penalty. Hence, the app specific score should only be the spam penalty, // and the peer should be deprived of the default reward for its valid staked role. score := reg.AppSpecificScoreFunc()(peerID) @@ -938,7 +957,14 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), - scoring.DefaultDecayFunction(params.SpamRecordCache.PenaltyDecaySlowdownThreshold, params.SpamRecordCache.DecayRateReductionFactor, params.SpamRecordCache.PenaltyDecayEvaluationPeriod)) + scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ + SlowerDecayPenaltyThreshold: params.SpamRecordCache.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: params.SpamRecordCache.DecayRateReductionFactor, + DecayAdjustInterval: params.SpamRecordCache.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: params.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + })) appSpecificScoreCache := internal.NewAppSpecificScoreCache(100, unittest.Logger(), metrics.NewNoopCollector()) validator := mockp2p.NewSubscriptionValidator(t) @@ -952,7 +978,7 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring validator.On("Done").Return(f()).Maybe() cfg := &scoring.GossipSubAppSpecificScoreRegistryConfig{ Logger: unittest.Logger(), - Init: scoring.InitAppScoreRecordState, + Init: scoring.InitAppScoreRecordStateFunc(params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor), Penalty: penaltyValueFixtures(), IdProvider: mock.NewIdentityProvider(t), Validator: validator, @@ -962,9 +988,13 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { return cache }, - Parameters: params.AppSpecificScore, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - NetworkingType: network.PrivateNetwork, + Parameters: params.AppSpecificScore, + HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), + NetworkingType: network.PrivateNetwork, + UnknownIdentityPenalty: params.ScoreOption.Penalties.UnknownIdentityPenalty, + MinAppSpecificPenalty: params.ScoreOption.Penalties.MinAppSpecificPenalty, + StakedIdentityReward: params.ScoreOption.Rewards.StakedIdentityReward, + InvalidSubscriptionPenalty: params.ScoreOption.Penalties.InvalidSubscriptionPenalty, } for _, opt := range opts { opt(cfg) diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index a9bc3c90f91..aa9ec19d4c7 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -22,287 +22,17 @@ import ( "github.com/onflow/flow-go/utils/logging" ) -const ( - // DefaultAppSpecificScoreWeight is the default weight for app-specific scores. It is used to scale the app-specific - // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific - // scores and the other scores, so we set it to 1. - DefaultAppSpecificScoreWeight = 1 - - // MaxAppSpecificReward is the default reward for well-behaving staked peers. If a peer does not have - // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. - MaxAppSpecificReward = float64(100) - - // MaxAppSpecificPenalty is the maximum penalty for sever offenses that we apply to a remote node score. The score - // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of - // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to - // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty - // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. - MaxAppSpecificPenalty = -1 * MaxAppSpecificReward - MinAppSpecificPenalty = -1 - - // DefaultStakedIdentityReward is the default reward for staking peers. It is applied to the peer's score when - // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. - // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. - DefaultStakedIdentityReward = MaxAppSpecificReward - - // DefaultUnknownIdentityPenalty is the default penalty for unknown identity. It is applied to the peer's score when - // the peer is not in the identity list. - DefaultUnknownIdentityPenalty = MaxAppSpecificPenalty - - // DefaultInvalidSubscriptionPenalty is the default penalty for invalid subscription. It is applied to the peer's score when - // the peer subscribes to a topic that it is not authorized to subscribe to. - DefaultInvalidSubscriptionPenalty = MaxAppSpecificPenalty - - // DefaultGossipThreshold when a peer's penalty drops below this threshold, - // no gossip is emitted towards that peer and gossip from that peer is ignored. - // - // Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all gossips - // to and from peers with penalty -100 are ignored. - DefaultGossipThreshold = MaxAppSpecificPenalty + 1 - - // DefaultPublishThreshold when a peer's penalty drops below this threshold, - // self-published messages are not propagated towards this peer. - // - // Validation Constraint: - // PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all penalized peers are deprived of - // receiving any published messages. - DefaultPublishThreshold = MaxAppSpecificPenalty + 1 - - // DefaultGraylistThreshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., - // incoming RPCs from the peer are ignored. - // - // Validation Constraint: - // GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 - // - // How we use it: - // As current max penalty is -100, we set the threshold to -99 so that all penalized peers are graylisted. - DefaultGraylistThreshold = MaxAppSpecificPenalty + 1 - - // DefaultAcceptPXThreshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied - // peers if the originating peer's penalty exceeds this threshold. - // - // Validation Constraint: must be non-negative. - // - // How we use it: - // As current max reward is 100, we set the threshold to 99 so that we only receive supplied peers from - // well-behaved peers. - DefaultAcceptPXThreshold = MaxAppSpecificReward - 1 - - // DefaultOpportunisticGraftThreshold when the median peer penalty in the mesh drops below this value, - // the peer may select more peers with penalty above the median to opportunistically graft on the mesh. - // - // Validation Constraint: must be non-negative. - // - // How we use it: - // We set it to the MaxAppSpecificReward + 1 so that we only opportunistically graft peers that are not access nodes (i.e., with MinAppSpecificPenalty), - // or penalized peers (i.e., with MaxAppSpecificPenalty). - DefaultOpportunisticGraftThreshold = MaxAppSpecificReward + 1 - - // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above - // this threshold are dropped. - MaxDebugLogs = 50 - - // defaultDecayInterval is the default decay interval for the overall score of a peer at the GossipSub scoring - // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty - // and is not too long so that a well-behaved node can't recover from a penalty. - defaultDecayInterval = 1 * time.Minute - - // defaultDecayToZero is the default decay to zero for the overall score of a peer at the GossipSub scoring system. - // It defines the maximum value below which a peer scoring counter is reset to zero. - // This is to prevent the counter from decaying to a very small value. - // The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. - // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - // for a long time, and we can reset the counter. - defaultDecayToZero = 0.01 - - // defaultTopicSkipAtomicValidation is the default value for the skip atomic validation flag for topics. - // We set it to true, which means gossipsub parameter validation will not fail if we leave some of the - // topic parameters at their default values, i.e., zero. This is because we are not setting all - // topic parameters at the current implementation. - defaultTopicSkipAtomicValidation = true - - // defaultTopicInvalidMessageDeliveriesWeight this value is applied to the square of the number of invalid message deliveries on a topic. - // It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the - // publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid - // message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. - // The supporting math is as follows: - // - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). - // - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. - // - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). - // - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. - defaultTopicInvalidMessageDeliveriesWeight = -1.0 - - // defaultTopicInvalidMessageDeliveriesDecay decay factor used to decay the number of invalid message deliveries. - // The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to - // decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops - // sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will - // decay by 1% at each heartbeat interval. - // The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). - defaultTopicInvalidMessageDeliveriesDecay = .99 - - // defaultTopicTimeInMeshQuantum is the default time in mesh quantum for the GossipSub scoring system. It is used to gauge - // a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is - // in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. - // The reason of setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that - // are churners, i.e., peers that join and leave a topic mesh frequently. - defaultTopicTimeInMesh = time.Hour - - // defaultTopicWeight is the default weight of a topic in the GossipSub scoring system. - // The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. - // We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. - defaultTopicWeight = 1.0 - - // defaultTopicMeshMessageDeliveriesDecay is applied to the number of actual message deliveries in a topic mesh - // at each decay interval (i.e., defaultDecayInterval). - // It is used to decay the number of actual message deliveries, and prevents past message - // deliveries from affecting the current score of the peer. - // As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message - // deliveries will decay by 50% at each decay interval. - defaultTopicMeshMessageDeliveriesDecay = .5 - - // defaultTopicMeshMessageDeliveriesCap is the maximum number of actual message deliveries in a topic - // mesh that is used to calculate the score of a peer in that topic mesh. - // We set it to 1000, which means that the maximum number of actual message deliveries in a - // topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. - // This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual - // message deliveries and also affect the score of the peer in other topic meshes. - // When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic - // mesh will not be affected by the actual message deliveries in that topic mesh. - // Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh - // and then start under-performing in that topic mesh without being penalized. - defaultTopicMeshMessageDeliveriesCap = 1000 - - // defaultTopicMeshMessageDeliveriesThreshold is the threshold for the number of actual message deliveries in a - // topic mesh that is used to calculate the score of a peer in that topic mesh. - // If the number of actual message deliveries in a topic mesh is less than this value, - // the peer will be penalized by square of the difference between the actual message deliveries and the threshold, - // i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the - // threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). - // We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the - // maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer - // in that topic mesh. - defaultTopicMeshMessageDeliveryThreshold = 0.1 * defaultTopicMeshMessageDeliveriesCap - - // defaultTopicMeshDeliveriesWeight is the weight for applying penalty when a peer is under-performing in a topic mesh. - // Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold - // (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual - // message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and - // `actual` and `threshold` are the actual message deliveries and the threshold, respectively. - // We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer - // is not delivering any message in a topic mesh, it will not be disconnected. - // Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked - // nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh - // will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. - // It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose - // its MaxAppSpecificReward that is awarded by our app-specific scoring function. - // At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be - // causing a false positive network partition. - // TODO: we must increase the penalty for under-performing in a topic mesh in the future, and disconnect the peer if it is under-performing. - defaultTopicMeshMessageDeliveriesWeight = -0.05 * MaxAppSpecificReward / (defaultTopicMeshMessageDeliveryThreshold * defaultTopicMeshMessageDeliveryThreshold) - - // defaultMeshMessageDeliveriesWindow is the window size is time interval that we count a delivery of an already - // seen message towards the score of a peer in a topic mesh. The delivery is counted - // by GossipSub only if the previous sender of the message is different from the current sender. - // We set it to the decay interval of the GossipSub scoring system, which is 1 minute. - // It means that if a peer delivers a message that it has already seen less than one minute ago, - // the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. - // This also prevents replay attacks of messages that are older than one minute. As replayed messages will not - // be counted towards the actual message deliveries of a peer in a topic mesh. - defaultMeshMessageDeliveriesWindow = defaultDecayInterval - - // defaultMeshMessageDeliveryActivation is the time interval that we wait for a new peer that joins a topic mesh - // till start counting the number of actual message deliveries of that peer in that topic mesh. - // We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting - // the number of actual message deliveries of a peer in a topic mesh. - // With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the - // number of actual message deliveries of a peer in a topic mesh. This is to account for - // the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. - defaultMeshMessageDeliveriesActivation = 2 * defaultDecayInterval - - // defaultBehaviorPenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. - // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting - // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - // for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). - // When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // - // We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every - // excess RPC containing iHave broken promises. - // The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system). - defaultBehaviourPenaltyThreshold = 10 - - // defaultBehaviorPenaltyWeight is the weight for applying penalty when a peer misbehavior goes beyond the threshold. - // Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting - // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - // This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // - // The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. - // We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose - // its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. - // Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). - // - // The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty - // if misbehavior > threshold + sqrt(2) * 10. - // As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight - // is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that - // the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. - // Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold - // where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality - // to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system that are reported by the engines). - defaultBehaviourPenaltyWeight = 0.01 * MaxAppSpecificPenalty - - // defaultBehaviorPenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is - // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). - // - // An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - // For iHave broken promises, the gossipsub scoring works as follows: - // It samples ONLY A SINGLE iHave out of the entire RPC. - // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. - // That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. - // - // The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. - // We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - // With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, - // We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. - // - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system that is based on the engines report). - defaultBehaviourPenaltyDecay = 0.99 -) - // ScoreOption is a functional option for configuring the peer scoring system. // TODO: rename it to ScoreManager. type ScoreOption struct { component.Component logger zerolog.Logger - peerScoreParams *pubsub.PeerScoreParams - peerThresholdParams *pubsub.PeerScoreThresholds - validator p2p.SubscriptionValidator - appScoreFunc func(peer.ID) float64 + peerScoreParams *pubsub.PeerScoreParams + peerThresholdParams *pubsub.PeerScoreThresholds + defaultTopicScoreParams *pubsub.TopicScoreParams + validator p2p.SubscriptionValidator + appScoreFunc func(peer.ID) float64 } type ScoreOptionConfig struct { @@ -366,7 +96,7 @@ func (c *ScoreOptionConfig) SetRegisterNotificationConsumerFunc(f func(p2p.Gossi // NewScoreOption creates a new penalty option with the given configuration. func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { - throttledSampler := logging.BurstSampler(MaxDebugLogs, time.Second) + throttledSampler := logging.BurstSampler(cfg.params.ScoreOption.MaxDebugLogs, time.Second) logger := cfg.logger.With(). Str("module", "pubsub_score_option"). Logger(). @@ -377,9 +107,9 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( validator := NewSubscriptionValidator(cfg.logger, provider) scoreRegistry, err := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ Logger: logger, - Penalty: DefaultGossipSubCtrlMsgPenaltyValue(), + Penalty: GossipSubCtrlMsgPenaltyValues(cfg.params.ScoringRegistryParameters.MisbehaviourPenalties), Validator: validator, - Init: InitAppScoreRecordState, + Init: InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor), IdProvider: cfg.provider, HeroCacheMetricsFactory: cfg.heroCacheMetricsFactory, AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { @@ -389,23 +119,73 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) return netcache.NewGossipSubSpamRecordCache(cfg.params.SpamRecordCache.CacheSize, cfg.logger, collector, - DefaultDecayFunction( - cfg.params.SpamRecordCache.PenaltyDecaySlowdownThreshold, - cfg.params.SpamRecordCache.DecayRateReductionFactor, - cfg.params.SpamRecordCache.PenaltyDecayEvaluationPeriod)) + DefaultDecayFunction(&DecayFunctionConfig{ + SlowerDecayPenaltyThreshold: cfg.params.SpamRecordCache.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: cfg.params.SpamRecordCache.DecayRateReductionFactor, + DecayAdjustInterval: cfg.params.SpamRecordCache.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + })) }, - Parameters: cfg.params.AppSpecificScore, - NetworkingType: cfg.networkingType, + Parameters: cfg.params.AppSpecificScore, + NetworkingType: cfg.networkingType, + UnknownIdentityPenalty: cfg.params.ScoreOption.Penalties.UnknownIdentityPenalty, + MinAppSpecificPenalty: cfg.params.ScoreOption.Penalties.MinAppSpecificPenalty, + StakedIdentityReward: cfg.params.ScoreOption.Rewards.StakedIdentityReward, + InvalidSubscriptionPenalty: cfg.params.ScoreOption.Penalties.InvalidSubscriptionPenalty, }) if err != nil { return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) } s := &ScoreOption{ - logger: logger, - validator: validator, - peerScoreParams: defaultPeerScoreParams(), - appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), + logger: logger, + validator: validator, + peerScoreParams: &pubsub.PeerScoreParams{ + Topics: make(map[string]*pubsub.TopicScoreParams), + // we don't set all the parameters, so we skip the atomic validation. + // atomic validation fails initialization if any parameter is not set. + SkipAtomicValidation: cfg.params.ScoreOption.TopicValidation.SkipAtomicValidation, + // DecayInterval is the interval over which we decay the effect of past behavior, so that + // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval + // that GossipSub uses to refresh the scores of all peers. + DecayInterval: cfg.params.ScoreOption.DecayInterval, + // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. + // This is to prevent the counter from decaying to a very small value. + // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + // for a long time, and we can reset the counter. + DecayToZero: cfg.params.ScoreOption.DecayToZero, + // AppSpecificWeight is the weight of the application specific penalty. + AppSpecificWeight: cfg.params.ScoreOption.AppSpecificScoreWeight, + // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. + BehaviourPenaltyThreshold: cfg.params.ScoreOption.Behaviour.PenaltyThreshold, + // PenaltyWeight is the weight of the GossipSub-level penalty. + BehaviourPenaltyWeight: cfg.params.ScoreOption.Behaviour.PenaltyWeight, + // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). + BehaviourPenaltyDecay: cfg.params.ScoreOption.Behaviour.PenaltyDecay, + }, + peerThresholdParams: &pubsub.PeerScoreThresholds{ + GossipThreshold: cfg.params.ScoreOption.Thresholds.Gossip, + PublishThreshold: cfg.params.ScoreOption.Thresholds.Publish, + GraylistThreshold: cfg.params.ScoreOption.Thresholds.Graylist, + AcceptPXThreshold: cfg.params.ScoreOption.Thresholds.AcceptPX, + OpportunisticGraftThreshold: cfg.params.ScoreOption.Thresholds.OpportunisticGraft, + }, + defaultTopicScoreParams: &pubsub.TopicScoreParams{ + TopicWeight: cfg.params.ScoreOption.TopicValidation.TopicWeight, + SkipAtomicValidation: cfg.params.ScoreOption.TopicValidation.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: cfg.params.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: cfg.params.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: cfg.params.ScoreOption.TopicValidation.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: cfg.params.ScoreOption.TopicValidation.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + }, + appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), } // set the app specific penalty function for the penalty option @@ -462,8 +242,6 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( } func (s *ScoreOption) BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pubsub.PeerScoreThresholds) { - s.preparePeerScoreThresholds() - s.logger.Info(). Float64("gossip_threshold", s.peerThresholdParams.GossipThreshold). Float64("publish_threshold", s.peerThresholdParams.PublishThreshold). @@ -481,16 +259,6 @@ func (s *ScoreOption) BuildFlowPubSubScoreOption() (*pubsub.PeerScoreParams, *pu return s.peerScoreParams, s.peerThresholdParams } -func (s *ScoreOption) preparePeerScoreThresholds() { - s.peerThresholdParams = &pubsub.PeerScoreThresholds{ - GossipThreshold: DefaultGossipThreshold, - PublishThreshold: DefaultPublishThreshold, - GraylistThreshold: DefaultGraylistThreshold, - AcceptPXThreshold: DefaultAcceptPXThreshold, - OpportunisticGraftThreshold: DefaultOpportunisticGraftThreshold, - } -} - // TopicScoreParams returns the topic score parameters for the given topic. If the topic // score parameters are not set, it returns the default topic score parameters. // The custom topic parameters are set at the initialization of the score option. @@ -502,59 +270,7 @@ func (s *ScoreOption) preparePeerScoreThresholds() { func (s *ScoreOption) TopicScoreParams(topic *pubsub.Topic) *pubsub.TopicScoreParams { params, exists := s.peerScoreParams.Topics[topic.String()] if !exists { - return DefaultTopicScoreParams() + return s.defaultTopicScoreParams } return params } - -func defaultPeerScoreParams() *pubsub.PeerScoreParams { - // DO NOT CHANGE THE DEFAULT VALUES, THEY ARE TUNED FOR THE BEST SECURITY PRACTICES. - return &pubsub.PeerScoreParams{ - Topics: make(map[string]*pubsub.TopicScoreParams), - // we don't set all the parameters, so we skip the atomic validation. - // atomic validation fails initialization if any parameter is not set. - SkipAtomicValidation: true, - // DecayInterval is the interval over which we decay the effect of past behavior, so that - // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval - // that GossipSub uses to refresh the scores of all peers. - DecayInterval: defaultDecayInterval, - // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. - // This is to prevent the counter from decaying to a very small value. - // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - // for a long time, and we can reset the counter. - DecayToZero: defaultDecayToZero, - // AppSpecificWeight is the weight of the application specific penalty. - AppSpecificWeight: DefaultAppSpecificScoreWeight, - // BehaviourPenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. - BehaviourPenaltyThreshold: defaultBehaviourPenaltyThreshold, - // BehaviourPenaltyWeight is the weight of the GossipSub-level penalty. - BehaviourPenaltyWeight: defaultBehaviourPenaltyWeight, - // BehaviourPenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). - BehaviourPenaltyDecay: defaultBehaviourPenaltyDecay, - } -} - -// DefaultTopicScoreParams returns the default score params for topics. -func DefaultTopicScoreParams() *pubsub.TopicScoreParams { - // DO NOT CHANGE THE DEFAULT VALUES, THEY ARE TUNED FOR THE BEST SECURITY PRACTICES. - p := &pubsub.TopicScoreParams{ - TopicWeight: defaultTopicWeight, - SkipAtomicValidation: defaultTopicSkipAtomicValidation, - InvalidMessageDeliveriesWeight: defaultTopicInvalidMessageDeliveriesWeight, - InvalidMessageDeliveriesDecay: defaultTopicInvalidMessageDeliveriesDecay, - TimeInMeshQuantum: defaultTopicTimeInMesh, - MeshMessageDeliveriesWeight: defaultTopicMeshMessageDeliveriesWeight, - MeshMessageDeliveriesDecay: defaultTopicMeshMessageDeliveriesDecay, - MeshMessageDeliveriesCap: defaultTopicMeshMessageDeliveriesCap, - MeshMessageDeliveriesThreshold: defaultTopicMeshMessageDeliveryThreshold, - MeshMessageDeliveriesWindow: defaultMeshMessageDeliveriesWindow, - MeshMessageDeliveriesActivation: defaultMeshMessageDeliveriesActivation, - } - - if p.MeshMessageDeliveriesWeight >= 0 { - // GossipSub also does a validation, but we want to panic as early as possible. - panic(fmt.Sprintf("invalid mesh message deliveries weight %f", p.MeshMessageDeliveriesWeight)) - } - - return p -} From 1ef9f1bf1ffc0cea3606add814376f5e4dac1a62 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 5 Jan 2024 20:33:11 -0500 Subject: [PATCH 04/19] relax behaviour penalty configs --- config/default-config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/default-config.yml b/config/default-config.yml index 7cf1769978c..c08c4a0b39f 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -332,7 +332,7 @@ network-config: # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through # the ALSP system). - penalty-threshold: 10 + penalty-threshold: 1000 # The weight for applying penalty when a peer misbehavior goes beyond the threshold. # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh @@ -361,7 +361,7 @@ network-config: # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through # the ALSP system that are reported by the engines). - penalty-weight: -1 + penalty-weight: -0.01 # The decay interval for the misbehavior counter of a peer. The misbehavior counter is # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). # @@ -379,7 +379,7 @@ network-config: # # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through # the ALSP system that is based on the engines report). - penalty-decay: 0.99 + penalty-decay: 0.5 topic: # This is the default value for the skip atomic validation flag for topics. # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the From cb7c070be7093dbdf49750ab30f49f5388402e55 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 5 Jan 2024 20:34:03 -0500 Subject: [PATCH 05/19] Update default-config.yml --- config/default-config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default-config.yml b/config/default-config.yml index c08c4a0b39f..cdc7525371f 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -1,5 +1,5 @@ config-file: "./default-config.yml" -# WARNING: Only modify the gossipsub configurations below if you fully understand their implications. +# WARNING: Only modify the network configurations below if you fully understand their implications. # Incorrect settings may lead to system instability, security vulnerabilities, or degraded performance. # Make changes with caution and refer to the documentation for guidance. # Network configuration. From c6ee697f39f8a7ad87843ea9772851f597c1bfd5 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Mon, 8 Jan 2024 12:29:34 -0500 Subject: [PATCH 06/19] add missing ScoreParamsKey to score option flags --- network/netconf/flags.go | 115 ++++++++++++++++---------------- network/p2p/config/gossipsub.go | 4 +- 2 files changed, 60 insertions(+), 59 deletions(-) diff --git a/network/netconf/flags.go b/network/netconf/flags.go index 8191faca891..fee7e4fd71b 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -320,124 +320,124 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { config.GossipSub.ScoringParameters.DecayInterval, "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + "the minimum speed at which the spam penalty value of a peer is decayed") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + "the maximum rate at which the spam penalty value of a peer decays") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + "the threshold for which when the negative penalty is above this value, the decay function will not be called") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.GraftMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PruneMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IHaveMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IWantMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishMisbehaviourKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PublishMisbehaviour, + "the penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.ClusterPrefixedReductionFactor, + "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), config.GossipSub.ScoringParameters.ScoreOption.AppSpecificScoreWeight, "the weight for app-specific scores") - flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), config.GossipSub.ScoringParameters.ScoreOption.MaxDebugLogs, "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), config.GossipSub.ScoringParameters.ScoreOption.DecayInterval, "the decay interval for the overall score of a peer at the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), config.GossipSub.ScoringParameters.ScoreOption.DecayToZero, "the decay to zero for the overall score of a peer at the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), config.GossipSub.ScoringParameters.ScoreOption.Penalties.MaxAppSpecificPenalty, "the maximum penalty for sever offenses that we apply to a remote node score") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), config.GossipSub.ScoringParameters.ScoreOption.Penalties.MinAppSpecificPenalty, "the minimum penalty for sever offenses that we apply to a remote node score") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), config.GossipSub.ScoringParameters.ScoreOption.Penalties.UnknownIdentityPenalty, "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), config.GossipSub.ScoringParameters.ScoreOption.Penalties.InvalidSubscriptionPenalty, "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), config.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward, "the reward for well-behaving staked peers") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), config.GossipSub.ScoringParameters.ScoreOption.Rewards.StakedIdentityReward, "the reward for staking peers") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Gossip, "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Publish, "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Graylist, "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Thresholds.AcceptPX, "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Thresholds.OpportunisticGraft, "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, - "the minimum speed at which the spam penalty value of a peer is decayed") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, - "the maximum rate at which the spam penalty value of a peer decays") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, - "the threshold for which when the negative penalty is above this value, the decay function will not be called") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.GraftMisbehaviour, - "the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneMisbehaviourKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PruneMisbehaviour, - "the penalty applied to the application specific penalty when a peer conducts a prune misbehaviour") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveMisbehaviourKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IHaveMisbehaviour, - "the penalty applied to the application specific penalty when a peer conducts a iHave misbehaviour") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantMisbehaviourKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.IWantMisbehaviour, - "the penalty applied to the application specific penalty when a peer conducts a iWant misbehaviour") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishMisbehaviourKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.PublishMisbehaviour, - "the penalty applied to the application specific penalty when a peer conducts a rpc publish message misbehaviour") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.ClusterPrefixedReductionFactor, - "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") - - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyThreshold, "the threshold when the behavior of a peer is considered as bad by GossipSub") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyWeight, "the weight for applying penalty when a peer misbehavior goes beyond the threshold") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyDecay, "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") - flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.SkipAtomicValidation, "the default value for the skip atomic validation flag for topics") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, "this value is applied to the square of the number of invalid message deliveries on a topic") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, "the decay factor used to decay the number of invalid message deliveries") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TimeInMeshQuantum, "the time in mesh quantum for the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TopicWeight, "the weight of a topic in the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshDeliveriesWeight, "the weight for applying penalty when a peer is under-performing in a topic mesh") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } @@ -515,6 +515,7 @@ func SetAliases(conf *viper.Viper) error { if !ok { return fmt.Errorf("invalid network configuration missing configuration key flag name %s check config file and cli flags", flagName) } + conf.RegisterAlias(fullKey, flagName) } return nil diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index 750257fed6c..20741a8bfc8 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -91,8 +91,8 @@ type ScoringParameters struct { SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` // DecayInterval is the interval at which the counters associated with a peer behavior in GossipSub system are decayed. DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` - ScoreOption ScoreOption `mapstructure:"score-option"` - ScoringRegistryParameters ScoringRegistryParameters `mapstructure:"scoring-registry"` + ScoreOption ScoreOption `validate:"required" mapstructure:"score-option"` + ScoringRegistryParameters ScoringRegistryParameters `validate:"required" mapstructure:"scoring-registry"` } const ( From 8fcaaa8b937a104828db4e3ed0adb868ed4c27d3 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Mon, 8 Jan 2024 13:15:01 -0500 Subject: [PATCH 07/19] Update score_registry.go --- network/p2p/config/score_registry.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/network/p2p/config/score_registry.go b/network/p2p/config/score_registry.go index ea5709fde5c..95437d90111 100644 --- a/network/p2p/config/score_registry.go +++ b/network/p2p/config/score_registry.go @@ -24,11 +24,11 @@ const ( ) type MisbehaviourPenalties struct { - SkipDecayThreshold float64 `validate:"gt=0,lte=1" mapstructure:"skip-decay-threshold"` - GraftMisbehaviour float64 `validate:"gt=0" mapstructure:"graft"` - PruneMisbehaviour float64 `validate:"gt=0" mapstructure:"prune"` - IHaveMisbehaviour float64 `validate:"gt=0" mapstructure:"ihave"` - IWantMisbehaviour float64 `validate:"gt=0" mapstructure:"iwant"` - PublishMisbehaviour float64 `validate:"gt=0" mapstructure:"publish"` + SkipDecayThreshold float64 `validate:"gt=-1,lt=0" mapstructure:"skip-decay-threshold"` + GraftMisbehaviour float64 `validate:"lt=0" mapstructure:"graft"` + PruneMisbehaviour float64 `validate:"lt=0" mapstructure:"prune"` + IHaveMisbehaviour float64 `validate:"lt=0" mapstructure:"ihave"` + IWantMisbehaviour float64 `validate:"lt=0" mapstructure:"iwant"` + PublishMisbehaviour float64 `validate:"lt=0" mapstructure:"publish"` ClusterPrefixedReductionFactor float64 `validate:"gt=0,lte=1" mapstructure:"cluster-prefixed-reduction-factor"` } From f51fac97b63c77c2d83671b5aed9b2b9352ae3fc Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Thu, 11 Jan 2024 21:29:57 -0500 Subject: [PATCH 08/19] rename ScoreOption -> InternalPeerScoring --- config/default-config.yml | 2 +- .../validation_inspector_test.go | 2 +- .../test/gossipsub/scoring/ihave_spam_test.go | 40 ++++++------- .../test/gossipsub/scoring/scoring_test.go | 14 ++--- network/netconf/flags.go | 58 +++++++++---------- network/p2p/config/gossipsub.go | 2 +- network/p2p/config/score_option.go | 6 +- network/p2p/scoring/app_score_test.go | 4 +- network/p2p/scoring/registry_test.go | 26 ++++----- network/p2p/scoring/score_option.go | 56 +++++++++--------- 10 files changed, 105 insertions(+), 105 deletions(-) diff --git a/config/default-config.yml b/config/default-config.yml index cdc7525371f..a94970a25cc 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -231,7 +231,7 @@ network-config: penalty-decay-evaluation-period: 10m # the intervals at which counters associated with a peer behavior in gossipsub system are decayed. decay-interval: 1m - score-option: + internal-peer-scoring: # The weight for app-specific scores. # It is used to scale the app-specific scores to the same range as the other scores. # At the current version, we don't distinguish between the app-specific scores diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go index e147605de37..248432c5afa 100644 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go +++ b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go @@ -1104,7 +1104,7 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsWithMalformedTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsInvalidSporkIDTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsDuplicateTopic) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // wait for three GossipSub heartbeat intervals to ensure that the victim node has penalized the spammer node. require.Eventually(t, func() bool { score, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) 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 e9d0ef3558b..088adaca1a1 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -133,21 +133,21 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // since spammer is not yet considered to be penalized, its score must be greater than the gossipsub health thresholds. require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, spammerScore) // eventually, after a heartbeat the spammer behavioral counter must be decayed @@ -316,27 +316,27 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // 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-5*5*0.01*scoreParams.ScoreOption.Rewards.MaxAppSpecificReward, + initScore-5*5*0.01*scoreParams.InternalPeerScoring.Rewards.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-5*5*0.1*scoreParams.ScoreOption.Rewards.MaxAppSpecificReward, + initScore-5*5*0.1*scoreParams.InternalPeerScoring.Rewards.MaxAppSpecificReward, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, spammerScore) // since the spammer score is above the gossip, graylist and publish thresholds, it should be still able to exchange messages with victim. @@ -371,21 +371,21 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // victim will not exchange messages with it anymore, and also that it will be graylisted meaning all incoming and outgoing RPCs to and from the spammer will be dropped by the victim. require.Lessf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be less than gossip threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Gossip, + scoreParams.InternalPeerScoring.Thresholds.Gossip, spammerScore) require.Lessf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, "sanity check failed, the score of the spammer node must be less than publish threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Publish, + scoreParams.InternalPeerScoring.Thresholds.Publish, spammerScore) require.Lessf(t, spammerScore, - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be less than graylist threshold: %f, actual: %f", - scoreParams.ScoreOption.Thresholds.Graylist, + scoreParams.InternalPeerScoring.Thresholds.Graylist, spammerScore) // since the spammer score is below the gossip, graylist and publish thresholds, it should not be able to exchange messages with victim anymore. diff --git a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go index 173b49f4b54..782dea22fd3 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go @@ -151,15 +151,15 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun if !ok { return false } - if spammerScore >= scoreParams.ScoreOption.Thresholds.Gossip { + if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Gossip { // ensure the score is low enough so that no gossip is routed by victim node to spammer node. return false } - if spammerScore >= scoreParams.ScoreOption.Thresholds.Publish { + if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Publish { // ensure the score is low enough so that non of the published messages of the victim node are routed to the spammer node. return false } - if spammerScore >= scoreParams.ScoreOption.Thresholds.Graylist { + if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Graylist { // ensure the score is low enough so that the victim node does not accept RPC messages from the spammer node. return false } @@ -257,7 +257,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { return unittest.ProposalFixture() }) - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // Also initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked @@ -373,7 +373,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { } } - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // Initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked @@ -485,7 +485,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // Initially the replaying node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and initially every node is considered staked @@ -596,7 +596,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { func defaultTopicScoreParams(t *testing.T) *pubsub.TopicScoreParams { defaultConfig, err := config.DefaultConfig() require.NoError(t, err) - topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.ScoreOption.TopicValidation + topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation p := &pubsub.TopicScoreParams{ TopicWeight: topicScoreParams.TopicWeight, SkipAtomicValidation: topicScoreParams.SkipAtomicValidation, diff --git a/network/netconf/flags.go b/network/netconf/flags.go index fee7e4fd71b..56d52bfba18 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -349,96 +349,96 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.AppSpecificScoreWeight, + config.GossipSub.ScoringParameters.InternalPeerScoring.AppSpecificScoreWeight, "the weight for app-specific scores") flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - config.GossipSub.ScoringParameters.ScoreOption.MaxDebugLogs, + config.GossipSub.ScoringParameters.InternalPeerScoring.MaxDebugLogs, "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - config.GossipSub.ScoringParameters.ScoreOption.DecayInterval, + config.GossipSub.ScoringParameters.InternalPeerScoring.DecayInterval, "the decay interval for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - config.GossipSub.ScoringParameters.ScoreOption.DecayToZero, + config.GossipSub.ScoringParameters.InternalPeerScoring.DecayToZero, "the decay to zero for the overall score of a peer at the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.MaxAppSpecificPenalty, + config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.MaxAppSpecificPenalty, "the maximum penalty for sever offenses that we apply to a remote node score") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.MinAppSpecificPenalty, + config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.MinAppSpecificPenalty, "the minimum penalty for sever offenses that we apply to a remote node score") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.UnknownIdentityPenalty, + config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.UnknownIdentityPenalty, "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - config.GossipSub.ScoringParameters.ScoreOption.Penalties.InvalidSubscriptionPenalty, + config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - config.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward, + config.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.MaxAppSpecificReward, "the reward for well-behaving staked peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - config.GossipSub.ScoringParameters.ScoreOption.Rewards.StakedIdentityReward, + config.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.StakedIdentityReward, "the reward for staking peers") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Gossip, + config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Gossip, "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Publish, + config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Publish, "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.Graylist, + config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Graylist, "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.AcceptPX, + config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.AcceptPX, "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Thresholds.OpportunisticGraft, + config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.OpportunisticGraft, "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyThreshold, + config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyThreshold, "the threshold when the behavior of a peer is considered as bad by GossipSub") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyWeight, + config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyWeight, "the weight for applying penalty when a peer misbehavior goes beyond the threshold") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.Behaviour.PenaltyDecay, + config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyDecay, "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.SkipAtomicValidation, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.SkipAtomicValidation, "the default value for the skip atomic validation flag for topics") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesWeight, "this value is applied to the square of the number of invalid message deliveries on a topic") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesDecay, "the decay factor used to decay the number of invalid message deliveries") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TimeInMeshQuantum, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.TimeInMeshQuantum, "the time in mesh quantum for the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.TopicWeight, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.TopicWeight, "the weight of a topic in the GossipSub scoring system") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesDecay, "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesCap, "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveryThreshold, "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshDeliveriesWeight, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshDeliveriesWeight, "the weight for applying penalty when a peer is under-performing in a topic mesh") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesWindow, "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), - config.GossipSub.ScoringParameters.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveryActivation, "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index 20741a8bfc8..e9b82c68e4c 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -91,7 +91,7 @@ type ScoringParameters struct { SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` // DecayInterval is the interval at which the counters associated with a peer behavior in GossipSub system are decayed. DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` - ScoreOption ScoreOption `validate:"required" mapstructure:"score-option"` + InternalPeerScoring InternalPeerScoring `validate:"required" mapstructure:"internal-peer-scoring"` ScoringRegistryParameters ScoringRegistryParameters `validate:"required" mapstructure:"scoring-registry"` } diff --git a/network/p2p/config/score_option.go b/network/p2p/config/score_option.go index 2f608dae79e..6db160f0b81 100644 --- a/network/p2p/config/score_option.go +++ b/network/p2p/config/score_option.go @@ -3,7 +3,7 @@ package p2pconfig import "time" const ( - ScoreOptionKey = "score-option" + ScoreOptionKey = "internal-peer-scoring" AppScoreWeightKey = "app-specific-score-weight" MaxDebugLogsKey = "max-debug-logs" ScoreOptionDecayIntervalKey = "decay-interval" @@ -15,8 +15,8 @@ const ( ScoreOptionTopicKey = "topic" ) -// ScoreOption gossipsub scoring option configuration parameters. -type ScoreOption struct { +// InternalPeerScoring gossipsub scoring option configuration parameters. +type InternalPeerScoring struct { // AppSpecificScoreWeight is the weight for app-specific scores. It is used to scale the app-specific // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific // scores and the other scores, so we set it to 1. diff --git a/network/p2p/scoring/app_score_test.go b/network/p2p/scoring/app_score_test.go index 9b75bb272a4..8b732c16c13 100644 --- a/network/p2p/scoring/app_score_test.go +++ b/network/p2p/scoring/app_score_test.go @@ -142,7 +142,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi p2ptest.WithRole(flow.RoleAccess), // overrides the default peer scoring parameters to mute GossipSub traffic from/to honest nodes. p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ - AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.ScoreOption), + AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring), }), ) @@ -223,7 +223,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi // maliciousAppSpecificScore returns a malicious app specific penalty function that rewards the malicious node and // punishes the honest nodes. -func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.ScoreOption) func(peer.ID) float64 { +func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.InternalPeerScoring) func(peer.ID) float64 { honestIdProvider := id.NewFixedIdentityProvider(honestIds) return func(p peer.ID) float64 { _, isHonest := honestIdProvider.ByPeerID(p) diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index 162d98d22d1..a4002c60072 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -57,7 +57,7 @@ func TestScoreRegistry_FreshStart(t *testing.T) { require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption.Rewards.MaxAppSpecificReward + maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.MaxAppSpecificReward queryTime := time.Now() require.Eventually(t, func() bool { @@ -141,7 +141,7 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // eventually, the app specific score should be updated in the cache. require.Eventually(t, func() bool { @@ -243,7 +243,7 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // eventually the app specific score should be updated in the cache to the penalty value for unknown identity. require.Eventually(t, func() bool { @@ -347,7 +347,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // peer does not have spam record, but has invalid subscription. Hence, the app specific score should be subscription penalty. // eventually the app specific score should be updated in the cache to the penalty value for subscription penalty. @@ -496,7 +496,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -560,7 +560,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // initially, the app specific score should be the default unknown identity penalty. require.Eventually(t, func() bool { @@ -633,7 +633,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring // initially, the app specific score should be the default invalid subscription penalty. require.Eventually(t, func() bool { @@ -705,7 +705,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { assert.False(t, spamRecords.Has(peer1)) assert.False(t, spamRecords.Has(peer2)) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring scoringRegistryParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters // since the both peers do not have a spam record, their app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. @@ -799,7 +799,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoreOption + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring for _, peerID := range peerIds { // initially, the spamRecords should not have the peer id. @@ -991,10 +991,10 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring Parameters: params.AppSpecificScore, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), NetworkingType: network.PrivateNetwork, - UnknownIdentityPenalty: params.ScoreOption.Penalties.UnknownIdentityPenalty, - MinAppSpecificPenalty: params.ScoreOption.Penalties.MinAppSpecificPenalty, - StakedIdentityReward: params.ScoreOption.Rewards.StakedIdentityReward, - InvalidSubscriptionPenalty: params.ScoreOption.Penalties.InvalidSubscriptionPenalty, + UnknownIdentityPenalty: params.InternalPeerScoring.Penalties.UnknownIdentityPenalty, + MinAppSpecificPenalty: params.InternalPeerScoring.Penalties.MinAppSpecificPenalty, + StakedIdentityReward: params.InternalPeerScoring.Rewards.StakedIdentityReward, + InvalidSubscriptionPenalty: params.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, } for _, opt := range opts { opt(cfg) diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index aa9ec19d4c7..223e1006432 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -96,7 +96,7 @@ func (c *ScoreOptionConfig) SetRegisterNotificationConsumerFunc(f func(p2p.Gossi // NewScoreOption creates a new penalty option with the given configuration. func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { - throttledSampler := logging.BurstSampler(cfg.params.ScoreOption.MaxDebugLogs, time.Second) + throttledSampler := logging.BurstSampler(cfg.params.InternalPeerScoring.MaxDebugLogs, time.Second) logger := cfg.logger.With(). Str("module", "pubsub_score_option"). Logger(). @@ -130,10 +130,10 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( }, Parameters: cfg.params.AppSpecificScore, NetworkingType: cfg.networkingType, - UnknownIdentityPenalty: cfg.params.ScoreOption.Penalties.UnknownIdentityPenalty, - MinAppSpecificPenalty: cfg.params.ScoreOption.Penalties.MinAppSpecificPenalty, - StakedIdentityReward: cfg.params.ScoreOption.Rewards.StakedIdentityReward, - InvalidSubscriptionPenalty: cfg.params.ScoreOption.Penalties.InvalidSubscriptionPenalty, + UnknownIdentityPenalty: cfg.params.InternalPeerScoring.Penalties.UnknownIdentityPenalty, + MinAppSpecificPenalty: cfg.params.InternalPeerScoring.Penalties.MinAppSpecificPenalty, + StakedIdentityReward: cfg.params.InternalPeerScoring.Rewards.StakedIdentityReward, + InvalidSubscriptionPenalty: cfg.params.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, }) if err != nil { return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) @@ -146,44 +146,44 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( Topics: make(map[string]*pubsub.TopicScoreParams), // we don't set all the parameters, so we skip the atomic validation. // atomic validation fails initialization if any parameter is not set. - SkipAtomicValidation: cfg.params.ScoreOption.TopicValidation.SkipAtomicValidation, + SkipAtomicValidation: cfg.params.InternalPeerScoring.TopicValidation.SkipAtomicValidation, // DecayInterval is the interval over which we decay the effect of past behavior, so that // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval // that GossipSub uses to refresh the scores of all peers. - DecayInterval: cfg.params.ScoreOption.DecayInterval, + DecayInterval: cfg.params.InternalPeerScoring.DecayInterval, // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. // This is to prevent the counter from decaying to a very small value. // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior // for a long time, and we can reset the counter. - DecayToZero: cfg.params.ScoreOption.DecayToZero, + DecayToZero: cfg.params.InternalPeerScoring.DecayToZero, // AppSpecificWeight is the weight of the application specific penalty. - AppSpecificWeight: cfg.params.ScoreOption.AppSpecificScoreWeight, + AppSpecificWeight: cfg.params.InternalPeerScoring.AppSpecificScoreWeight, // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. - BehaviourPenaltyThreshold: cfg.params.ScoreOption.Behaviour.PenaltyThreshold, + BehaviourPenaltyThreshold: cfg.params.InternalPeerScoring.Behaviour.PenaltyThreshold, // PenaltyWeight is the weight of the GossipSub-level penalty. - BehaviourPenaltyWeight: cfg.params.ScoreOption.Behaviour.PenaltyWeight, + BehaviourPenaltyWeight: cfg.params.InternalPeerScoring.Behaviour.PenaltyWeight, // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). - BehaviourPenaltyDecay: cfg.params.ScoreOption.Behaviour.PenaltyDecay, + BehaviourPenaltyDecay: cfg.params.InternalPeerScoring.Behaviour.PenaltyDecay, }, peerThresholdParams: &pubsub.PeerScoreThresholds{ - GossipThreshold: cfg.params.ScoreOption.Thresholds.Gossip, - PublishThreshold: cfg.params.ScoreOption.Thresholds.Publish, - GraylistThreshold: cfg.params.ScoreOption.Thresholds.Graylist, - AcceptPXThreshold: cfg.params.ScoreOption.Thresholds.AcceptPX, - OpportunisticGraftThreshold: cfg.params.ScoreOption.Thresholds.OpportunisticGraft, + GossipThreshold: cfg.params.InternalPeerScoring.Thresholds.Gossip, + PublishThreshold: cfg.params.InternalPeerScoring.Thresholds.Publish, + GraylistThreshold: cfg.params.InternalPeerScoring.Thresholds.Graylist, + AcceptPXThreshold: cfg.params.InternalPeerScoring.Thresholds.AcceptPX, + OpportunisticGraftThreshold: cfg.params.InternalPeerScoring.Thresholds.OpportunisticGraft, }, defaultTopicScoreParams: &pubsub.TopicScoreParams{ - TopicWeight: cfg.params.ScoreOption.TopicValidation.TopicWeight, - SkipAtomicValidation: cfg.params.ScoreOption.TopicValidation.SkipAtomicValidation, - InvalidMessageDeliveriesWeight: cfg.params.ScoreOption.TopicValidation.InvalidMessageDeliveriesWeight, - InvalidMessageDeliveriesDecay: cfg.params.ScoreOption.TopicValidation.InvalidMessageDeliveriesDecay, - TimeInMeshQuantum: cfg.params.ScoreOption.TopicValidation.TimeInMeshQuantum, - MeshMessageDeliveriesWeight: cfg.params.ScoreOption.TopicValidation.MeshDeliveriesWeight, - MeshMessageDeliveriesDecay: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesDecay, - MeshMessageDeliveriesCap: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesCap, - MeshMessageDeliveriesThreshold: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveryThreshold, - MeshMessageDeliveriesWindow: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveriesWindow, - MeshMessageDeliveriesActivation: cfg.params.ScoreOption.TopicValidation.MeshMessageDeliveryActivation, + TopicWeight: cfg.params.InternalPeerScoring.TopicValidation.TopicWeight, + SkipAtomicValidation: cfg.params.InternalPeerScoring.TopicValidation.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: cfg.params.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: cfg.params.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: cfg.params.InternalPeerScoring.TopicValidation.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: cfg.params.InternalPeerScoring.TopicValidation.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveryActivation, }, appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), } From 14b3dd7005702f9a90ed9228191ebc73fa8f8632 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Thu, 11 Jan 2024 21:47:12 -0500 Subject: [PATCH 09/19] trim godocs remove notions of "set . . ." --- network/p2p/config/score_option.go | 94 +++--------------------------- 1 file changed, 8 insertions(+), 86 deletions(-) diff --git a/network/p2p/config/score_option.go b/network/p2p/config/score_option.go index 6db160f0b81..7d344fd2c1b 100644 --- a/network/p2p/config/score_option.go +++ b/network/p2p/config/score_option.go @@ -18,22 +18,17 @@ const ( // InternalPeerScoring gossipsub scoring option configuration parameters. type InternalPeerScoring struct { // AppSpecificScoreWeight is the weight for app-specific scores. It is used to scale the app-specific - // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific - // scores and the other scores, so we set it to 1. + // scores to the same range as the other scores. AppSpecificScoreWeight float64 `validate:"gt=0,lte=1" mapstructure:"app-specific-score-weight"` // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above // this threshold are dropped. MaxDebugLogs uint32 `validate:"lte=50" mapstructure:"max-debug-logs"` // DecayInterval is the decay interval for the overall score of a peer at the GossipSub scoring - // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty - // and is not too long so that a well-behaved node can't recover from a penalty. + // system. DecayInterval time.Duration `validate:"gte=1m" mapstructure:"decay-interval"` // DecayToZero is the decay to zero for the overall score of a peer at the GossipSub scoring system. // It defines the maximum value below which a peer scoring counter is reset to zero. // This is to prevent the counter from decaying to a very small value. - // The value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. - // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - // for a long time, and we can reset the counter. DecayToZero float64 `validate:"required" mapstructure:"decay-to-zero"` Penalties ScoreOptionPenalties `validate:"required" mapstructure:"penalties"` Rewards ScoreOptionRewards `validate:"required" mapstructure:"rewards"` @@ -53,9 +48,7 @@ const ( type ScoreOptionPenalties struct { // MaxAppSpecificPenalty the maximum penalty for sever offenses that we apply to a remote node score. The score // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of - // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to - // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty - // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. + // this value. MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific-penalty"` @@ -129,8 +122,6 @@ type ScoreOptionBehaviour struct { // It samples ONLY A SINGLE iHave out of the entire RPC. // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. // - // We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every - // excess RPC containing iHave broken promises. // The counter is also decayed by (0.99) every decay interval (DecayInterval) i.e., every minute. // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through // the ALSP system). @@ -144,25 +135,6 @@ type ScoreOptionBehaviour struct { // For iHave broken promises, the gossipsub scoring works as follows: // It samples ONLY A SINGLE iHave out of the entire RPC. // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - // - // The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. - // We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose - // its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by . - // Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the Graylist, and the peer will be graylisted (i.e., disconnected). - // - // The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty - // if misbehavior > threshold + sqrt(2) * 10. - // As shown above, with this choice of BehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - // to be dropped below the MaxAppSpecificPenalty, which is also below the Graylist, and the peer will be graylisted (i.e., disconnected). This weight - // is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that - // the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. - // Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - // 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > Graylist - // where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality - // to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. - // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - // the ALSP system that are reported by the engines). PenaltyWeight float64 `validate:"lt=0" mapstructure:"penalty-weight"` // PenaltyDecay is the decay interval for the misbehavior counter of a peer. The misbehavior counter is // incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). @@ -173,11 +145,7 @@ type ScoreOptionBehaviour struct { // If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. // This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. // That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. - // // The misbehavior counter is decayed per decay interval (i.e., DecayInterval = 1 minute) by GossipSub. - // We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - // With the generous threshold that we set (i.e., PenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, - // We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. // // Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through // the ALSP system that is based on the engines report). @@ -201,54 +169,31 @@ const ( // ScoreOptionTopicValidation score option topic validation configuration parameters. type ScoreOptionTopicValidation struct { // SkipAtomicValidation is the value for the skip atomic validation flag for topics. - // We set it to true, which means gossipsub parameter validation will not fail if we leave some of the - // topic parameters at their values, i.e., zero. This is because we are not setting all - // topic parameters at the current implementation. + // If set it to true, the gossipsub parameter validation will not fail if we leave some of the + // topic parameters at their values, i.e., zero. SkipAtomicValidation bool `validate:"required" mapstructure:"skip-atomic-validation"` // InvalidMessageDeliveriesWeight this value is applied to the square of the number of invalid message deliveries on a topic. // It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the - // publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid - // message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. - // The supporting math is as follows: - // - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., StakedIdentityReward). - // - x invalid message deliveries will result in a penalty of x^2 * InvalidMessageDeliveriesWeight, i.e., -x^2. - // - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). - // - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/InvalidMessageDeliveriesWeight) ~ 14. + // publisher, or a message that is not signed by the peer that sent it. InvalidMessageDeliveriesWeight float64 `validate:"lt=0" mapstructure:"invalid-message-deliveries-weight"` // InvalidMessageDeliveriesDecay decay factor used to decay the number of invalid message deliveries. // The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to // decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops - // sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will - // decay by 1% at each heartbeat interval. - // The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (DecayInterval). + // sending invalid messages. InvalidMessageDeliveriesDecay float64 `validate:"gt=0,lt=1" mapstructure:"invalid-message-deliveries-decay"` // TimeInMeshQuantum is the time in mesh quantum for the GossipSub scoring system. It is used to gauge - // a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is - // in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. - // The reason of setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that - // are churners, i.e., peers that join and leave a topic mesh frequently. + // a discrete time interval for the time in mesh counter. TimeInMeshQuantum time.Duration `validate:"gte=1h" mapstructure:"time-in-mesh-quantum"` // Weight is the weight of a topic in the GossipSub scoring system. // The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. - // We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. TopicWeight float64 `validate:"gt=0" mapstructure:"topic-weight"` // MeshMessageDeliveriesDecay is applied to the number of actual message deliveries in a topic mesh // at each decay interval (i.e., DecayInterval). // It is used to decay the number of actual message deliveries, and prevents past message // deliveries from affecting the current score of the peer. - // As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message - // deliveries will decay by 50% at each decay interval. MeshMessageDeliveriesDecay float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-decay"` // MeshMessageDeliveriesCap is the maximum number of actual message deliveries in a topic // mesh that is used to calculate the score of a peer in that topic mesh. - // We set it to 1000, which means that the maximum number of actual message deliveries in a - // topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. - // This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual - // message deliveries and also affect the score of the peer in other topic meshes. - // When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic - // mesh will not be affected by the actual message deliveries in that topic mesh. - // Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh - // and then start under-performing in that topic mesh without being penalized. MeshMessageDeliveriesCap float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-cap"` // MeshMessageDeliveryThreshold is the threshold for the number of actual message deliveries in a // topic mesh that is used to calculate the score of a peer in that topic mesh. @@ -256,41 +201,18 @@ type ScoreOptionTopicValidation struct { // the peer will be penalized by square of the difference between the actual message deliveries and the threshold, // i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the // threshold, respectively, and `w` is the weight (i.e., MeshMessageDeliveriesWeight). - // We set it to 0.1 * MeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the - // maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer - // in that topic mesh. MeshMessageDeliveryThreshold float64 `validate:"gt=0" mapstructure:"mesh-message-deliveries-threshold"` // MeshDeliveriesWeight is the weight for applying penalty when a peer is under-performing in a topic mesh. // Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold // (i.e., MeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual // message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and // `actual` and `threshold` are the actual message deliveries and the threshold, respectively. - // We set this value to be - 0.05 MaxAppSpecificReward / (MeshMessageDeliveriesThreshold^2). This guarantees that even if a peer - // is not delivering any message in a topic mesh, it will not be disconnected. - // Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked - // nodes by will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh - // will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. - // It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose - // its MaxAppSpecificReward that is awarded by our app-specific scoring function. - // At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be - // causing a false positive network partition. - // TODO: we must increase the penalty for under-performing in a topic mesh in the future, and disconnect the peer if it is under-performing. MeshDeliveriesWeight float64 `validate:"lt=0" mapstructure:"mesh-deliveries-weight"` // MeshMessageDeliveriesWindow is the window size is time interval that we count a delivery of an already // seen message towards the score of a peer in a topic mesh. The delivery is counted // by GossipSub only if the previous sender of the message is different from the current sender. - // We set it to the decay interval of the GossipSub scoring system, which is 1 minute. - // It means that if a peer delivers a message that it has already seen less than one minute ago, - // the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. - // This also prevents replay attacks of messages that are older than one minute. As replayed messages will not - // be counted towards the actual message deliveries of a peer in a topic mesh. MeshMessageDeliveriesWindow time.Duration `validate:"gte=1m" mapstructure:"mesh-message-deliveries-window"` // MeshMessageDeliveryActivation is the time interval that we wait for a new peer that joins a topic mesh // till start counting the number of actual message deliveries of that peer in that topic mesh. - // We set it to 2 * DecayInterval, which means that we wait for 2 decay intervals before start counting - // the number of actual message deliveries of a peer in a topic mesh. - // With a decay interval of 1 minute, it means that we wait for 2 minutes before start counting the - // number of actual message deliveries of a peer in a topic mesh. This is to account for - // the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. MeshMessageDeliveryActivation time.Duration `validate:"gte=2m" mapstructure:"mesh-message-delivery-activation"` } From dd0b5ef13832690bb824ebe447e6d74d29e0090f Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Thu, 11 Jan 2024 23:36:59 -0500 Subject: [PATCH 10/19] shorten flag names where applicable --- config/default-config.yml | 12 +++++----- network/p2p/config/score_option.go | 36 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/config/default-config.yml b/config/default-config.yml index a94970a25cc..4d049c46297 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -260,25 +260,25 @@ network-config: # i.e., -100 + 1. This ensures that a node with a score of # -100 will be graylisted (i.e., all incoming and outgoing RPCs # are rejected) and will not be able to publish or gossip any messages. - max-app-specific-penalty: -100 - min-app-specific-penalty: -1 + max-app-specific: -100 + min-app-specific: -1 # This is the penalty for unknown identity. It is # applied to the peer's score when the peer is not in the identity list. - unknown-identity-penalty: -100 + unknown-identity: -100 # This is the penalty for invalid subscription. # It is applied to the peer's score when the peer subscribes to a topic that it is # not authorized to subscribe to. - invalid-subscription-penalty: -100 + invalid-subscription: -100 rewards: # This is the reward for well-behaving staked peers. # If a peer does not have any misbehavior record, e.g., invalid subscription, # invalid message, etc., it will be rewarded with this score. - max-app-specific-reward: 100 + max-app-specific: 100 # This is the reward for staking peers. It is applied # to the peer's score when the peer does not have any misbehavior record, e.g., # invalid subscription, invalid message, etc. The purpose is to reward the staking # peers for their contribution to the network and prioritize them in neighbor selection. - staked-identity-reward: 100 + staked-identity: 100 thresholds: # This is the threshold when a peer's penalty drops below this threshold, no gossip # is emitted towards that peer and gossip from that peer is ignored. diff --git a/network/p2p/config/score_option.go b/network/p2p/config/score_option.go index 7d344fd2c1b..b2c7d93369a 100644 --- a/network/p2p/config/score_option.go +++ b/network/p2p/config/score_option.go @@ -32,16 +32,16 @@ type InternalPeerScoring struct { DecayToZero float64 `validate:"required" mapstructure:"decay-to-zero"` Penalties ScoreOptionPenalties `validate:"required" mapstructure:"penalties"` Rewards ScoreOptionRewards `validate:"required" mapstructure:"rewards"` - Thresholds ScoreOptionThresholds `validate:"required" mapstructure:"thresholds"` - Behaviour ScoreOptionBehaviour `validate:"required" mapstructure:"behaviour"` + Thresholds InternalScoringThresholds `validate:"required" mapstructure:"thresholds"` + Behaviour InternalScoringBehavioural `validate:"required" mapstructure:"behaviour"` TopicValidation ScoreOptionTopicValidation `validate:"required" mapstructure:"topic"` } const ( - MaxAppSpecificPenaltyKey = "max-app-specific-penalty" - MinAppSpecificPenaltyKey = "min-app-specific-penalty" - UnknownIdentityPenaltyKey = "unknown-identity-penalty" - InvalidSubscriptionPenaltyKey = "invalid-subscription-penalty" + MaxAppSpecificPenaltyKey = "max-app-specific" + MinAppSpecificPenaltyKey = "min-app-specific" + UnknownIdentityPenaltyKey = "unknown-identity" + InvalidSubscriptionPenaltyKey = "invalid-subscription" ) // ScoreOptionPenalties score option penalty configuration parameters. @@ -49,31 +49,31 @@ type ScoreOptionPenalties struct { // MaxAppSpecificPenalty the maximum penalty for sever offenses that we apply to a remote node score. The score // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of // this value. - MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` + MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific"` // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. - MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific-penalty"` + MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific"` // UnknownIdentityPenalty is the penalty for unknown identity. It is applied to the peer's score when // the peer is not in the identity list. - UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity-penalty"` + UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity"` // InvalidSubscriptionPenalty is the penalty for invalid subscription. It is applied to the peer's score when // the peer subscribes to a topic that it is not authorized to subscribe to. - InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription-penalty"` + InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription"` } const ( - MaxAppSpecificRewardKey = "max-app-specific-reward" - StakedIdentityRewardKey = "staked-identity-reward" + MaxAppSpecificRewardKey = "max-app-specific" + StakedIdentityRewardKey = "staked-identity" ) // ScoreOptionRewards score option rewards configuration parameters. type ScoreOptionRewards struct { // MaxAppSpecificReward is the reward for well-behaving staked peers. If a peer does not have // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. - MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific-reward"` + MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific"` // StakedIdentityReward is the reward for staking peers. It is applied to the peer's score when // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. - StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity-reward"` + StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity"` } const ( @@ -84,8 +84,8 @@ const ( OpportunisticGraftThresholdKey = "opportunistic-graft" ) -// ScoreOptionThresholds score option threshold configuration parameters. -type ScoreOptionThresholds struct { +// InternalScoringThresholds score option threshold configuration parameters. +type InternalScoringThresholds struct { // Gossip when a peer's penalty drops below this threshold, // no gossip is emitted towards that peer and gossip from that peer is ignored. Gossip float64 `validate:"lt=0" mapstructure:"gossip"` @@ -109,8 +109,8 @@ const ( BehaviourPenaltyDecayKey = "penalty-decay" ) -// ScoreOptionBehaviour score option behaviour configuration parameters. -type ScoreOptionBehaviour struct { +// InternalScoringBehavioural score option behaviour configuration parameters. +type InternalScoringBehavioural struct { // PenaltyThreshold is the threshold when the behavior of a peer is considered as bad by GossipSub. // Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting // on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh From 73cf1b118e3f8f98b268dae9adb71271b865c5cf Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Thu, 11 Jan 2024 23:50:40 -0500 Subject: [PATCH 11/19] remove redundant GossipSubCtrlMsgPenaltyValue --- network/p2p/scoring/registry.go | 40 +++----------- network/p2p/scoring/registry_test.go | 83 ++++++++++++++-------------- network/p2p/scoring/score_option.go | 3 +- 3 files changed, 52 insertions(+), 74 deletions(-) diff --git a/network/p2p/scoring/registry.go b/network/p2p/scoring/registry.go index f1167ef55e2..38a9584b4cc 100644 --- a/network/p2p/scoring/registry.go +++ b/network/p2p/scoring/registry.go @@ -27,30 +27,6 @@ import ( type SpamRecordInitFunc func() p2p.GossipSubSpamRecord -// GossipSubCtrlMsgPenaltyValue is the penalty value for each control message type. -type GossipSubCtrlMsgPenaltyValue struct { - Graft float64 // penalty value for an individual graft message misbehaviour. - Prune float64 // penalty value for an individual prune message misbehaviour. - IHave float64 // penalty value for an individual iHave message misbehaviour. - IWant float64 // penalty value for an individual iWant message misbehaviour. - // ClusterPrefixedPenaltyReductionFactor factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics. This is allows a more lenient punishment for nodes - // that fall behind and may need to request old data. - ClusterPrefixedPenaltyReductionFactor float64 - RpcPublishMessage float64 // penalty value for an individual RpcPublishMessage message misbehaviour. -} - -// GossipSubCtrlMsgPenaltyValues returns the default penalty value for each control message type. -func GossipSubCtrlMsgPenaltyValues(penalties p2pconfig.MisbehaviourPenalties) GossipSubCtrlMsgPenaltyValue { - return GossipSubCtrlMsgPenaltyValue{ - Graft: penalties.GraftMisbehaviour, - Prune: penalties.PruneMisbehaviour, - IHave: penalties.IHaveMisbehaviour, - IWant: penalties.IWantMisbehaviour, - ClusterPrefixedPenaltyReductionFactor: penalties.ClusterPrefixedReductionFactor, - RpcPublishMessage: penalties.PublishMisbehaviour, - } -} - // GossipSubAppSpecificScoreRegistry is the registry for the application specific score of peers in the GossipSub protocol. // The application specific score is part of the overall score of a peer, and is used to determine the peer's score based // on its behavior related to the application (Flow protocol). @@ -66,7 +42,7 @@ type GossipSubAppSpecificScoreRegistry struct { // spamScoreCache currently only holds the control message misbehaviour penalty (spam related penalty). spamScoreCache p2p.GossipSubSpamRecordCache - penalty GossipSubCtrlMsgPenaltyValue + penalty p2pconfig.MisbehaviourPenalties // initial application specific penalty record, used to initialize the penalty cache entry. init func() p2p.GossipSubSpamRecord @@ -103,7 +79,7 @@ type GossipSubAppSpecificScoreRegistryConfig struct { Validator p2p.SubscriptionValidator `validate:"required"` // Penalty encapsulates the penalty unit for each control message type misbehaviour. - Penalty GossipSubCtrlMsgPenaltyValue `validate:"required"` + Penalty p2pconfig.MisbehaviourPenalties `validate:"required"` // IdProvider is the identity provider used to translate peer ids at the networking layer to Flow identifiers (if // an authorized peer is found). @@ -398,15 +374,15 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( penalty := 0.0 switch notification.MsgType { case p2pmsg.CtrlMsgGraft: - penalty += r.penalty.Graft + penalty += r.penalty.GraftMisbehaviour case p2pmsg.CtrlMsgPrune: - penalty += r.penalty.Prune + penalty += r.penalty.PruneMisbehaviour case p2pmsg.CtrlMsgIHave: - penalty += r.penalty.IHave + penalty += r.penalty.IHaveMisbehaviour case p2pmsg.CtrlMsgIWant: - penalty += r.penalty.IWant + penalty += r.penalty.IWantMisbehaviour case p2pmsg.RpcPublishMessage: - penalty += r.penalty.RpcPublishMessage + penalty += r.penalty.PublishMisbehaviour default: // the error is considered fatal as it means that we have an unsupported misbehaviour type, we should crash the node to prevent routing attack vulnerability. lg.Fatal().Str("misbehavior_type", notification.MsgType.String()).Msg("unknown misbehaviour type") @@ -414,7 +390,7 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( // reduce penalty for cluster prefixed topics allowing nodes that are potentially behind to catch up if notification.TopicType == p2p.CtrlMsgTopicTypeClusterPrefixed { - penalty *= r.penalty.ClusterPrefixedPenaltyReductionFactor + penalty *= r.penalty.ClusterPrefixedReductionFactor } record.Penalty += penalty diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index a4002c60072..20151fedc4d 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -90,19 +90,19 @@ func TestScoreRegistry_FreshStart(t *testing.T) { // asynchronous nature of app-specific score updates in GossipSub's cache. func TestScoreRegistry_PeerWithSpamRecord(t *testing.T) { t.Run("graft", func(t *testing.T) { - testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testScoreRegistryPeerWithSpamRecord(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistryPeerWithSpamRecord(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } @@ -193,19 +193,19 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro // penalty computation and updates to the score registry when a peer with an unknown identity sends these control messages. func TestScoreRegistry_SpamRecordWithUnknownIdentity(t *testing.T) { t.Run("graft", func(t *testing.T) { - testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistrySpamRecordWithUnknownIdentity(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } @@ -298,19 +298,19 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 // in spam activities, as indicated by these control messages. func TestScoreRegistry_SpamRecordWithSubscriptionPenalty(t *testing.T) { t.Run("graft", func(t *testing.T) { - testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().Graft) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgGraft, penaltyValueFixtures().GraftMisbehaviour) }) t.Run("prune", func(t *testing.T) { - testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().Prune) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgPrune, penaltyValueFixtures().PruneMisbehaviour) }) t.Run("ihave", func(t *testing.T) { - testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHave) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIHave, penaltyValueFixtures().IHaveMisbehaviour) }) t.Run("iwant", func(t *testing.T) { - testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWant) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.CtrlMsgIWant, penaltyValueFixtures().IWantMisbehaviour) }) t.Run("RpcPublishMessage", func(t *testing.T) { - testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().RpcPublishMessage) + testScoreRegistrySpamRecordWithSubscriptionPenalty(t, p2pmsg.RpcPublishMessage, penaltyValueFixtures().PublishMisbehaviour) }) } @@ -447,11 +447,11 @@ func TestScoreRegistry_SpamPenaltyDecaysInCache(t *testing.T) { time.Sleep(1 * time.Second) // wait for the penalty to decay. // the upper bound is the sum of the penalties without decay. - scoreUpperBound := penaltyValueFixtures().Prune + - penaltyValueFixtures().Graft + - penaltyValueFixtures().IHave + - penaltyValueFixtures().IWant + - penaltyValueFixtures().RpcPublishMessage + scoreUpperBound := penaltyValueFixtures().PruneMisbehaviour + + penaltyValueFixtures().GraftMisbehaviour + + penaltyValueFixtures().IHaveMisbehaviour + + penaltyValueFixtures().IWantMisbehaviour + + penaltyValueFixtures().PublishMisbehaviour // the lower bound is the sum of the penalties with decay assuming the decay is applied 4 times to the sum of the penalties. // in reality, the decay is applied 4 times to the first penalty, then 3 times to the second penalty, and so on. r := scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)() @@ -510,7 +510,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { require.Eventually(t, func() bool { score := reg.AppSpecificScoreFunc()(peerID) // the penalty should be less than zero and greater than the penalty value (due to decay). - return score < 0 && score > penaltyValueFixtures().Graft + return score < 0 && score > penaltyValueFixtures().GraftMisbehaviour }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -582,8 +582,8 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { score := reg.AppSpecificScoreFunc()(peerID) // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. - // (scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().Graft, scoring.DefaultUnknownIdentityPenalty). - return score < scoreOptParameters.Penalties.UnknownIdentityPenalty && score > scoreOptParameters.Penalties.UnknownIdentityPenalty+penaltyValueFixtures().Graft + // (scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultUnknownIdentityPenalty). + return score < scoreOptParameters.Penalties.UnknownIdentityPenalty && score > scoreOptParameters.Penalties.UnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -652,8 +652,8 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { score := reg.AppSpecificScoreFunc()(peerID) // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. - // (DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().Graft, scoring.DefaultInvalidSubscriptionPenalty). - return score < scoreOptParameters.Penalties.InvalidSubscriptionPenalty && score > scoreOptParameters.Penalties.InvalidSubscriptionPenalty+penaltyValueFixtures().Graft + // (DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultInvalidSubscriptionPenalty). + return score < scoreOptParameters.Penalties.InvalidSubscriptionPenalty && score > scoreOptParameters.Penalties.InvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -841,8 +841,8 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { }() unittest.RequireReturnsBefore(t, wg.Wait, 100*time.Millisecond, "timed out waiting for goroutines to finish") - // expected penalty should be penaltyValueFixtures().Graft * (1 + clusterReductionFactor) - expectedPenalty := penaltyValueFixture(ctlMsgType) * (1 + penaltyValueFixtures().ClusterPrefixedPenaltyReductionFactor) + // expected penalty should be penaltyValueFixtures().GraftMisbehaviour * (1 + clusterReductionFactor) + expectedPenalty := penaltyValueFixture(ctlMsgType) * (1 + penaltyValueFixtures().ClusterPrefixedReductionFactor) // the penalty should now be updated in the spamRecords record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. @@ -1009,14 +1009,15 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring // penaltyValueFixtures returns a set of penalty values for testing purposes. // The values are not realistic. The important thing is that they are different from each other. This is to make sure // that the tests are not passing because of the default values. -func penaltyValueFixtures() scoring.GossipSubCtrlMsgPenaltyValue { - return scoring.GossipSubCtrlMsgPenaltyValue{ - Graft: -100, - Prune: -50, - IHave: -20, - IWant: -10, - ClusterPrefixedPenaltyReductionFactor: .5, - RpcPublishMessage: -10, +func penaltyValueFixtures() p2pconfig.MisbehaviourPenalties { + return p2pconfig.MisbehaviourPenalties{ + GraftMisbehaviour: -100, + PruneMisbehaviour: -50, + IHaveMisbehaviour: -20, + IWantMisbehaviour: -10, + ClusterPrefixedReductionFactor: .5, + PublishMisbehaviour: -10, + SkipDecayThreshold: -0.1, } } @@ -1025,16 +1026,16 @@ func penaltyValueFixture(msgType p2pmsg.ControlMessageType) float64 { penaltyValues := penaltyValueFixtures() switch msgType { case p2pmsg.CtrlMsgGraft: - return penaltyValues.Graft + return penaltyValues.GraftMisbehaviour case p2pmsg.CtrlMsgPrune: - return penaltyValues.Prune + return penaltyValues.PruneMisbehaviour case p2pmsg.CtrlMsgIHave: - return penaltyValues.IHave + return penaltyValues.IHaveMisbehaviour case p2pmsg.CtrlMsgIWant: - return penaltyValues.IWant + return penaltyValues.IWantMisbehaviour case p2pmsg.RpcPublishMessage: - return penaltyValues.RpcPublishMessage + return penaltyValues.PublishMisbehaviour default: - return penaltyValues.ClusterPrefixedPenaltyReductionFactor + return penaltyValues.ClusterPrefixedReductionFactor } } diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index 223e1006432..9a20167d460 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -104,10 +104,11 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( TraceSampler: throttledSampler, DebugSampler: throttledSampler, }) + validator := NewSubscriptionValidator(cfg.logger, provider) scoreRegistry, err := NewGossipSubAppSpecificScoreRegistry(&GossipSubAppSpecificScoreRegistryConfig{ Logger: logger, - Penalty: GossipSubCtrlMsgPenaltyValues(cfg.params.ScoringRegistryParameters.MisbehaviourPenalties), + Penalty: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties, Validator: validator, Init: InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor), IdProvider: cfg.provider, From 1393487ef9f1d7b77bb886d12781bfc49e670efe Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Thu, 11 Jan 2024 23:51:28 -0500 Subject: [PATCH 12/19] rename network/p2p/config/score_option.go -> network/p2p/config/peer_scoring.go --- network/p2p/config/{score_option.go => peer_scoring.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename network/p2p/config/{score_option.go => peer_scoring.go} (100%) diff --git a/network/p2p/config/score_option.go b/network/p2p/config/peer_scoring.go similarity index 100% rename from network/p2p/config/score_option.go rename to network/p2p/config/peer_scoring.go From aa1626d1adfa884b227b6d0608bb44a6d7f29098 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 14:06:14 -0500 Subject: [PATCH 13/19] refactor configs with into 2 top level fields internal and protocol --- config/default-config.yml | 604 +++++++++--------- .../test/gossipsub/scoring/ihave_spam_test.go | 42 +- network/netconf/flags.go | 331 +++++----- network/p2p/config/gossipsub.go | 52 +- network/p2p/config/peer_scoring.go | 103 +-- network/p2p/config/score_registry.go | 73 ++- network/p2p/scoring/app_score_test.go | 8 +- network/p2p/scoring/decay_test.go | 16 +- network/p2p/scoring/registry_test.go | 129 ++-- network/p2p/scoring/score_option.go | 76 +-- network/p2p/scoring/scoring_test.go | 2 +- .../scoring/subscription_validator_test.go | 2 +- 12 files changed, 737 insertions(+), 701 deletions(-) diff --git a/config/default-config.yml b/config/default-config.yml index 4d049c46297..32658739807 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -199,311 +199,313 @@ network-config: # Peer scoring is the default value for enabling peer scoring peer-scoring-enabled: true scoring-parameters: - app-specific-score: - # number of workers that asynchronously update the app specific score requests when they are expired. - score-update-worker-num: 5 - # size of the queue used by the worker pool for the app specific score update requests. The queue is used to buffer the app specific score update requests - # before they are processed by the worker pool. The queue size must be larger than total number of peers in the network. - # The queue is deduplicated based on the peer ids ensuring that there is only one app specific score update request per peer in the queue. - score-update-request-queue-size: 10_000 - # score ttl is the time to live for the app specific score. Once the score is expired; a new request will be sent to the app specific score provider to update the score. - # until the score is updated, the previous score will be used. - score-ttl: 1m - spam-record-cache: - # size of cache used to track spam records at gossipsub. Each peer id is mapped to a spam record that keeps track of the spam score for that peer. - # cache should be big enough to keep track of the entire network's size. Otherwise, the local node's view of the network will be incomplete due to cache eviction. - cache-size: 10_000 - # Threshold level for spam record penalty. - # At each evaluation period, when a node's penalty is below this value, the decay rate slows down, ensuring longer decay periods for malicious nodes and quicker decay for honest ones. - penalty-decay-slowdown-threshold: -99 - # This setting adjusts the decay rate when a node's penalty falls below the threshold. - # The decay rate, ranging between 0 and 1, dictates how quickly penalties decrease: a higher rate results in slower decay. - # The decay calculation is multiplicative (newPenalty = decayRate * oldPenalty). - # The reduction factor increases the decay rate, thus decelerating the penalty reduction. For instance, with a 0.01 reduction factor, - # the decay rate increases by 0.01 at each evaluation interval when the penalty is below the threshold. - # Consequently, a decay rate of `x` diminishes the penalty to zero more rapidly than a rate of `x+0.01`. - penalty-decay-rate-reduction-factor: 0.01 - # Defines the frequency for evaluating and potentially adjusting the decay process of a spam record. - # At each interval, the system assesses the current penalty of a node. - # If this penalty is below the defined threshold, the decay rate is modified according to the reduction factor, slowing down the penalty reduction process. - # This reassessment at regular intervals ensures that the decay rate is dynamically adjusted to reflect the node's ongoing behavior, - # maintaining a balance between penalizing malicious activity and allowing recovery for honest nodes. - penalty-decay-evaluation-period: 10m # the intervals at which counters associated with a peer behavior in gossipsub system are decayed. decay-interval: 1m - internal-peer-scoring: - # The weight for app-specific scores. - # It is used to scale the app-specific scores to the same range as the other scores. - # At the current version, we don't distinguish between the app-specific scores - # and the other scores, so we set it to 1. - app-specific-score-weight: 1 - # The max number of debug/trace log events per second. - # Logs emitted above this threshold are dropped. - max-debug-logs: 50 - # The default decay interval for the overall score of a peer at the GossipSub scoring - # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty - # and is not too long so that a well-behaved node can't recover from a penalty. - decay-interval: 1m - # The default decay to zero for the overall score of a peer at the GossipSub scoring system. - # It defines the maximum value below which a peer scoring counter is reset to zero. - # This is to prevent the counter from decaying to a very small value. - # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. - # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior - # for a long time, and we can reset the counter. - decay-to-zero: 0.01 - penalties: - # This is the maximum penalty for severe offenses that we apply - # to a remote node score. The score mechanism of GossipSub in Flow is designed - # in a way that all other infractions are penalized with a fraction of this value. - # We have also set the other parameters such as GraylistThreshold, - # GossipThreshold, and PublishThreshold to be a bit higher than this, - # i.e., -100 + 1. This ensures that a node with a score of - # -100 will be graylisted (i.e., all incoming and outgoing RPCs - # are rejected) and will not be able to publish or gossip any messages. - max-app-specific: -100 - min-app-specific: -1 - # This is the penalty for unknown identity. It is - # applied to the peer's score when the peer is not in the identity list. - unknown-identity: -100 - # This is the penalty for invalid subscription. - # It is applied to the peer's score when the peer subscribes to a topic that it is - # not authorized to subscribe to. - invalid-subscription: -100 - rewards: - # This is the reward for well-behaving staked peers. - # If a peer does not have any misbehavior record, e.g., invalid subscription, - # invalid message, etc., it will be rewarded with this score. - max-app-specific: 100 - # This is the reward for staking peers. It is applied - # to the peer's score when the peer does not have any misbehavior record, e.g., - # invalid subscription, invalid message, etc. The purpose is to reward the staking - # peers for their contribution to the network and prioritize them in neighbor selection. - staked-identity: 100 - thresholds: - # This is the threshold when a peer's penalty drops below this threshold, no gossip - # is emitted towards that peer and gossip from that peer is ignored. - # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all gossips to and from peers with penalty -100 are ignored. - gossip: -99 - # This is the threshold when a peer's penalty drops below this threshold, - # self-published messages are not propagated towards this peer. - # Validation Constraint: - # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all penalized peers are deprived of receiving any published messages. - publish: -99 - # This is the threshold when a peer's penalty drops below this threshold, - # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. - # Validation Constraint: - # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 - # How we use it: As the current max penalty is -100, we set the threshold to -99 - # so that all penalized peers are graylisted. - graylist: -99 - # This is the threshold when a peer sends us PX information with a prune, - # we only accept it and connect to the supplied peers if the originating peer's - # penalty exceeds this threshold. - # Validation Constraint: must be non-negative. - # How we use it: As the current max reward is 100, we set the threshold to 99 - # so that we only receive supplied peers from well-behaved peers. - accept-px: 99 - # This is the threshold when the median peer penalty in the mesh drops - # below this value, the peer may select more peers with penalty above the median - # to opportunistically graft on the mesh. - # Validation Constraint: must be non-negative. - # How we use it: We set it to the -100 + 1 so that we only - # opportunistically graft peers that are not access nodes (i.e., with -1), - # or penalized peers (i.e., with -100). - opportunistic-graft: 101 - behaviour: - # The threshold when the behavior of a peer is considered as bad by GossipSub. - # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting - # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). - # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # - # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every - # excess RPC containing iHave broken promises. - # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system). - penalty-threshold: 1000 - # The weight for applying penalty when a peer misbehavior goes beyond the threshold. - # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting - # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh - # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # - # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. - # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose - # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. - # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). - # - # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty - # if misbehavior > threshold + sqrt(2) * 10. - # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score - # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight - # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that - # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. - # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of - # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold - # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality - # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system that are reported by the engines). - penalty-weight: -0.01 - # The decay interval for the misbehavior counter of a peer. The misbehavior counter is - # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). - # - # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. - # For iHave broken promises, the gossipsub scoring works as follows: - # It samples ONLY A SINGLE iHave out of the entire RPC. - # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. - # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. - # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. - # - # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. - # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. - # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, - # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. - # - # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through - # the ALSP system that is based on the engines report). - penalty-decay: 0.5 - topic: - # This is the default value for the skip atomic validation flag for topics. - # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the - # topic parameters at their default values, i.e., zero. This is because we are not setting all - # topic parameters at the current implementation. - skip-atomic-validation: true - # This value is applied to the square of the number of invalid message deliveries on a topic. - # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the - # publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid - # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. - # The supporting math is as follows: - # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). - # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. - # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). - # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. - invalid-message-deliveries-weight: -1.0 - # The decay factor used to decay the number of invalid message deliveries. - # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to - # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops - # sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will - # decay by 1% at each heartbeat interval. - # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). - invalid-message-deliveries-decay: 0.99 - # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge - # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is - # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. - # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that - # are churners, i.e., peers that join and leave a topic mesh frequently. - time-in-mesh-quantum: 1h - # The default weight of a topic in the GossipSub scoring system. - # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. - # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. - topic-weight: 1.0 - # This is applied to the number of actual message deliveries in a topic mesh - # at each decay interval (i.e., defaultDecayInterval). - # It is used to decay the number of actual message deliveries, and prevents past message - # deliveries from affecting the current score of the peer. - # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message - # deliveries will decay by 50% at each decay interval. - mesh-message-deliveries-decay: 0.5 - # The maximum number of actual message deliveries in a topic - # mesh that is used to calculate the score of a peer in that topic mesh. - # We set it to 1000, which means that the maximum number of actual message deliveries in a - # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. - # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual - # message deliveries and also affect the score of the peer in other topic meshes. - # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic - # mesh will not be affected by the actual message deliveries in that topic mesh. - # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh - # and then start under-performing in that topic mesh without being penalized. - mesh-message-deliveries-cap: 1000 - # The threshold for the number of actual message deliveries in a - # topic mesh that is used to calculate the score of a peer in that topic mesh. - # If the number of actual message deliveries in a topic mesh is less than this value, - # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, - # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the - # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). - # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the - # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer - # in that topic mesh. - mesh-message-deliveries-threshold: 100 - # The weight for applying penalty when a peer is under-performing in a topic mesh. - # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold - # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual - # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and - # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. - # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer - # is not delivering any message in a topic mesh, it will not be disconnected. - # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked - # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh - # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. - # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose - # its MaxAppSpecificReward that is awarded by our app-specific scoring function. - # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be - # causing a false positive network partition. - mesh-deliveries-weight: -0.0005 - # The window size is time interval that we count a delivery of an already - # seen message towards the score of a peer in a topic mesh. The delivery is counted - # by GossipSub only if the previous sender of the message is different from the current sender. - # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. - # It means that if a peer delivers a message that it has already seen less than one minute ago, - # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. - # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not - # be counted towards the actual message deliveries of a peer in a topic mesh. - mesh-message-deliveries-window: 1m - # The time interval that we wait for a new peer that joins a topic mesh - # till start counting the number of actual message deliveries of that peer in that topic mesh. - # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting - # the number of actual message deliveries of a peer in a topic mesh. - # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the - # number of actual message deliveries of a peer in a topic mesh. This is to account for - # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. - mesh-message-delivery-activation: 2m + peer-scoring: + internal: + # The weight for app-specific scores. + # It is used to scale the app-specific scores to the same range as the other scores. + # At the current version, we don't distinguish between the app-specific scores + # and the other scores, so we set it to 1. + app-specific-score-weight: 1 + # The default decay interval for the overall score of a peer at the GossipSub scoring + # system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + # and is not too long so that a well-behaved node can't recover from a penalty. + decay-interval: 1m + # The default decay to zero for the overall score of a peer at the GossipSub scoring system. + # It defines the maximum value below which a peer scoring counter is reset to zero. + # This is to prevent the counter from decaying to a very small value. + # The default value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + # When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + # for a long time, and we can reset the counter. + decay-to-zero: 0.01 + topic: + # This is the default value for the skip atomic validation flag for topics. + # We set it to true, which means gossipsub parameter validation will not fail if we leave some of the + # topic parameters at their default values, i.e., zero. This is because we are not setting all + # topic parameters at the current implementation. + skip-atomic-validation: true + # This value is applied to the square of the number of invalid message deliveries on a topic. + # It is used to penalize peers that send invalid messages. By an invalid message, we mean a message that is not signed by the + # publisher, or a message that is not signed by the peer that sent it. We set it to -1.0, which means that with around 14 invalid + # message deliveries within a gossipsub heartbeat interval, the peer will be disconnected. + # The supporting math is as follows: + # - each staked (i.e., authorized) peer is rewarded by the fixed reward of 100 (i.e., DefaultStakedIdentityReward). + # - x invalid message deliveries will result in a penalty of x^2 * DefaultTopicInvalidMessageDeliveriesWeight, i.e., -x^2. + # - the peer will be disconnected when its penalty reaches -100 (i.e., MaxAppSpecificPenalty). + # - so, the maximum number of invalid message deliveries that a peer can have before being disconnected is sqrt(200/DefaultTopicInvalidMessageDeliveriesWeight) ~ 14. + invalid-message-deliveries-weight: -1.0 + # The decay factor used to decay the number of invalid message deliveries. + # The total number of invalid message deliveries is multiplied by this factor at each heartbeat interval to + # decay the number of invalid message deliveries, and prevent the peer from being disconnected if it stops + # sending invalid messages. We set it to 0.99, which means that the number of invalid message deliveries will + # decay by 1% at each heartbeat interval. + # The decay heartbeats are defined by the heartbeat interval of the gossipsub scoring system, which is 1 Minute (defaultDecayInterval). + invalid-message-deliveries-decay: 0.99 + # The default time in mesh quantum for the GossipSub scoring system. It is used to gauge + # a discrete time interval for the time in mesh counter. We set it to 1 hour, which means that every one complete hour a peer is + # in a topic mesh, the time in mesh counter will be incremented by 1 and is counted towards the availability score of the peer in that topic mesh. + # The reason for setting it to 1 hour is that we want to reward peers that are in a topic mesh for a long time, and we want to avoid rewarding peers that + # are churners, i.e., peers that join and leave a topic mesh frequently. + time-in-mesh-quantum: 1h + # The default weight of a topic in the GossipSub scoring system. + # The overall score of a peer in a topic mesh is multiplied by the weight of the topic when calculating the overall score of the peer. + # We set it to 1.0, which means that the overall score of a peer in a topic mesh is not affected by the weight of the topic. + topic-weight: 1.0 + # This is applied to the number of actual message deliveries in a topic mesh + # at each decay interval (i.e., defaultDecayInterval). + # It is used to decay the number of actual message deliveries, and prevents past message + # deliveries from affecting the current score of the peer. + # As the decay interval is 1 minute, we set it to 0.5, which means that the number of actual message + # deliveries will decay by 50% at each decay interval. + mesh-message-deliveries-decay: 0.5 + # The maximum number of actual message deliveries in a topic + # mesh that is used to calculate the score of a peer in that topic mesh. + # We set it to 1000, which means that the maximum number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh is 1000. + # This is to prevent the score of a peer in a topic mesh from being affected by a large number of actual + # message deliveries and also affect the score of the peer in other topic meshes. + # When the total delivered messages in a topic mesh exceeds this value, the score of the peer in that topic + # mesh will not be affected by the actual message deliveries in that topic mesh. + # Moreover, this does not allow the peer to accumulate a large number of actual message deliveries in a topic mesh + # and then start under-performing in that topic mesh without being penalized. + mesh-message-deliveries-cap: 1000 + # The threshold for the number of actual message deliveries in a + # topic mesh that is used to calculate the score of a peer in that topic mesh. + # If the number of actual message deliveries in a topic mesh is less than this value, + # the peer will be penalized by square of the difference between the actual message deliveries and the threshold, + # i.e., -w * (actual - threshold)^2 where `actual` and `threshold` are the actual message deliveries and the + # threshold, respectively, and `w` is the weight (i.e., defaultTopicMeshMessageDeliveriesWeight). + # We set it to 0.1 * defaultTopicMeshMessageDeliveriesCap, which means that if a peer delivers less tha 10% of the + # maximum number of actual message deliveries in a topic mesh, it will be considered as an under-performing peer + # in that topic mesh. + mesh-message-deliveries-threshold: 100 + # The weight for applying penalty when a peer is under-performing in a topic mesh. + # Upon every decay interval, if the number of actual message deliveries is less than the topic mesh message deliveries threshold + # (i.e., defaultTopicMeshMessageDeliveriesThreshold), the peer will be penalized by square of the difference between the actual + # message deliveries and the threshold, multiplied by this weight, i.e., -w * (actual - threshold)^2 where w is the weight, and + # `actual` and `threshold` are the actual message deliveries and the threshold, respectively. + # We set this value to be - 0.05 MaxAppSpecificReward / (defaultTopicMeshMessageDeliveriesThreshold^2). This guarantees that even if a peer + # is not delivering any message in a topic mesh, it will not be disconnected. + # Rather, looses part of the MaxAppSpecificReward that is awarded by our app-specific scoring function to all staked + # nodes by default will be withdrawn, and the peer will be slightly penalized. In other words, under-performing in a topic mesh + # will drop the overall score of a peer by 5% of the MaxAppSpecificReward that is awarded by our app-specific scoring function. + # It means that under-performing in a topic mesh will not cause a peer to be disconnected, but it will cause the peer to lose + # its MaxAppSpecificReward that is awarded by our app-specific scoring function. + # At this point, we do not want to disconnect a peer only because it is under-performing in a topic mesh as it might be + # causing a false positive network partition. + mesh-deliveries-weight: -0.0005 + # The window size is time interval that we count a delivery of an already + # seen message towards the score of a peer in a topic mesh. The delivery is counted + # by GossipSub only if the previous sender of the message is different from the current sender. + # We set it to the decay interval of the GossipSub scoring system, which is 1 minute. + # It means that if a peer delivers a message that it has already seen less than one minute ago, + # the delivery will be counted towards the score of the peer in a topic mesh only if the previous sender of the message. + # This also prevents replay attacks of messages that are older than one minute. As replayed messages will not + # be counted towards the actual message deliveries of a peer in a topic mesh. + mesh-message-deliveries-window: 1m + # The time interval that we wait for a new peer that joins a topic mesh + # till start counting the number of actual message deliveries of that peer in that topic mesh. + # We set it to 2 * defaultDecayInterval, which means that we wait for 2 decay intervals before start counting + # the number of actual message deliveries of a peer in a topic mesh. + # With a default decay interval of 1 minute, it means that we wait for 2 minutes before start counting the + # number of actual message deliveries of a peer in a topic mesh. This is to account for + # the time that it takes for a peer to start up and receive messages from other peers in the topic mesh. + mesh-message-delivery-activation: 2m + thresholds: + # This is the threshold when a peer's penalty drops below this threshold, no gossip + # is emitted towards that peer and gossip from that peer is ignored. + # Validation Constraint: GossipThreshold >= PublishThreshold && GossipThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all gossips to and from peers with penalty -100 are ignored. + gossip: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # self-published messages are not propagated towards this peer. + # Validation Constraint: + # PublishThreshold >= GraylistThreshold && PublishThreshold <= GossipThreshold && PublishThreshold < 0. + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are deprived of receiving any published messages. + publish: -99 + # This is the threshold when a peer's penalty drops below this threshold, + # the peer is graylisted, i.e., incoming RPCs from the peer are ignored. + # Validation Constraint: + # GraylistThreshold =< PublishThreshold && GraylistThreshold =< GossipThreshold && GraylistThreshold < 0 + # How we use it: As the current max penalty is -100, we set the threshold to -99 + # so that all penalized peers are graylisted. + graylist: -99 + # This is the threshold when a peer sends us PX information with a prune, + # we only accept it and connect to the supplied peers if the originating peer's + # penalty exceeds this threshold. + # Validation Constraint: must be non-negative. + # How we use it: As the current max reward is 100, we set the threshold to 99 + # so that we only receive supplied peers from well-behaved peers. + accept-px: 99 + # This is the threshold when the median peer penalty in the mesh drops + # below this value, the peer may select more peers with penalty above the median + # to opportunistically graft on the mesh. + # Validation Constraint: must be non-negative. + # How we use it: We set it to the -100 + 1 so that we only + # opportunistically graft peers that are not access nodes (i.e., with -1), + # or penalized peers (i.e., with -100). + opportunistic-graft: 101 + behaviour: + # The threshold when the behavior of a peer is considered as bad by GossipSub. + # Currently, the misbehavior is defined as advertising an iHave without responding to the iWants (iHave broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # for a while, and the remote peer keep attempting on GRAFT (aka GRAFT flood). + # When the misbehavior counter of a peer goes beyond this threshold, the peer is penalized by defaultBehaviorPenaltyWeight (see below) for the excess misbehavior. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # We set it to 10, meaning that we at most tolerate 10 of such RPCs containing iHave broken promises. After that, the peer is penalized for every + # excess RPC containing iHave broken promises. + # The counter is also decayed by (0.99) every decay interval (defaultDecayInterval) i.e., every minute. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system). + penalty-threshold: 1000 + # The weight for applying penalty when a peer misbehavior goes beyond the threshold. + # Misbehavior of a peer at gossipsub layer is defined as advertising an iHave without responding to the iWants (broken promises), as well as attempting + # on GRAFT when the peer is considered for a PRUNE backoff, i.e., the local peer does not allow the peer to join the local topic mesh + # This is detected by the GossipSub scoring system, and the peer is penalized by defaultBehaviorPenaltyWeight. + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # + # The penalty is applied to the square of the difference between the misbehavior counter and the threshold, i.e., -|w| * (misbehavior counter - threshold)^2. + # We set it to 0.01 * MaxAppSpecificPenalty, which means that misbehaving 10 times more than the threshold (i.e., 10 + 10) will cause the peer to lose + # its entire AppSpecificReward that is awarded by our app-specific scoring function to all staked (i.e., authorized) nodes by default. + # Moreover, as the MaxAppSpecificPenalty is -MaxAppSpecificReward, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). + # + # The math is as follows: -|w| * (misbehavior - threshold)^2 = 0.01 * MaxAppSpecificPenalty * (misbehavior - threshold)^2 < 2 * MaxAppSpecificPenalty + # if misbehavior > threshold + sqrt(2) * 10. + # As shown above, with this choice of defaultBehaviorPenaltyWeight, misbehaving sqrt(2) * 10 times more than the threshold will cause the peer score + # to be dropped below the MaxAppSpecificPenalty, which is also below the GraylistThreshold, and the peer will be graylisted (i.e., disconnected). This weight + # is chosen in a way that with almost a few misbehaviors more than the threshold, the peer will be graylisted. The rationale relies on the fact that + # the misbehavior counter is incremented by 1 for each RPC containing one or more broken promises. Hence, it is per RPC, and not per broken promise. + # Having sqrt(2) * 10 broken promises RPC is a blatant misbehavior, and the peer should be graylisted. With decay interval of 1 minute, and decay value of + # 0.99 we expect a graylisted node due to borken promises to get back in about 527 minutes, i.e., (0.99)^x * (sqrt(2) * 10)^2 * MaxAppSpecificPenalty > GraylistThreshold + # where x is the number of decay intervals that the peer is graylisted. As MaxAppSpecificPenalty and GraylistThresholds are close, we can simplify the inequality + # to (0.99)^x * (sqrt(2) * 10)^2 > 1 --> (0.99)^x * 200 > 1 --> (0.99)^x > 1/200 --> x > log(1/200) / log(0.99) --> x > 527.17 decay intervals, i.e., 527 minutes. + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that are reported by the engines). + penalty-weight: -0.01 + # The decay interval for the misbehavior counter of a peer. The misbehavior counter is + # incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff). + # + # An iHave broken promise means that a peer advertises an iHave for a message, but does not respond to the iWant requests for that message. + # For iHave broken promises, the gossipsub scoring works as follows: + # It samples ONLY A SINGLE iHave out of the entire RPC. + # If that iHave is not followed by an actual message within the next 3 seconds, the peer misbehavior counter is incremented by 1. + # This means that regardless of how many iHave broken promises an RPC contains, the misbehavior counter is incremented by 1. + # That is why we decay the misbehavior counter very slow, as this counter indicates a severe misbehavior. + # + # The misbehavior counter is decayed per decay interval (i.e., defaultDecayInterval = 1 minute) by GossipSub. + # We set it to 0.99, which means that the misbehavior counter is decayed by 1% per decay interval. + # With the generous threshold that we set (i.e., defaultBehaviourPenaltyThreshold = 10), we take the peers going beyond the threshold as persistent misbehaviors, + # We expect honest peers never to go beyond the threshold, and if they do, we expect them to go back below the threshold quickly. + # + # Note that misbehaviors are counted by GossipSub across all topics (and is different from the Application Layer Misbehaviors that we count through + # the ALSP system that is based on the engines report). + penalty-decay: 0.5 + protocol: + # The max number of debug/trace log events per second. + # Logs emitted above this threshold are dropped. + max-debug-logs: 50 + application-specific: + # This is the maximum penalty for severe offenses that we apply + # to a remote node score. The score mechanism of GossipSub in Flow is designed + # in a way that all other infractions are penalized with a fraction of this value. + # We have also set the other parameters such as GraylistThreshold, + # GossipThreshold, and PublishThreshold to be a bit higher than this, + # i.e., -100 + 1. This ensures that a node with a score of + # -100 will be graylisted (i.e., all incoming and outgoing RPCs + # are rejected) and will not be able to publish or gossip any messages. + max-app-specific-penalty: -100 + min-app-specific-penalty: -1 + # This is the penalty for unknown identity. It is + # applied to the peer's score when the peer is not in the identity list. + unknown-identity-penalty: -100 + # This is the penalty for invalid subscription. + # It is applied to the peer's score when the peer subscribes to a topic that it is + # not authorized to subscribe to. + invalid-subscription-penalty: -100 + # This is the reward for well-behaving staked peers. + # If a peer does not have any misbehavior record, e.g., invalid subscription, + # invalid message, etc., it will be rewarded with this score. + max-app-specific-reward: 100 + # This is the reward for staking peers. It is applied + # to the peer's score when the peer does not have any misbehavior record, e.g., + # invalid subscription, invalid message, etc. The purpose is to reward the staking + # peers for their contribution to the network and prioritize them in neighbor selection. + staked-identity-reward: 100 scoring-registry: - # The minimum speed at which the spam penalty value of a peer is decayed. - # Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, - # The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value - # indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. - # assume: - # penalty = -100 (the maximum application specific penalty is -100) - # skipDecayThreshold = -0.1 - # it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. - # x * 0.99 ^ n > -0.1 (assuming negative x). - # 0.99 ^ n > -0.1 / x - # Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). - # log( 0.99 ^ n ) < log( 0.1 / x ) - # Using the properties of logarithms, we can bring down the exponent: - # n * log( 0.99 ) < log( -0.1 / x ) - # And finally, we can solve for n: - # n > log( -0.1 / x ) / log( 0.99 ) - # We can plug in x = -100: - # n > log( -0.1 / -100 ) / log( 0.99 ) - # n > log( 0.001 ) / log( 0.99 ) - # n > -3 / log( 0.99 ) - # n > 458.22 - minimum-spam-penalty-decay-factor: 0.99 - # The maximum rate at which the spam penalty value of a peer decays. Decay speeds increase - # during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, - # decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. - maximum-spam-penalty-decay-factor: 0.8 + app-specific-score: + # number of workers that asynchronously update the app specific score requests when they are expired. + score-update-worker-num: 5 + # size of the queue used by the worker pool for the app specific score update requests. The queue is used to buffer the app specific score update requests + # before they are processed by the worker pool. The queue size must be larger than total number of peers in the network. + # The queue is deduplicated based on the peer ids ensuring that there is only one app specific score update request per peer in the queue. + score-update-request-queue-size: 10_000 + # score ttl is the time to live for the app specific score. Once the score is expired; a new request will be sent to the app specific score provider to update the score. + # until the score is updated, the previous score will be used. + score-ttl: 1m + spam-record-cache: + # size of cache used to track spam records at gossipsub. Each peer id is mapped to a spam record that keeps track of the spam score for that peer. + # cache should be big enough to keep track of the entire network's size. Otherwise, the local node's view of the network will be incomplete due to cache eviction. + cache-size: 10_000 + decay: + # Threshold level for spam record penalty. + # At each evaluation period, when a node's penalty is below this value, the decay rate slows down, ensuring longer decay periods for malicious nodes and quicker decay for honest ones. + penalty-decay-slowdown-threshold: -99 + # This setting adjusts the decay rate when a node's penalty falls below the threshold. + # The decay rate, ranging between 0 and 1, dictates how quickly penalties decrease: a higher rate results in slower decay. + # The decay calculation is multiplicative (newPenalty = decayRate * oldPenalty). + # The reduction factor increases the decay rate, thus decelerating the penalty reduction. For instance, with a 0.01 reduction factor, + # the decay rate increases by 0.01 at each evaluation interval when the penalty is below the threshold. + # Consequently, a decay rate of `x` diminishes the penalty to zero more rapidly than a rate of `x+0.01`. + penalty-decay-rate-reduction-factor: 0.01 + # Defines the frequency for evaluating and potentially adjusting the decay process of a spam record. + # At each interval, the system assesses the current penalty of a node. + # If this penalty is below the defined threshold, the decay rate is modified according to the reduction factor, slowing down the penalty reduction process. + # This reassessment at regular intervals ensures that the decay rate is dynamically adjusted to reflect the node's ongoing behavior, + # maintaining a balance between penalizing malicious activity and allowing recovery for honest nodes. + penalty-decay-evaluation-period: 10m + # The minimum speed at which the spam penalty value of a peer is decayed. + # Spam record will be initialized with a decay value between .5 , .7 and this value will then be decayed up to .99 on consecutive misbehavior's, + # The maximum decay value decays the penalty by 1% every second. The decay is applied geometrically, i.e., `newPenalty = oldPenalty * decay`, hence, the higher decay value + # indicates a lower decay speed, i.e., it takes more heartbeat intervals to decay a penalty back to zero when the decay value is high. + # assume: + # penalty = -100 (the maximum application specific penalty is -100) + # skipDecayThreshold = -0.1 + # it takes around 459 seconds for the penalty to decay to reach greater than -0.1 and turn into 0. + # x * 0.99 ^ n > -0.1 (assuming negative x). + # 0.99 ^ n > -0.1 / x + # Now we can take the logarithm of both sides (with any base, but let's use base 10 for simplicity). + # log( 0.99 ^ n ) < log( 0.1 / x ) + # Using the properties of logarithms, we can bring down the exponent: + # n * log( 0.99 ) < log( -0.1 / x ) + # And finally, we can solve for n: + # n > log( -0.1 / x ) / log( 0.99 ) + # We can plug in x = -100: + # n > log( -0.1 / -100 ) / log( 0.99 ) + # n > log( 0.001 ) / log( 0.99 ) + # n > -3 / log( 0.99 ) + # n > 458.22 + minimum-spam-penalty-decay-factor: 0.99 + # The maximum rate at which the spam penalty value of a peer decays. Decay speeds increase + # during sustained malicious activity, leading to a slower recovery of the app-specific score for the penalized node. Conversely, + # decay speeds decrease, allowing faster recoveries, when nodes exhibit fleeting misbehavior. + maximum-spam-penalty-decay-factor: 0.8 + # The threshold for which when the negative penalty is above this value, the decay function will not be called. + # instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. + skip-decay-threshold: -0.1 misbehaviour-penalties: - # The threshold for which when the negative penalty is above this value, the decay function will not be called. - # instead, the penalty will be set to 0. This is to prevent the penalty from keeping a small negative value for a long time. - skip-decay-threshold: -0.1 # The penalty applied to the application specific penalty when a peer conducts a graft misbehaviour. graft: -10 # The penalty applied to the application specific penalty when a peer conducts a prune misbehaviour. 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 088adaca1a1..e9959977b55 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -64,7 +64,7 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { conf, err := config.DefaultConfig() 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.ScoringParameters.PeerScoring.Internal.DecayInterval = 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( @@ -133,21 +133,21 @@ func TestGossipSubIHaveBrokenPromises_Below_Threshold(t *testing.T) { // since spammer is not yet considered to be penalized, its score must be greater than the gossipsub health thresholds. require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // eventually, after a heartbeat the spammer behavioral counter must be decayed @@ -316,27 +316,27 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // 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-5*5*0.01*scoreParams.InternalPeerScoring.Rewards.MaxAppSpecificReward, + initScore-5*5*0.01*scoreParams.PeerScoring.Protocol.AppSpecificScore.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-5*5*0.1*scoreParams.InternalPeerScoring.Rewards.MaxAppSpecificReward, + initScore-5*5*0.1*scoreParams.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be greater than gossip threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be greater than publish threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Greaterf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be greater than graylist threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // since the spammer score is above the gossip, graylist and publish thresholds, it should be still able to exchange messages with victim. @@ -371,21 +371,21 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { // victim will not exchange messages with it anymore, and also that it will be graylisted meaning all incoming and outgoing RPCs to and from the spammer will be dropped by the victim. require.Lessf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, "sanity check failed, the score of the spammer node must be less than gossip threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Gossip, + scoreParams.PeerScoring.Internal.Thresholds.Gossip, spammerScore) require.Lessf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, "sanity check failed, the score of the spammer node must be less than publish threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Publish, + scoreParams.PeerScoring.Internal.Thresholds.Publish, spammerScore) require.Lessf(t, spammerScore, - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, "sanity check failed, the score of the spammer node must be less than graylist threshold: %f, actual: %f", - scoreParams.InternalPeerScoring.Thresholds.Graylist, + scoreParams.PeerScoring.Internal.Thresholds.Graylist, spammerScore) // since the spammer score is below the gossip, graylist and publish thresholds, it should not be able to exchange messages with victim anymore. diff --git a/network/netconf/flags.go b/network/netconf/flags.go index 56d52bfba18..9dbd4703f43 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -101,54 +101,61 @@ func AllFlagNames() []string { BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.MessageErrorThresholdKey), BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.UpdateIntervalKey), BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.CacheSizeKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayRateReductionFactorKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.AppSpecificScoreWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayToZeroKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.SkipAtomicValidationKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TimeInMeshQuantumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TopicWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshDeliveriesWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GossipThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.PublishThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GraylistThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.AcceptPXThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.MaxDebugLogsKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MinAppSpecificKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.UnknownIdentityKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.InvalidSubscriptionKey, p2pconfig.PenaltyKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.RewardKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.StakedIdentityKey, p2pconfig.RewardKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), + + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.SkipDecayThresholdKey), + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PruneMisbehaviourKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IHaveMisbehaviourKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.IWantMisbehaviourKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.PublishMisbehaviourKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.ClusterPrefixedReductionFactorKey), - - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), } for _, scope := range []string{systemScope, transientScope, protocolScope, peerScope, peerProtocolScope} { @@ -257,17 +264,6 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { "base probability of creating a misbehavior report for a range request message") flags.Float32(alspSyncEngineSyncRequestProb, config.AlspConfig.SyncEngine.SyncRequestProb, "probability of creating a misbehavior report for a sync request message") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), - config.GossipSub.ScoringParameters.SpamRecordCache.PenaltyDecaySlowdownThreshold, - fmt.Sprintf("the penalty level at which the decay rate is reduced by --%s", - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayRateReductionFactorKey))) - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayRateReductionFactorKey), - config.GossipSub.ScoringParameters.SpamRecordCache.DecayRateReductionFactor, - fmt.Sprintf("defines the value by which the decay rate is decreased every time the penalty is below the --%s", - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecaySlowdownThresholdKey))) - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), - config.GossipSub.ScoringParameters.SpamRecordCache.PenaltyDecayEvaluationPeriod, - "defines the period at which the decay for a spam record is okay to be adjusted.") flags.Int(BuildFlagName(gossipsubKey, p2pconfig.RpcInspectorKey, p2pconfig.ValidationConfigKey, p2pconfig.IHaveConfigKey, p2pconfig.MaxSampleSizeKey), config.GossipSub.RpcInspector.Validation.IHave.MaxSampleSize, "max number of ihaves to sample when performing validation") @@ -304,31 +300,136 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.CacheSizeKey), config.GossipSub.SubscriptionProvider.CacheSize, "size of the cache that keeps the list of topics each peer has subscribed to, recommended size is 10x the number of authorized nodes") - flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), - config.GossipSub.ScoringParameters.AppSpecificScore.ScoreUpdateWorkerNum, - "number of workers for the app specific score update worker pool") - flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), - config.GossipSub.ScoringParameters.AppSpecificScore.ScoreUpdateRequestQueueSize, - "size of the app specific score update worker pool queue") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), - config.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL, - "time to live for app specific scores; when expired a new request will be sent to the score update worker pool; till then the expired score will be used") - flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), - config.GossipSub.ScoringParameters.SpamRecordCache.CacheSize, - "size of the spam record cache, recommended size is 10x the number of authorized nodes") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), config.GossipSub.ScoringParameters.DecayInterval, "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval, + "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.AppSpecificScoreWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.AppSpecificScoreWeight, + "the weight for app-specific scores") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayToZeroKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.DecayToZero, + "the maximum value below which a peer scoring counter is reset to zero") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, + flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.SkipAtomicValidationKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, + "the default value for the skip atomic validation flag for topics") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, + "this value is applied to the square of the number of invalid message deliveries on a topic") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, + "the decay factor used to decay the number of invalid message deliveries") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TimeInMeshQuantumKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, + "the time in mesh quantum for the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.TopicWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.TopicWeight, + "the weight of a topic in the GossipSub scoring system") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, + "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesCapKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, + "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, + "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshDeliveriesWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, + "the weight for applying penalty when a peer is under-performing in a topic mesh") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, + "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.TopicKey, p2pconfig.MeshMessageDeliveryActivationKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, + "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GossipThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip, + "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.PublishThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish, + "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.GraylistThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist, + "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.AcceptPXThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.AcceptPX, + "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.ThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.OpportunisticGraft, + "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") + + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyThreshold, + "the threshold when the behavior of a peer is considered as bad by GossipSub") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyWeight, + "the weight for applying penalty when a peer misbehavior goes beyond the threshold") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.BehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), + config.GossipSub.ScoringParameters.PeerScoring.Internal.Behaviour.PenaltyDecay, + "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") + + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.MaxDebugLogsKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.MaxDebugLogs, + "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificPenalty, + "the maximum penalty for sever offenses that we apply to a remote node score") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MinAppSpecificKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, + "the minimum penalty for sever offenses that we apply to a remote node score") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.UnknownIdentityKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, + "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.InvalidSubscriptionKey, p2pconfig.PenaltyKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, + "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.MaxAppSpecificKey, p2pconfig.RewardKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward, + "the reward for well-behaving staked peers") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.ProtocolKey, p2pconfig.AppSpecificKey, p2pconfig.StakedIdentityKey, p2pconfig.RewardKey), + config.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, + "the reward for staking peers") + + flags.Int(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateWorkerNumKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreUpdateWorkerNum, + "number of workers for the app specific score update worker pool") + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreUpdateRequestQueueSizeKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreUpdateRequestQueueSize, + "size of the app specific score update worker pool queue") + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.AppSpecificScoreRegistryKey, p2pconfig.ScoreTTLKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL, + "time to live for app specific scores; when expired a new request will be sent to the score update worker pool; till then the expired score will be used") + + flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.CacheSizeKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.CacheSize, + "size of the spam record cache, recommended size is 10x the number of authorized nodes") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, + fmt.Sprintf("the penalty level at which the decay rate is reduced by --%s", + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey))) + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.DecayRateReductionFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, + fmt.Sprintf("defines the value by which the decay rate is decreased every time the penalty is below the --%s", + BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecaySlowdownThresholdKey))) + flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.PenaltyDecayEvaluationPeriodKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, + "defines the period at which the decay for a spam record is okay to be adjusted.") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MinimumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, "the minimum speed at which the spam penalty value of a peer is decayed") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.MaximumSpamPenaltyDecayFactorKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, "the maximum rate at which the spam penalty value of a peer decays") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.SkipDecayThresholdKey), - config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.SpamRecordCacheKey, p2pconfig.DecayKey, p2pconfig.SkipDecayThresholdKey), + config.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, "the threshold for which when the negative penalty is above this value, the decay function will not be called") + flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoringRegistryKey, p2pconfig.MisbehaviourPenaltiesKey, p2pconfig.GraftMisbehaviourKey), config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.GraftMisbehaviour, "the penalty applied to the application specific penalty when a peer conducts a graft misbehaviour") @@ -348,98 +449,6 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { config.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.ClusterPrefixedReductionFactor, "the factor used to reduce the penalty for control message misbehaviours on cluster prefixed topics") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.AppScoreWeightKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.AppSpecificScoreWeight, - "the weight for app-specific scores") - flags.Uint32(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.MaxDebugLogsKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.MaxDebugLogs, - "the max number of debug/trace log events per second. Logs emitted above this threshold are dropped") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayIntervalKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.DecayInterval, - "the decay interval for the overall score of a peer at the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionDecayToZeroKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.DecayToZero, - "the decay to zero for the overall score of a peer at the GossipSub scoring system") - - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MaxAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.MaxAppSpecificPenalty, - "the maximum penalty for sever offenses that we apply to a remote node score") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.MinAppSpecificPenaltyKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.MinAppSpecificPenalty, - "the minimum penalty for sever offenses that we apply to a remote node score") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.UnknownIdentityPenaltyKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.UnknownIdentityPenalty, - "the penalty for unknown identity. It is applied to the peer's score when the peer is not in the identity list") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionPenaltiesKey, p2pconfig.InvalidSubscriptionPenaltyKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, - "the penalty for invalid subscription. It is applied to the peer's score when the peer subscribes to a topic that it is not authorized to subscribe to") - - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.MaxAppSpecificRewardKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.MaxAppSpecificReward, - "the reward for well-behaving staked peers") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionRewardsKey, p2pconfig.StakedIdentityRewardKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.StakedIdentityReward, - "the reward for staking peers") - - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GossipThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Gossip, - "the threshold when a peer's penalty drops below this threshold, no gossip is emitted towards that peer and gossip from that peer is ignored") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.PublishThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Publish, - "the threshold when a peer's penalty drops below this threshold, self-published messages are not propagated towards this peer") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.GraylistThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.Graylist, - "the threshold when a peer's penalty drops below this threshold, the peer is graylisted, i.e., incoming RPCs from the peer are ignored") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.AcceptPXThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.AcceptPX, - "the threshold when a peer sends us PX information with a prune, we only accept it and connect to the supplied peers if the originating peer's penalty exceeds this threshold") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionThresholdsKey, p2pconfig.OpportunisticGraftThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Thresholds.OpportunisticGraft, - "the threshold when the median peer penalty in the mesh drops below this value, the peer may select more peers with penalty above the median to opportunistically graft on the mesh") - - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyThreshold, - "the threshold when the behavior of a peer is considered as bad by GossipSub") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyWeightKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyWeight, - "the weight for applying penalty when a peer misbehavior goes beyond the threshold") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionBehaviourKey, p2pconfig.BehaviourPenaltyDecayKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.Behaviour.PenaltyDecay, - "the decay interval for the misbehavior counter of a peer. The misbehavior counter is incremented by GossipSub for iHave broken promises or the GRAFT flooding attacks (i.e., each GRAFT received from a remote peer while that peer is on a PRUNE backoff)") - - flags.Bool(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.SkipAtomicValidationKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.SkipAtomicValidation, - "the default value for the skip atomic validation flag for topics") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesWeightKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesWeight, - "this value is applied to the square of the number of invalid message deliveries on a topic") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.InvalidMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesDecay, - "the decay factor used to decay the number of invalid message deliveries") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TimeInMeshQuantumKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.TimeInMeshQuantum, - "the time in mesh quantum for the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.TopicWeightKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.TopicWeight, - "the weight of a topic in the GossipSub scoring system") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesDecayKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesDecay, - "this is applied to the number of actual message deliveries in a topic mesh at each decay interval (i.e., DecayInterval)") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesCapKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesCap, - "The maximum number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryThresholdKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveryThreshold, - "The threshold for the number of actual message deliveries in a topic mesh that is used to calculate the score of a peer in that topic mesh") - flags.Float64(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshDeliveriesWeightKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshDeliveriesWeight, - "the weight for applying penalty when a peer is under-performing in a topic mesh") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveriesWindowKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesWindow, - "the window size is time interval that we count a delivery of an already seen message towards the score of a peer in a topic mesh. The delivery is counted by GossipSub only if the previous sender of the message is different from the current sender") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.ScoreOptionKey, p2pconfig.ScoreOptionTopicKey, p2pconfig.MeshMessageDeliveryActivationKey), - config.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation.MeshMessageDeliveryActivation, - "the time interval that we wait for a new peer that joins a topic mesh till start counting the number of actual message deliveries of that peer in that topic mesh") } // LoadLibP2PResourceManagerFlags loads all CLI flags for the libp2p resource manager configuration on the provided pflag set. diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index e9b82c68e4c..f1edd8426d8 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -79,66 +79,18 @@ type GossipSubParameters struct { } const ( - AppSpecificScoreRegistryKey = "app-specific-score" - SpamRecordCacheKey = "spam-record-cache" - DecayIntervalKey = "decay-interval" + DecayIntervalKey = "decay-interval" ) // ScoringParameters are the parameters for the score option. // Parameters are "numerical values" that are used to compute or build components that compute the score of a peer in GossipSub system. type ScoringParameters struct { - AppSpecificScore AppSpecificScoreParameters `validate:"required" mapstructure:"app-specific-score"` - SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` // DecayInterval is the interval at which the counters associated with a peer behavior in GossipSub system are decayed. DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` - InternalPeerScoring InternalPeerScoring `validate:"required" mapstructure:"internal-peer-scoring"` + PeerScoring PeerScoringParameters `validate:"required" mapstructure:"peer-scoring"` ScoringRegistryParameters ScoringRegistryParameters `validate:"required" mapstructure:"scoring-registry"` } -const ( - ScoreUpdateWorkerNumKey = "score-update-worker-num" - ScoreUpdateRequestQueueSizeKey = "score-update-request-queue-size" - ScoreTTLKey = "score-ttl" -) - -// AppSpecificScoreParameters is the parameters for the GossipSubAppSpecificScoreRegistry. -// Parameters are "numerical values" that are used to compute or build components that compute or maintain the application specific score of peers. -type AppSpecificScoreParameters struct { - // ScoreUpdateWorkerNum is the number of workers in the worker pool for handling the application specific score update of peers in a non-blocking way. - ScoreUpdateWorkerNum int `validate:"gt=0" mapstructure:"score-update-worker-num"` - - // ScoreUpdateRequestQueueSize is the size of the worker pool for handling the application specific score update of peers in a non-blocking way. - ScoreUpdateRequestQueueSize uint32 `validate:"gt=0" mapstructure:"score-update-request-queue-size"` - - // ScoreTTL is the time to live of the application specific score of a peer; the registry keeps a cached copy of the - // application specific score of a peer for this duration. When the duration expires, the application specific score - // of the peer is updated asynchronously. As long as the update is in progress, the cached copy of the application - // specific score of the peer is used even if it is expired. - ScoreTTL time.Duration `validate:"required" mapstructure:"score-ttl"` -} - -const ( - PenaltyDecaySlowdownThresholdKey = "penalty-decay-slowdown-threshold" - DecayRateReductionFactorKey = "penalty-decay-rate-reduction-factor" - PenaltyDecayEvaluationPeriodKey = "penalty-decay-evaluation-period" -) - -type SpamRecordCacheParameters struct { - // CacheSize is size of the cache used to store the spam records of peers. - // The spam records are used to penalize peers that send invalid messages. - CacheSize uint32 `validate:"gt=0" mapstructure:"cache-size"` - - // PenaltyDecaySlowdownThreshold defines the penalty level which the decay rate is reduced by `DecayRateReductionFactor` every time the penalty of a node falls below the threshold, thereby slowing down the decay process. - // This mechanism ensures that malicious nodes experience longer decay periods, while honest nodes benefit from quicker decay. - PenaltyDecaySlowdownThreshold float64 `validate:"lt=0" mapstructure:"penalty-decay-slowdown-threshold"` - - // DecayRateReductionFactor defines the value by which the decay rate is decreased every time the penalty is below the PenaltyDecaySlowdownThreshold. A reduced decay rate extends the time it takes for penalties to diminish. - DecayRateReductionFactor float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay-rate-reduction-factor"` - - // PenaltyDecayEvaluationPeriod defines the interval at which the decay for a spam record is okay to be adjusted. - PenaltyDecayEvaluationPeriod time.Duration `validate:"gt=0" mapstructure:"penalty-decay-evaluation-period"` -} - // SubscriptionProviderParameters keys. const ( UpdateIntervalKey = "update-interval" diff --git a/network/p2p/config/peer_scoring.go b/network/p2p/config/peer_scoring.go index b2c7d93369a..25b70e77687 100644 --- a/network/p2p/config/peer_scoring.go +++ b/network/p2p/config/peer_scoring.go @@ -3,77 +3,92 @@ package p2pconfig import "time" const ( - ScoreOptionKey = "internal-peer-scoring" - AppScoreWeightKey = "app-specific-score-weight" - MaxDebugLogsKey = "max-debug-logs" - ScoreOptionDecayIntervalKey = "decay-interval" - ScoreOptionDecayToZeroKey = "decay-to-zero" - ScoreOptionPenaltiesKey = "penalties" - ScoreOptionRewardsKey = "rewards" - ScoreOptionThresholdsKey = "thresholds" - ScoreOptionBehaviourKey = "behaviour" - ScoreOptionTopicKey = "topic" + PeerScoringKey = "peer-scoring" + InternalKey = "internal" + ProtocolKey = "protocol" ) -// InternalPeerScoring gossipsub scoring option configuration parameters. -type InternalPeerScoring struct { +// PeerScoringParameters encapsulates the parameters of the GossipSub scoring system. +type PeerScoringParameters struct { + // Internal is the internal parameters of the GossipSub scoring system that are hosted by + // the GossipSub system, and are not exposed to the Flow protocol. + // The internal parameters are hosted by the GossipSub system. + Internal InternalGossipSubScoreParams `validate:"required" mapstructure:"internal"` + // Protocol is the protocol parameters of the peer scoring system that is hosted by the Flow protocol. + Protocol ProtocolLevelGossipSubScoreParams `validate:"required" mapstructure:"protocol"` +} + +const ( + AppSpecificScoreWeightKey = "app-specific-score-weight" + DecayToZeroKey = "decay-to-zero" + ThresholdsKey = "thresholds" + BehaviourKey = "behaviour" + TopicKey = "topic" +) + +type InternalGossipSubScoreParams struct { // AppSpecificScoreWeight is the weight for app-specific scores. It is used to scale the app-specific - // scores to the same range as the other scores. + // scores to the same range as the other scores. At the current version, we don't distinguish between the app-specific + // scores and the other scores, so we set it to 1. AppSpecificScoreWeight float64 `validate:"gt=0,lte=1" mapstructure:"app-specific-score-weight"` - // MaxDebugLogs sets the max number of debug/trace log events per second. Logs emitted above - // this threshold are dropped. - MaxDebugLogs uint32 `validate:"lte=50" mapstructure:"max-debug-logs"` // DecayInterval is the decay interval for the overall score of a peer at the GossipSub scoring - // system. + // system. We set it to 1 minute so that it is not too short so that a malicious node can recover from a penalty + // and is not too long so that a well-behaved node can't recover from a penalty. DecayInterval time.Duration `validate:"gte=1m" mapstructure:"decay-interval"` // DecayToZero is the decay to zero for the overall score of a peer at the GossipSub scoring system. // It defines the maximum value below which a peer scoring counter is reset to zero. // This is to prevent the counter from decaying to a very small value. + // The value is 0.01, which means that a counter will be reset to zero if it decays to 0.01. + // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior + // for a long time, and we can reset the counter. DecayToZero float64 `validate:"required" mapstructure:"decay-to-zero"` - Penalties ScoreOptionPenalties `validate:"required" mapstructure:"penalties"` - Rewards ScoreOptionRewards `validate:"required" mapstructure:"rewards"` + TopicParameters TopicScoringParameters `validate:"required" mapstructure:"topic"` Thresholds InternalScoringThresholds `validate:"required" mapstructure:"thresholds"` Behaviour InternalScoringBehavioural `validate:"required" mapstructure:"behaviour"` - TopicValidation ScoreOptionTopicValidation `validate:"required" mapstructure:"topic"` } const ( - MaxAppSpecificPenaltyKey = "max-app-specific" - MinAppSpecificPenaltyKey = "min-app-specific" - UnknownIdentityPenaltyKey = "unknown-identity" - InvalidSubscriptionPenaltyKey = "invalid-subscription" + MaxDebugLogsKey = "max-debug-logs" + AppSpecificKey = "application-specific" ) -// ScoreOptionPenalties score option penalty configuration parameters. -type ScoreOptionPenalties struct { +type ProtocolLevelGossipSubScoreParams struct { + MaxDebugLogs uint32 `validate:"lte=50" mapstructure:"max-debug-logs"` + AppSpecificScore ApplicationSpecificScoreParameters `validate:"required" mapstructure:"application-specific"` +} + +const ( + MaxAppSpecificKey = "max-app-specific" + MinAppSpecificKey = "min-app-specific" + UnknownIdentityKey = "unknown-identity" + InvalidSubscriptionKey = "invalid-subscription" + StakedIdentityKey = "staked-identity" + RewardKey = "reward" + PenaltyKey = "penalty" +) + +type ApplicationSpecificScoreParameters struct { // MaxAppSpecificPenalty the maximum penalty for sever offenses that we apply to a remote node score. The score // mechanism of GossipSub in Flow is designed in a way that all other infractions are penalized with a fraction of - // this value. - MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific"` + // this value. We have also set the other parameters such as DefaultGraylistThreshold, DefaultGossipThreshold and DefaultPublishThreshold to + // be a bit higher than this, i.e., MaxAppSpecificPenalty + 1. This ensures that a node with a score of MaxAppSpecificPenalty + // will be graylisted (i.e., all incoming and outgoing RPCs are rejected) and will not be able to publish or gossip any messages. + MaxAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"max-app-specific-penalty"` // MinAppSpecificPenalty the minimum penalty for sever offenses that we apply to a remote node score. - MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific"` + MinAppSpecificPenalty float64 `validate:"lt=0" mapstructure:"min-app-specific-penalty"` // UnknownIdentityPenalty is the penalty for unknown identity. It is applied to the peer's score when // the peer is not in the identity list. - UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity"` + UnknownIdentityPenalty float64 `validate:"lt=0" mapstructure:"unknown-identity-penalty"` // InvalidSubscriptionPenalty is the penalty for invalid subscription. It is applied to the peer's score when // the peer subscribes to a topic that it is not authorized to subscribe to. - InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription"` -} - -const ( - MaxAppSpecificRewardKey = "max-app-specific" - StakedIdentityRewardKey = "staked-identity" -) - -// ScoreOptionRewards score option rewards configuration parameters. -type ScoreOptionRewards struct { + InvalidSubscriptionPenalty float64 `validate:"lt=0" mapstructure:"invalid-subscription-penalty"` // MaxAppSpecificReward is the reward for well-behaving staked peers. If a peer does not have // any misbehavior record, e.g., invalid subscription, invalid message, etc., it will be rewarded with this score. - MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific"` + MaxAppSpecificReward float64 `validate:"gt=0" mapstructure:"max-app-specific-reward"` // StakedIdentityReward is the reward for staking peers. It is applied to the peer's score when // the peer does not have any misbehavior record, e.g., invalid subscription, invalid message, etc. // The purpose is to reward the staking peers for their contribution to the network and prioritize them in neighbor selection. - StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity"` + StakedIdentityReward float64 `validate:"gt=0" mapstructure:"staked-identity-reward"` } const ( @@ -166,8 +181,8 @@ const ( MeshMessageDeliveryActivationKey = "mesh-message-delivery-activation" ) -// ScoreOptionTopicValidation score option topic validation configuration parameters. -type ScoreOptionTopicValidation struct { +// TopicScoringParameters score option topic validation configuration parameters. +type TopicScoringParameters struct { // SkipAtomicValidation is the value for the skip atomic validation flag for topics. // If set it to true, the gossipsub parameter validation will not fail if we leave some of the // topic parameters at their values, i.e., zero. diff --git a/network/p2p/config/score_registry.go b/network/p2p/config/score_registry.go index 95437d90111..771edb685e8 100644 --- a/network/p2p/config/score_registry.go +++ b/network/p2p/config/score_registry.go @@ -1,20 +1,80 @@ package p2pconfig +import "time" + +const ( + SpamRecordCacheKey = "spam-record-cache" + ScoringRegistryKey = "scoring-registry" + AppSpecificScoreRegistryKey = "app-specific-score" +) + +type ScoringRegistryParameters struct { + AppSpecificScore AppSpecificScoreParameters `validate:"required" mapstructure:"app-specific-score"` + SpamRecordCache SpamRecordCacheParameters `validate:"required" mapstructure:"spam-record-cache"` + MisbehaviourPenalties MisbehaviourPenalties `validate:"required" mapstructure:"misbehaviour-penalties"` +} + +const ( + ScoreUpdateWorkerNumKey = "score-update-worker-num" + ScoreUpdateRequestQueueSizeKey = "score-update-request-queue-size" + ScoreTTLKey = "score-ttl" +) + +// AppSpecificScoreParameters is the parameters for the GossipSubAppSpecificScoreRegistry. +// Parameters are "numerical values" that are used to compute or build components that compute or maintain the application specific score of peers. +type AppSpecificScoreParameters struct { + // ScoreUpdateWorkerNum is the number of workers in the worker pool for handling the application specific score update of peers in a non-blocking way. + ScoreUpdateWorkerNum int `validate:"gt=0" mapstructure:"score-update-worker-num"` + + // ScoreUpdateRequestQueueSize is the size of the worker pool for handling the application specific score update of peers in a non-blocking way. + ScoreUpdateRequestQueueSize uint32 `validate:"gt=0" mapstructure:"score-update-request-queue-size"` + + // ScoreTTL is the time to live of the application specific score of a peer; the registry keeps a cached copy of the + // application specific score of a peer for this duration. When the duration expires, the application specific score + // of the peer is updated asynchronously. As long as the update is in progress, the cached copy of the application + // specific score of the peer is used even if it is expired. + ScoreTTL time.Duration `validate:"required" mapstructure:"score-ttl"` +} + +const ( + DecayKey = "decay" +) + +type SpamRecordCacheParameters struct { + // CacheSize is size of the cache used to store the spam records of peers. + // The spam records are used to penalize peers that send invalid messages. + CacheSize uint32 `validate:"gt=0" mapstructure:"cache-size"` + Decay SpamRecordCacheDecay `validate:"required" mapstructure:"decay"` +} + const ( - ScoringRegistryKey = "scoring-registry" + PenaltyDecaySlowdownThresholdKey = "penalty-decay-slowdown-threshold" + DecayRateReductionFactorKey = "penalty-decay-rate-reduction-factor" + PenaltyDecayEvaluationPeriodKey = "penalty-decay-evaluation-period" MinimumSpamPenaltyDecayFactorKey = "minimum-spam-penalty-decay-factor" MaximumSpamPenaltyDecayFactorKey = "maximum-spam-penalty-decay-factor" + SkipDecayThresholdKey = "skip-decay-threshold" ) -type ScoringRegistryParameters struct { - MinimumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"minimum-spam-penalty-decay-factor"` - MaximumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"maximum-spam-penalty-decay-factor"` - MisbehaviourPenalties MisbehaviourPenalties `validate:"required" mapstructure:"misbehaviour-penalties"` +type SpamRecordCacheDecay struct { + // PenaltyDecaySlowdownThreshold defines the penalty level which the decay rate is reduced by `DecayRateReductionFactor` every time the penalty of a node falls below the threshold, thereby slowing down the decay process. + // This mechanism ensures that malicious nodes experience longer decay periods, while honest nodes benefit from quicker decay. + PenaltyDecaySlowdownThreshold float64 `validate:"lt=0" mapstructure:"penalty-decay-slowdown-threshold"` + + // DecayRateReductionFactor defines the value by which the decay rate is decreased every time the penalty is below the PenaltyDecaySlowdownThreshold. A reduced decay rate extends the time it takes for penalties to diminish. + DecayRateReductionFactor float64 `validate:"gt=0,lt=1" mapstructure:"penalty-decay-rate-reduction-factor"` + + // PenaltyDecayEvaluationPeriod defines the interval at which the decay for a spam record is okay to be adjusted. + PenaltyDecayEvaluationPeriod time.Duration `validate:"gt=0" mapstructure:"penalty-decay-evaluation-period"` + + SkipDecayThreshold float64 `validate:"gt=-1,lt=0" mapstructure:"skip-decay-threshold"` + + MinimumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"minimum-spam-penalty-decay-factor"` + MaximumSpamPenaltyDecayFactor float64 `validate:"gt=0,lte=1" mapstructure:"maximum-spam-penalty-decay-factor"` } const ( MisbehaviourPenaltiesKey = "misbehaviour-penalties" - SkipDecayThresholdKey = "skip-decay-threshold" GraftMisbehaviourKey = "graft" PruneMisbehaviourKey = "prune" IHaveMisbehaviourKey = "ihave" @@ -24,7 +84,6 @@ const ( ) type MisbehaviourPenalties struct { - SkipDecayThreshold float64 `validate:"gt=-1,lt=0" mapstructure:"skip-decay-threshold"` GraftMisbehaviour float64 `validate:"lt=0" mapstructure:"graft"` PruneMisbehaviour float64 `validate:"lt=0" mapstructure:"prune"` IHaveMisbehaviour float64 `validate:"lt=0" mapstructure:"ihave"` diff --git a/network/p2p/scoring/app_score_test.go b/network/p2p/scoring/app_score_test.go index 8b732c16c13..e373cd6ac0e 100644 --- a/network/p2p/scoring/app_score_test.go +++ b/network/p2p/scoring/app_score_test.go @@ -142,7 +142,7 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi p2ptest.WithRole(flow.RoleAccess), // overrides the default peer scoring parameters to mute GossipSub traffic from/to honest nodes. p2ptest.EnablePeerScoringWithOverride(&p2p.PeerScoringConfigOverride{ - AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring), + AppSpecificScoreParams: maliciousAppSpecificScore(flow.IdentityList{&con1Id, &con2Id}, defaultConfig.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol), }), ) @@ -223,14 +223,14 @@ func TestFullGossipSubConnectivityAmongHonestNodesWithMaliciousMajority(t *testi // maliciousAppSpecificScore returns a malicious app specific penalty function that rewards the malicious node and // punishes the honest nodes. -func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.InternalPeerScoring) func(peer.ID) float64 { +func maliciousAppSpecificScore(honestIds flow.IdentityList, optionCfg p2pconfig.ProtocolLevelGossipSubScoreParams) func(peer.ID) float64 { honestIdProvider := id.NewFixedIdentityProvider(honestIds) return func(p peer.ID) float64 { _, isHonest := honestIdProvider.ByPeerID(p) if isHonest { - return optionCfg.Penalties.MaxAppSpecificPenalty + return optionCfg.AppSpecificScore.MaxAppSpecificPenalty } - return optionCfg.Rewards.MaxAppSpecificReward + return optionCfg.AppSpecificScore.MaxAppSpecificReward } } diff --git a/network/p2p/scoring/decay_test.go b/network/p2p/scoring/decay_test.go index 3abcd3b7ac5..c87c6679ac4 100644 --- a/network/p2p/scoring/decay_test.go +++ b/network/p2p/scoring/decay_test.go @@ -287,7 +287,7 @@ func TestDefaultDecayFunction(t *testing.T) { record: p2p.GossipSubSpamRecord{ Penalty: -100, Decay: 0.8, - LastDecayAdjustment: time.Now().Add(-flowConfig.NetworkConfig.GossipSub.ScoringParameters.SpamRecordCache.PenaltyDecayEvaluationPeriod), + LastDecayAdjustment: time.Now().Add(-flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod), }, lastUpdated: time.Now(), }, @@ -299,14 +299,14 @@ func TestDefaultDecayFunction(t *testing.T) { }, }, } - scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.SpamRecordCache + scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters decayFunc := scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: scoringRegistryConfig.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: scoringRegistryConfig.DecayRateReductionFactor, - DecayAdjustInterval: scoringRegistryConfig.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + SlowerDecayPenaltyThreshold: scoringRegistryConfig.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: scoringRegistryConfig.SpamRecordCache.Decay.DecayRateReductionFactor, + DecayAdjustInterval: scoringRegistryConfig.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, }) for _, tt := range tests { diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index 20151fedc4d..f195cfb8bbe 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -39,7 +39,7 @@ func TestScoreRegistry_FreshStart(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withValidSubscriptions(peerID)) @@ -57,7 +57,7 @@ func TestScoreRegistry_FreshStart(t *testing.T) { require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring.Rewards.MaxAppSpecificReward + maxAppSpecificReward := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore.MaxAppSpecificReward queryTime := time.Now() require.Eventually(t, func() bool { @@ -123,7 +123,7 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withValidSubscriptions(peerID)) @@ -141,7 +141,7 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // eventually, the app specific score should be updated in the cache. require.Eventually(t, func() bool { @@ -149,7 +149,7 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro score := reg.AppSpecificScoreFunc()(peerID) // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - return scoreOptParameters.Rewards.MaxAppSpecificReward == score + return scoreOptParameters.MaxAppSpecificReward == score }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -162,8 +162,8 @@ func testScoreRegistryPeerWithSpamRecord(t *testing.T, messageType p2pmsg.Contro record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. assert.True(t, ok) assert.NoError(t, err) - assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. - assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10. + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -224,7 +224,7 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withUnknownIdentity(peerID), @@ -243,14 +243,14 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // eventually the app specific score should be updated in the cache to the penalty value for unknown identity. require.Eventually(t, func() bool { // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // peer does not have spam record, but has an unknown identity. Hence, the app specific score should be the staking penalty. - return scoreOptParameters.Penalties.UnknownIdentityPenalty == score + return scoreOptParameters.UnknownIdentityPenalty == score }, 5*time.Second, 100*time.Millisecond) // queryTime := time.Now() @@ -264,8 +264,8 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 record, err, ok := spamRecords.Get(peerID) // get the record from the spamRecords. require.True(t, ok) require.NoError(t, err) - require.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. - require.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + require.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) // penalty should be updated to -10, we account for decay. + require.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -275,7 +275,7 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty // and the staking penalty. // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. - return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.Penalties.UnknownIdentityPenalty, score, 0.01) + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.UnknownIdentityPenalty, score, 0.01) }, 5*time.Second, 10*time.Millisecond) // the app specific score should now be updated in the cache. @@ -283,8 +283,8 @@ func testScoreRegistrySpamRecordWithUnknownIdentity(t *testing.T, messageType p2 require.True(t, exists) require.True(t, updated.After(queryTime)) - unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.Penalties.UnknownIdentityPenalty, score, 0.01) - assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.UnknownIdentityPenalty, score, 0.01) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. // stop the registry. cancel() @@ -329,7 +329,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, appScoreCache := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withInvalidSubscriptions(peerID)) @@ -347,7 +347,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp require.Equal(t, time.Time{}, updated) require.Equal(t, float64(0), score) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // peer does not have spam record, but has invalid subscription. Hence, the app specific score should be subscription penalty. // eventually the app specific score should be updated in the cache to the penalty value for subscription penalty. @@ -355,7 +355,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp // calling the app specific score function when there is no app specific score in the cache should eventually update the cache. score := reg.AppSpecificScoreFunc()(peerID) // peer does not have spam record, but has an invalid subscription penalty. - return scoreOptParameters.Penalties.InvalidSubscriptionPenalty == score + return scoreOptParameters.InvalidSubscriptionPenalty == score }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -369,7 +369,7 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // decay should be initialized to the initial state. queryTime := time.Now() // eventually, the app specific score should be updated in the cache. @@ -379,14 +379,14 @@ func testScoreRegistrySpamRecordWithSubscriptionPenalty(t *testing.T, messageTyp // the peer has spam record as well as an unknown identity. Hence, the app specific score should be the spam penalty // and the staking penalty. // As the app specific score in the cache and spam penalty in the spamRecords are updated at different times, we account for 0.1% error. - return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.Penalties.InvalidSubscriptionPenalty, score, 0.01) + return unittest.AreNumericallyClose(expectedPenalty+scoreOptParameters.InvalidSubscriptionPenalty, score, 0.01) }, 5*time.Second, 10*time.Millisecond) // the app specific score should now be updated in the cache. score, updated, exists = appScoreCache.Get(peerID) // get the score from the cache. require.True(t, exists) require.True(t, updated.After(queryTime)) - unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.Penalties.InvalidSubscriptionPenalty, score, 0.01) + unittest.RequireNumericallyClose(t, expectedPenalty+scoreOptParameters.InvalidSubscriptionPenalty, score, 0.01) // stop the registry. cancel() @@ -399,7 +399,7 @@ func TestScoreRegistry_SpamPenaltyDecaysInCache(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, _, _ := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withValidSubscriptions(peerID)) @@ -454,7 +454,7 @@ func TestScoreRegistry_SpamPenaltyDecaysInCache(t *testing.T) { penaltyValueFixtures().PublishMisbehaviour // the lower bound is the sum of the penalties with decay assuming the decay is applied 4 times to the sum of the penalties. // in reality, the decay is applied 4 times to the first penalty, then 3 times to the second penalty, and so on. - r := scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)() + r := scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)() scoreLowerBound := scoreUpperBound * math.Pow(r.Decay, 4) // eventually, the app specific score should be updated in the cache. @@ -479,7 +479,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withValidSubscriptions(peerID), @@ -496,7 +496,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // report a misbehavior for the peer id. reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -521,7 +521,7 @@ func TestScoreRegistry_SpamPenaltyDecayToZero(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should reset back to default staking reward. - return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Rewards.StakedIdentityReward + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.StakedIdentityReward }, 5*time.Second, 100*time.Millisecond) // the penalty should now be zero. @@ -543,7 +543,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withUnknownIdentity(peerID), // the peer id has an unknown identity. withValidSubscriptions(peerID), @@ -560,12 +560,12 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // initially, the app specific score should be the default unknown identity penalty. require.Eventually(t, func() bool { score := reg.AppSpecificScoreFunc()(peerID) - return score == scoreOptParameters.Penalties.UnknownIdentityPenalty + return score == scoreOptParameters.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -583,7 +583,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. // (scoring.DefaultUnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultUnknownIdentityPenalty). - return score < scoreOptParameters.Penalties.UnknownIdentityPenalty && score > scoreOptParameters.Penalties.UnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour + return score < scoreOptParameters.UnknownIdentityPenalty && score > scoreOptParameters.UnknownIdentityPenalty+penaltyValueFixtures().GraftMisbehaviour }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -594,7 +594,7 @@ func TestScoreRegistry_PersistingUnknownIdentityPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Penalties.UnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -616,7 +616,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, withStakedIdentities(peerID), withInvalidSubscriptions(peerID), // the peer id has an invalid subscription. @@ -633,12 +633,12 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // initially, the app specific score should be the default invalid subscription penalty. require.Eventually(t, func() bool { score := reg.AppSpecificScoreFunc()(peerID) - return score == scoreOptParameters.Penalties.InvalidSubscriptionPenalty + return score == scoreOptParameters.InvalidSubscriptionPenalty }, 5*time.Second, 100*time.Millisecond) // report a misbehavior for the peer id. @@ -653,7 +653,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { // Ideally, the score should be the sum of the default invalid subscription penalty and the graft penalty, however, // due to exponential decay of the spam penalty and asynchronous update the app specific score; score should be in the range of [scoring. // (DefaultInvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour, scoring.DefaultInvalidSubscriptionPenalty). - return score < scoreOptParameters.Penalties.InvalidSubscriptionPenalty && score > scoreOptParameters.Penalties.InvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour + return score < scoreOptParameters.InvalidSubscriptionPenalty && score > scoreOptParameters.InvalidSubscriptionPenalty+penaltyValueFixtures().GraftMisbehaviour }, 5*time.Second, 100*time.Millisecond) require.Eventually(t, func() bool { @@ -664,7 +664,7 @@ func TestScoreRegistry_PersistingInvalidSubscriptionPenalty(t *testing.T) { require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the default invalid subscription penalty. - return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.Penalties.UnknownIdentityPenalty + return reg.AppSpecificScoreFunc()(peerID) == scoreOptParameters.UnknownIdentityPenalty }, 5*time.Second, 100*time.Millisecond) // the spam penalty should now be zero in spamRecords. @@ -684,10 +684,10 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond // increase configured DecayRateReductionFactor so that the decay time is increased faster - cfg.NetworkConfig.GossipSub.ScoringParameters.SpamRecordCache.DecayRateReductionFactor = .1 - cfg.NetworkConfig.GossipSub.ScoringParameters.SpamRecordCache.PenaltyDecayEvaluationPeriod = time.Second + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor = .1 + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod = time.Second peer1 := unittest.PeerIdFixture(t) peer2 := unittest.PeerIdFixture(t) @@ -705,18 +705,18 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { assert.False(t, spamRecords.Has(peer1)) assert.False(t, spamRecords.Has(peer2)) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore scoringRegistryParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters // since the both peers do not have a spam record, their app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. require.Eventually(t, func() bool { // when the spam penalty is decayed to zero, the app specific penalty of the node should only contain the unknown identity penalty. - return scoreOptParameters.Rewards.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer1) && scoreOptParameters.Rewards.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer2) + return scoreOptParameters.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer1) && scoreOptParameters.MaxAppSpecificReward == reg.AppSpecificScoreFunc()(peer2) }, 5*time.Second, 100*time.Millisecond) // simulate sustained malicious activity from peer1, eventually the decay speed // for a spam record should be reduced to the MinimumSpamPenaltyDecayFactor - prevDecay := scoringRegistryParameters.MaximumSpamPenaltyDecayFactor + prevDecay := scoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor tolerance := 0.1 require.Eventually(t, func() bool { reg.OnInvalidControlMessageNotification(&p2p.InvCtrlMsgNotif{ @@ -728,7 +728,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { require.True(t, ok) assert.Less(t, math.Abs(prevDecay-record.Decay), tolerance) prevDecay = record.Decay - return record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) // initialize a spam record for peer2 @@ -736,14 +736,14 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { PeerID: peer2, MsgType: p2pmsg.CtrlMsgPrune, }) - // reduce penalty and increase Decay to scoringRegistryParameters.MinimumSpamPenaltyDecayFactor + // reduce penalty and increase Decay to scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor record, err := spamRecords.Update(peer2, func(record p2p.GossipSubSpamRecord) p2p.GossipSubSpamRecord { record.Penalty = -.1 - record.Decay = scoringRegistryParameters.MinimumSpamPenaltyDecayFactor + record.Decay = scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor return record }) require.NoError(t, err) - require.True(t, record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor) + require.True(t, record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor) require.True(t, record.Penalty == -.1) // simulate sustained good behavior from peer 2, each time the spam record is read from the cache // using Get method the record penalty will be decayed until it is eventually reset to @@ -753,7 +753,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { record, err, ok := spamRecords.Get(peer2) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoringRegistryParameters.MaximumSpamPenaltyDecayFactor && + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor && record.Penalty == 0 && record.LastDecayAdjustment.IsZero() }, 5*time.Second, time.Second) @@ -767,7 +767,7 @@ func TestScoreRegistry_TestSpamRecordDecayAdjustment(t *testing.T) { record, err, ok := spamRecords.Get(peer1) require.NoError(t, err) require.True(t, ok) - return record.Decay == scoringRegistryParameters.MinimumSpamPenaltyDecayFactor + return record.Decay == scoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor }, 5*time.Second, 500*time.Millisecond) // stop the registry. @@ -786,7 +786,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) // refresh cached app-specific score every 100 milliseconds to speed up the test. - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond reg, spamRecords, _ := newGossipSubAppSpecificScoreRegistry(t, cfg.NetworkConfig.GossipSub.ScoringParameters, @@ -799,7 +799,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { reg.Start(signalerCtx) unittest.RequireCloseBefore(t, reg.Ready(), 1*time.Second, "failed to start GossipSubAppSpecificScoreRegistry") - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore for _, peerID := range peerIds { // initially, the spamRecords should not have the peer id. @@ -811,7 +811,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { score := reg.AppSpecificScoreFunc()(peerID) // since the peer id does not have a spam record, the app specific score should be the max app specific reward, which // is the default reward for a staked peer that has valid subscriptions. - return score == scoreOptParameters.Rewards.MaxAppSpecificReward + return score == scoreOptParameters.MaxAppSpecificReward }, 5*time.Second, 100*time.Millisecond) } @@ -849,7 +849,7 @@ func TestPeerSpamPenaltyClusterPrefixed(t *testing.T) { assert.True(t, ok) assert.NoError(t, err) assert.Less(t, math.Abs(expectedPenalty-record.Penalty), 10e-3) - assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) + assert.Equal(t, scoring.InitAppScoreRecordStateFunc(cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor)().Decay, record.Decay) // this peer has a spam record, with no subscription penalty. Hence, the app specific score should only be the spam penalty, // and the peer should be deprived of the default reward for its valid staked role. score := reg.AppSpecificScoreFunc()(peerID) @@ -958,12 +958,12 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring unittest.Logger(), metrics.NewNoopCollector(), scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: params.SpamRecordCache.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: params.SpamRecordCache.DecayRateReductionFactor, - DecayAdjustInterval: params.SpamRecordCache.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: params.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + SlowerDecayPenaltyThreshold: params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, + DecayAdjustInterval: params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: params.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, })) appSpecificScoreCache := internal.NewAppSpecificScoreCache(100, unittest.Logger(), metrics.NewNoopCollector()) @@ -978,7 +978,7 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring validator.On("Done").Return(f()).Maybe() cfg := &scoring.GossipSubAppSpecificScoreRegistryConfig{ Logger: unittest.Logger(), - Init: scoring.InitAppScoreRecordStateFunc(params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor), + Init: scoring.InitAppScoreRecordStateFunc(params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), Penalty: penaltyValueFixtures(), IdProvider: mock.NewIdentityProvider(t), Validator: validator, @@ -988,13 +988,13 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { return cache }, - Parameters: params.AppSpecificScore, + Parameters: params.ScoringRegistryParameters.AppSpecificScore, HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), NetworkingType: network.PrivateNetwork, - UnknownIdentityPenalty: params.InternalPeerScoring.Penalties.UnknownIdentityPenalty, - MinAppSpecificPenalty: params.InternalPeerScoring.Penalties.MinAppSpecificPenalty, - StakedIdentityReward: params.InternalPeerScoring.Rewards.StakedIdentityReward, - InvalidSubscriptionPenalty: params.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, + UnknownIdentityPenalty: params.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, + MinAppSpecificPenalty: params.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, + StakedIdentityReward: params.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, + InvalidSubscriptionPenalty: params.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, } for _, opt := range opts { opt(cfg) @@ -1017,7 +1017,6 @@ func penaltyValueFixtures() p2pconfig.MisbehaviourPenalties { IWantMisbehaviour: -10, ClusterPrefixedReductionFactor: .5, PublishMisbehaviour: -10, - SkipDecayThreshold: -0.1, } } diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index 9a20167d460..538725b3b1d 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -96,7 +96,7 @@ func (c *ScoreOptionConfig) SetRegisterNotificationConsumerFunc(f func(p2p.Gossi // NewScoreOption creates a new penalty option with the given configuration. func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) (*ScoreOption, error) { - throttledSampler := logging.BurstSampler(cfg.params.InternalPeerScoring.MaxDebugLogs, time.Second) + throttledSampler := logging.BurstSampler(cfg.params.PeerScoring.Protocol.MaxDebugLogs, time.Second) logger := cfg.logger.With(). Str("module", "pubsub_score_option"). Logger(). @@ -110,31 +110,31 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( Logger: logger, Penalty: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties, Validator: validator, - Init: InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor), + Init: InitAppScoreRecordStateFunc(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), IdProvider: cfg.provider, HeroCacheMetricsFactory: cfg.heroCacheMetricsFactory, AppScoreCacheFactory: func() p2p.GossipSubApplicationSpecificScoreCache { collector := metrics.NewGossipSubApplicationSpecificScoreCacheMetrics(cfg.heroCacheMetricsFactory, cfg.networkingType) - return internal.NewAppSpecificScoreCache(cfg.params.SpamRecordCache.CacheSize, cfg.logger, collector) + return internal.NewAppSpecificScoreCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector) }, SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) - return netcache.NewGossipSubSpamRecordCache(cfg.params.SpamRecordCache.CacheSize, cfg.logger, collector, + return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, DefaultDecayFunction(&DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: cfg.params.SpamRecordCache.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: cfg.params.SpamRecordCache.DecayRateReductionFactor, - DecayAdjustInterval: cfg.params.SpamRecordCache.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: cfg.params.ScoringRegistryParameters.MisbehaviourPenalties.SkipDecayThreshold, + SlowerDecayPenaltyThreshold: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, + DecayRateReductionFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, + DecayAdjustInterval: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, + MaximumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, + MinimumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, + SkipDecayThreshold: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, })) }, - Parameters: cfg.params.AppSpecificScore, + Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, NetworkingType: cfg.networkingType, - UnknownIdentityPenalty: cfg.params.InternalPeerScoring.Penalties.UnknownIdentityPenalty, - MinAppSpecificPenalty: cfg.params.InternalPeerScoring.Penalties.MinAppSpecificPenalty, - StakedIdentityReward: cfg.params.InternalPeerScoring.Rewards.StakedIdentityReward, - InvalidSubscriptionPenalty: cfg.params.InternalPeerScoring.Penalties.InvalidSubscriptionPenalty, + UnknownIdentityPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, + MinAppSpecificPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, + StakedIdentityReward: cfg.params.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, + InvalidSubscriptionPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, }) if err != nil { return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) @@ -147,44 +147,44 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( Topics: make(map[string]*pubsub.TopicScoreParams), // we don't set all the parameters, so we skip the atomic validation. // atomic validation fails initialization if any parameter is not set. - SkipAtomicValidation: cfg.params.InternalPeerScoring.TopicValidation.SkipAtomicValidation, + SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, // DecayInterval is the interval over which we decay the effect of past behavior, so that // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval // that GossipSub uses to refresh the scores of all peers. - DecayInterval: cfg.params.InternalPeerScoring.DecayInterval, + DecayInterval: cfg.params.DecayInterval, // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. // This is to prevent the counter from decaying to a very small value. // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior // for a long time, and we can reset the counter. - DecayToZero: cfg.params.InternalPeerScoring.DecayToZero, + DecayToZero: cfg.params.PeerScoring.Internal.DecayToZero, // AppSpecificWeight is the weight of the application specific penalty. - AppSpecificWeight: cfg.params.InternalPeerScoring.AppSpecificScoreWeight, + AppSpecificWeight: cfg.params.PeerScoring.Internal.AppSpecificScoreWeight, // PenaltyThreshold is the threshold above which a peer is penalized for GossipSub-level misbehaviors. - BehaviourPenaltyThreshold: cfg.params.InternalPeerScoring.Behaviour.PenaltyThreshold, + BehaviourPenaltyThreshold: cfg.params.PeerScoring.Internal.Behaviour.PenaltyThreshold, // PenaltyWeight is the weight of the GossipSub-level penalty. - BehaviourPenaltyWeight: cfg.params.InternalPeerScoring.Behaviour.PenaltyWeight, + BehaviourPenaltyWeight: cfg.params.PeerScoring.Internal.Behaviour.PenaltyWeight, // PenaltyDecay is the decay of the GossipSub-level penalty (applied every decay interval). - BehaviourPenaltyDecay: cfg.params.InternalPeerScoring.Behaviour.PenaltyDecay, + BehaviourPenaltyDecay: cfg.params.PeerScoring.Internal.Behaviour.PenaltyDecay, }, peerThresholdParams: &pubsub.PeerScoreThresholds{ - GossipThreshold: cfg.params.InternalPeerScoring.Thresholds.Gossip, - PublishThreshold: cfg.params.InternalPeerScoring.Thresholds.Publish, - GraylistThreshold: cfg.params.InternalPeerScoring.Thresholds.Graylist, - AcceptPXThreshold: cfg.params.InternalPeerScoring.Thresholds.AcceptPX, - OpportunisticGraftThreshold: cfg.params.InternalPeerScoring.Thresholds.OpportunisticGraft, + GossipThreshold: cfg.params.PeerScoring.Internal.Thresholds.Gossip, + PublishThreshold: cfg.params.PeerScoring.Internal.Thresholds.Publish, + GraylistThreshold: cfg.params.PeerScoring.Internal.Thresholds.Graylist, + AcceptPXThreshold: cfg.params.PeerScoring.Internal.Thresholds.AcceptPX, + OpportunisticGraftThreshold: cfg.params.PeerScoring.Internal.Thresholds.OpportunisticGraft, }, defaultTopicScoreParams: &pubsub.TopicScoreParams{ - TopicWeight: cfg.params.InternalPeerScoring.TopicValidation.TopicWeight, - SkipAtomicValidation: cfg.params.InternalPeerScoring.TopicValidation.SkipAtomicValidation, - InvalidMessageDeliveriesWeight: cfg.params.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesWeight, - InvalidMessageDeliveriesDecay: cfg.params.InternalPeerScoring.TopicValidation.InvalidMessageDeliveriesDecay, - TimeInMeshQuantum: cfg.params.InternalPeerScoring.TopicValidation.TimeInMeshQuantum, - MeshMessageDeliveriesWeight: cfg.params.InternalPeerScoring.TopicValidation.MeshDeliveriesWeight, - MeshMessageDeliveriesDecay: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesDecay, - MeshMessageDeliveriesCap: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesCap, - MeshMessageDeliveriesThreshold: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveryThreshold, - MeshMessageDeliveriesWindow: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveriesWindow, - MeshMessageDeliveriesActivation: cfg.params.InternalPeerScoring.TopicValidation.MeshMessageDeliveryActivation, + TopicWeight: cfg.params.PeerScoring.Internal.TopicParameters.TopicWeight, + SkipAtomicValidation: cfg.params.PeerScoring.Internal.TopicParameters.SkipAtomicValidation, + InvalidMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesWeight, + InvalidMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.InvalidMessageDeliveriesDecay, + TimeInMeshQuantum: cfg.params.PeerScoring.Internal.TopicParameters.TimeInMeshQuantum, + MeshMessageDeliveriesWeight: cfg.params.PeerScoring.Internal.TopicParameters.MeshDeliveriesWeight, + MeshMessageDeliveriesDecay: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesDecay, + MeshMessageDeliveriesCap: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesCap, + MeshMessageDeliveriesThreshold: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryThreshold, + MeshMessageDeliveriesWindow: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveriesWindow, + MeshMessageDeliveriesActivation: cfg.params.PeerScoring.Internal.TopicParameters.MeshMessageDeliveryActivation, }, appScoreFunc: scoreRegistry.AppSpecificScoreFunc(), } diff --git a/network/p2p/scoring/scoring_test.go b/network/p2p/scoring/scoring_test.go index b71c662a300..cee819d3c85 100644 --- a/network/p2p/scoring/scoring_test.go +++ b/network/p2p/scoring/scoring_test.go @@ -104,7 +104,7 @@ func TestInvalidCtrlMsgScoringIntegration(t *testing.T) { cfg, err := config.DefaultConfig() require.NoError(t, err) - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond // speed up the test + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond // speed up the test node1, id1 := p2ptest.NodeFixture( t, diff --git a/network/p2p/scoring/subscription_validator_test.go b/network/p2p/scoring/subscription_validator_test.go index b4edad1f04c..022e2789377 100644 --- a/network/p2p/scoring/subscription_validator_test.go +++ b/network/p2p/scoring/subscription_validator_test.go @@ -171,7 +171,7 @@ func TestSubscriptionValidator_Integration(t *testing.T) { require.NoError(t, err) // set a low update interval to speed up the test cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond sporkId := unittest.IdentifierFixture() From 6a0676a0fa214519f2254944e9419acdc4839467 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 14:15:21 -0500 Subject: [PATCH 14/19] remove DecayFunctionConfig --- network/p2p/scoring/decay_test.go | 9 +-------- network/p2p/scoring/registry.go | 15 +++------------ network/p2p/scoring/registry_test.go | 9 +-------- network/p2p/scoring/score_option.go | 9 +-------- 4 files changed, 6 insertions(+), 36 deletions(-) diff --git a/network/p2p/scoring/decay_test.go b/network/p2p/scoring/decay_test.go index c87c6679ac4..643d22fba83 100644 --- a/network/p2p/scoring/decay_test.go +++ b/network/p2p/scoring/decay_test.go @@ -300,14 +300,7 @@ func TestDefaultDecayFunction(t *testing.T) { }, } scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters - decayFunc := scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: scoringRegistryConfig.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: scoringRegistryConfig.SpamRecordCache.Decay.DecayRateReductionFactor, - DecayAdjustInterval: scoringRegistryConfig.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, - }) + decayFunc := scoring.DefaultDecayFunction(scoringRegistryConfig.SpamRecordCache.Decay) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/network/p2p/scoring/registry.go b/network/p2p/scoring/registry.go index 38a9584b4cc..8e77f474ef4 100644 --- a/network/p2p/scoring/registry.go +++ b/network/p2p/scoring/registry.go @@ -407,19 +407,10 @@ func (r *GossipSubAppSpecificScoreRegistry) OnInvalidControlMessageNotification( Msg("applied misbehaviour penalty and updated application specific penalty") } -type DecayFunctionConfig struct { - SlowerDecayPenaltyThreshold float64 - DecayRateReductionFactor float64 - DecayAdjustInterval time.Duration - MaximumSpamPenaltyDecayFactor float64 - MinimumSpamPenaltyDecayFactor float64 - SkipDecayThreshold float64 -} - // DefaultDecayFunction is the default decay function that is used to decay the application specific penalty of a peer. // It is used if no decay function is provided in the configuration. // It decays the application specific penalty of a peer if it is negative. -func DefaultDecayFunction(cfg *DecayFunctionConfig) netcache.PreprocessorFunc { +func DefaultDecayFunction(cfg p2pconfig.SpamRecordCacheDecay) netcache.PreprocessorFunc { return func(record p2p.GossipSubSpamRecord, lastUpdated time.Time) (p2p.GossipSubSpamRecord, error) { if record.Penalty >= 0 { // no need to decay the penalty if it is positive, the reason is currently the app specific penalty @@ -443,8 +434,8 @@ func DefaultDecayFunction(cfg *DecayFunctionConfig) netcache.PreprocessorFunc { } record.Penalty = penalty - if record.Penalty <= cfg.SlowerDecayPenaltyThreshold { - if time.Since(record.LastDecayAdjustment) > cfg.DecayAdjustInterval || record.LastDecayAdjustment.IsZero() { + if record.Penalty <= cfg.PenaltyDecaySlowdownThreshold { + if time.Since(record.LastDecayAdjustment) > cfg.PenaltyDecayEvaluationPeriod || record.LastDecayAdjustment.IsZero() { // reduces the decay speed flooring at MinimumSpamRecordDecaySpeed record.Decay = math.Min(record.Decay+cfg.DecayRateReductionFactor, cfg.MinimumSpamPenaltyDecayFactor) record.LastDecayAdjustment = time.Now() diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index f195cfb8bbe..0e5864423d1 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -957,14 +957,7 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), - scoring.DefaultDecayFunction(&scoring.DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, - DecayAdjustInterval: params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: params.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: params.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, - })) + scoring.DefaultDecayFunction(params.ScoringRegistryParameters.SpamRecordCache.Decay)) appSpecificScoreCache := internal.NewAppSpecificScoreCache(100, unittest.Logger(), metrics.NewNoopCollector()) validator := mockp2p.NewSubscriptionValidator(t) diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index 538725b3b1d..c8ef6e2e78f 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -120,14 +120,7 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { collector := metrics.GossipSubSpamRecordCacheMetricsFactory(cfg.heroCacheMetricsFactory, cfg.networkingType) return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, - DefaultDecayFunction(&DecayFunctionConfig{ - SlowerDecayPenaltyThreshold: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecaySlowdownThreshold, - DecayRateReductionFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.DecayRateReductionFactor, - DecayAdjustInterval: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod, - MaximumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor, - MinimumSpamPenaltyDecayFactor: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.MinimumSpamPenaltyDecayFactor, - SkipDecayThreshold: cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay.SkipDecayThreshold, - })) + DefaultDecayFunction(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay)) }, Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, NetworkingType: cfg.networkingType, From 933acf424568a21cdded32036691b04193878f9c Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 14:20:55 -0500 Subject: [PATCH 15/19] use p2pconfig.ApplicationSpecificScoreParameters directly --- network/p2p/scoring/registry.go | 17 +++++------------ network/p2p/scoring/registry_test.go | 11 ++++------- network/p2p/scoring/score_option.go | 9 +++------ 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/network/p2p/scoring/registry.go b/network/p2p/scoring/registry.go index 8e77f474ef4..2fc129e0ce3 100644 --- a/network/p2p/scoring/registry.go +++ b/network/p2p/scoring/registry.go @@ -101,14 +101,7 @@ type GossipSubAppSpecificScoreRegistryConfig struct { NetworkingType network.NetworkingType `validate:"required"` - // UnknownIdentityPenalty This is the penalty for unknown identity. - UnknownIdentityPenalty float64 `validate:"required"` - // MinAppSpecificPenalty This is the minimum penalty for severe offenses that we apply to a remote node score - MinAppSpecificPenalty float64 `validate:"required"` - // StakedIdentityReward This is the reward for staking peers. - StakedIdentityReward float64 `validate:"required"` - // InvalidSubscriptionPenalty This is the penalty for invalid subscription. - InvalidSubscriptionPenalty float64 `validate:"required"` + AppSpecificScoreParams p2pconfig.ApplicationSpecificScoreParameters `validate:"required"` } // NewGossipSubAppSpecificScoreRegistry returns a new GossipSubAppSpecificScoreRegistry. @@ -140,10 +133,10 @@ func NewGossipSubAppSpecificScoreRegistry(config *GossipSubAppSpecificScoreRegis validator: config.Validator, idProvider: config.IdProvider, scoreTTL: config.Parameters.ScoreTTL, - unknownIdentityPenalty: config.UnknownIdentityPenalty, - minAppSpecificPenalty: config.MinAppSpecificPenalty, - stakedIdentityReward: config.StakedIdentityReward, - invalidSubscriptionPenalty: config.InvalidSubscriptionPenalty, + unknownIdentityPenalty: config.AppSpecificScoreParams.UnknownIdentityPenalty, + minAppSpecificPenalty: config.AppSpecificScoreParams.MinAppSpecificPenalty, + stakedIdentityReward: config.AppSpecificScoreParams.StakedIdentityReward, + invalidSubscriptionPenalty: config.AppSpecificScoreParams.InvalidSubscriptionPenalty, } reg.appScoreUpdateWorkerPool = worker.NewWorkerPoolBuilder[peer.ID](lg.With().Str("component", "app_specific_score_update_worker_pool").Logger(), diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index 0e5864423d1..ffdda004885 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -981,13 +981,10 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, params p2pconfig.Scoring SpamRecordCacheFactory: func() p2p.GossipSubSpamRecordCache { return cache }, - Parameters: params.ScoringRegistryParameters.AppSpecificScore, - HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), - NetworkingType: network.PrivateNetwork, - UnknownIdentityPenalty: params.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, - MinAppSpecificPenalty: params.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, - StakedIdentityReward: params.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, - InvalidSubscriptionPenalty: params.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, + Parameters: params.ScoringRegistryParameters.AppSpecificScore, + HeroCacheMetricsFactory: metrics.NewNoopHeroCacheMetricsFactory(), + NetworkingType: network.PrivateNetwork, + AppSpecificScoreParams: params.PeerScoring.Protocol.AppSpecificScore, } for _, opt := range opts { opt(cfg) diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index c8ef6e2e78f..0320b21b042 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -122,12 +122,9 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( return netcache.NewGossipSubSpamRecordCache(cfg.params.ScoringRegistryParameters.SpamRecordCache.CacheSize, cfg.logger, collector, DefaultDecayFunction(cfg.params.ScoringRegistryParameters.SpamRecordCache.Decay)) }, - Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, - NetworkingType: cfg.networkingType, - UnknownIdentityPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.UnknownIdentityPenalty, - MinAppSpecificPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.MinAppSpecificPenalty, - StakedIdentityReward: cfg.params.PeerScoring.Protocol.AppSpecificScore.StakedIdentityReward, - InvalidSubscriptionPenalty: cfg.params.PeerScoring.Protocol.AppSpecificScore.InvalidSubscriptionPenalty, + Parameters: cfg.params.ScoringRegistryParameters.AppSpecificScore, + NetworkingType: cfg.networkingType, + AppSpecificScoreParams: cfg.params.PeerScoring.Protocol.AppSpecificScore, }) if err != nil { return nil, fmt.Errorf("failed to create gossipsub app specific score registry: %w", err) From 872583b331ccca28f4aa56bd61fbd38cd172805b Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 14:33:12 -0500 Subject: [PATCH 16/19] Update gossipSubScoreTracer_test.go --- network/p2p/tracer/gossipSubScoreTracer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/p2p/tracer/gossipSubScoreTracer_test.go b/network/p2p/tracer/gossipSubScoreTracer_test.go index d12de3e634a..dab069024c9 100644 --- a/network/p2p/tracer/gossipSubScoreTracer_test.go +++ b/network/p2p/tracer/gossipSubScoreTracer_test.go @@ -80,7 +80,7 @@ func TestGossipSubScoreTracer(t *testing.T) { cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second // the libp2p node updates the subscription list as well as the app-specific score every 10 milliseconds (for testing purposes) cfg.NetworkConfig.GossipSub.SubscriptionProvider.UpdateInterval = 10 * time.Millisecond - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 10 * time.Millisecond tracerNode, tracerId := p2ptest.NodeFixture( t, sporkId, From c20968322aed79d8e299eb8cc6b97453d693bcfa Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 14:59:09 -0500 Subject: [PATCH 17/19] fix lint --- .../validation_inspector_test.go | 6 +-- .../test/gossipsub/scoring/scoring_test.go | 46 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go index 248432c5afa..6a3e168f0f4 100644 --- a/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go +++ b/insecure/integration/functional/test/gossipsub/rpc_inspector/validation_inspector_test.go @@ -1023,7 +1023,7 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { require.NoError(t, err) // set the scoring parameters to be more aggressive to speed up the test cfg.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 100 * time.Millisecond - cfg.NetworkConfig.GossipSub.ScoringParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond + cfg.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.AppSpecificScore.ScoreTTL = 100 * time.Millisecond victimNode, victimId := p2ptest.NodeFixture(t, sporkID, t.Name(), @@ -1104,11 +1104,11 @@ func TestGossipSubSpamMitigationIntegration(t *testing.T) { spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsWithMalformedTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsInvalidSporkIDTopic) spammer.SpamControlMessage(t, victimNode, pruneCtlMsgsDuplicateTopic) - scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreOptParameters := cfg.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds // wait for three GossipSub heartbeat intervals to ensure that the victim node has penalized the spammer node. require.Eventually(t, func() bool { score, ok := victimNode.PeerScoreExposer().GetScore(spammer.SpammerNode.ID()) - return ok && score < 2*scoreOptParameters.Thresholds.Graylist + return ok && score < 2*scoreOptParameters.Graylist }, 5*time.Second, 100*time.Millisecond, "expected victim node to penalize spammer node") // now we expect the detection and mitigation to kick in and the victim node to disconnect from the spammer node. diff --git a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go index 782dea22fd3..20119f619a0 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go @@ -151,15 +151,15 @@ func testGossipSubInvalidMessageDeliveryScoring(t *testing.T, spamMsgFactory fun if !ok { return false } - if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Gossip { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Gossip { // ensure the score is low enough so that no gossip is routed by victim node to spammer node. return false } - if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Publish { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Publish { // ensure the score is low enough so that non of the published messages of the victim node are routed to the spammer node. return false } - if spammerScore >= scoreParams.InternalPeerScoring.Thresholds.Graylist { + if spammerScore >= scoreParams.PeerScoring.Internal.Thresholds.Graylist { // ensure the score is low enough so that the victim node does not accept RPC messages from the spammer node. return false } @@ -257,7 +257,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { return unittest.ProposalFixture() }) - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol // Also initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked @@ -267,7 +267,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.AppSpecificScore.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -282,17 +282,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.96*scoreParams.Rewards.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.96*scoreParams.AppSpecificScore.MaxAppSpecificReward { // score must be penalized by -0.05 * MaxAppSpecificReward. // 0.96 is to account for floating point errors. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -373,7 +373,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { } } - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // Initially the under-performing node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and the under-performing node is considered staked @@ -383,7 +383,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { + if underPerformingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -399,17 +399,17 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { if !ok { return false } - if underPerformingNodeScore > 0.91*scoreParams.Rewards.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. + if underPerformingNodeScore > 0.91*scoreParams.MaxAppSpecificReward { // score must be penalized by ~ 2 * -0.05 * MaxAppSpecificReward. // 0.91 is to account for the floating point errors. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { // even the node is slightly penalized, it should still be able to gossip with this node. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { // even the node is slightly penalized, it should still be able to publish to this node. return false } - if underPerformingNodeScore < scoreParams.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. + if underPerformingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { // even the node is slightly penalized, it should still be able to establish rpc connection with this node. return false } @@ -485,7 +485,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return unittest.ProposalFixture() }) - scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring + scoreParams := conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Protocol.AppSpecificScore // Initially the replaying node should have a score that is at least equal to the MaxAppSpecificReward. // The reason is in our scoring system, we reward the staked nodes by MaxAppSpecificReward, and initially every node is considered staked @@ -496,7 +496,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -523,7 +523,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { if !ok { return false } - if replayingNodeScore < scoreParams.Rewards.MaxAppSpecificReward { + if replayingNodeScore < scoreParams.MaxAppSpecificReward { // ensure the score is high enough so that gossip is routed by victim node to spammer node. return false } @@ -563,22 +563,22 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { return false } - if replayingNodeScore >= scoreParams.Rewards.MaxAppSpecificReward { + if replayingNodeScore >= scoreParams.MaxAppSpecificReward { // node must be penalized for just replaying the same messages. return false } // following if-statements check that even though the node is penalized, it is not penalized too much, and // can still participate in the network. We don't desire to disallow list a node for just under-performing. - if replayingNodeScore < scoreParams.Thresholds.Gossip { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Gossip { return false } - if replayingNodeScore < scoreParams.Thresholds.Publish { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Publish { return false } - if replayingNodeScore < scoreParams.Thresholds.Graylist { + if replayingNodeScore < conf.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.Thresholds.Graylist { return false } @@ -596,7 +596,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { func defaultTopicScoreParams(t *testing.T) *pubsub.TopicScoreParams { defaultConfig, err := config.DefaultConfig() require.NoError(t, err) - topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.InternalPeerScoring.TopicValidation + topicScoreParams := defaultConfig.NetworkConfig.GossipSub.ScoringParameters.PeerScoring.Internal.TopicParameters p := &pubsub.TopicScoreParams{ TopicWeight: topicScoreParams.TopicWeight, SkipAtomicValidation: topicScoreParams.SkipAtomicValidation, From b11255451e2f0bf8909462d8c63587db53022a97 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 15:18:57 -0500 Subject: [PATCH 18/19] remove redundant decay interval --- config/default-config.yml | 2 -- .../functional/test/gossipsub/scoring/ihave_spam_test.go | 2 +- .../functional/test/gossipsub/scoring/scoring_test.go | 6 +++--- network/netconf/flags.go | 5 ----- network/p2p/config/gossipsub.go | 2 -- network/p2p/scoring/score_option.go | 8 ++++---- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/config/default-config.yml b/config/default-config.yml index 32658739807..04e681ebe1c 100644 --- a/config/default-config.yml +++ b/config/default-config.yml @@ -199,8 +199,6 @@ network-config: # Peer scoring is the default value for enabling peer scoring peer-scoring-enabled: true scoring-parameters: - # the intervals at which counters associated with a peer behavior in gossipsub system are decayed. - decay-interval: 1m peer-scoring: internal: # The weight for app-specific scores. 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 e9959977b55..82043f109a4 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/ihave_spam_test.go @@ -205,7 +205,7 @@ func TestGossipSubIHaveBrokenPromises_Above_Threshold(t *testing.T) { conf.NetworkConfig.GossipSub.RpcInspector.Validation.IHave.MaxSampleSize = 10000 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.ScoringParameters.PeerScoring.Internal.DecayInterval = 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 diff --git a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go index 20119f619a0..902b698aa2d 100644 --- a/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go +++ b/insecure/integration/functional/test/gossipsub/scoring/scoring_test.go @@ -216,7 +216,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_SingleTopic(t *testing.T) { conf, err := config.DefaultConfig() 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.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, @@ -328,7 +328,7 @@ func TestGossipSubMeshDeliveryScoring_UnderDelivery_TwoTopics(t *testing.T) { conf, err := config.DefaultConfig() 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.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second thisNode, thisId := p2ptest.NodeFixture( // this node is the one that will be penalizing the under-performer node. t, @@ -442,7 +442,7 @@ func TestGossipSubMeshDeliveryScoring_Replay_Will_Not_Counted(t *testing.T) { conf, err := config.DefaultConfig() 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.ScoringParameters.PeerScoring.Internal.DecayInterval = 1 * time.Second conf.NetworkConfig.GossipSub.RpcTracer.ScoreTracerInterval = 1 * time.Second blockTopicOverrideParams := defaultTopicScoreParams(t) blockTopicOverrideParams.MeshMessageDeliveriesActivation = 1 * time.Second // we start observing the mesh message deliveries after 1 second of the node startup. diff --git a/network/netconf/flags.go b/network/netconf/flags.go index 9dbd4703f43..b0506f6d99f 100644 --- a/network/netconf/flags.go +++ b/network/netconf/flags.go @@ -102,8 +102,6 @@ func AllFlagNames() []string { BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.UpdateIntervalKey), BuildFlagName(gossipsubKey, p2pconfig.SubscriptionProviderKey, p2pconfig.CacheSizeKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), - BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.AppSpecificScoreWeightKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayToZeroKey), @@ -301,9 +299,6 @@ func InitializeNetworkFlags(flags *pflag.FlagSet, config *Config) { config.GossipSub.SubscriptionProvider.CacheSize, "size of the cache that keeps the list of topics each peer has subscribed to, recommended size is 10x the number of authorized nodes") - flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.DecayIntervalKey), - config.GossipSub.ScoringParameters.DecayInterval, - "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") flags.Duration(BuildFlagName(gossipsubKey, p2pconfig.ScoreParamsKey, p2pconfig.PeerScoringKey, p2pconfig.InternalKey, p2pconfig.DecayIntervalKey), config.GossipSub.ScoringParameters.PeerScoring.Internal.DecayInterval, "interval at which the counters associated with a peer behavior in GossipSub system are decayed, recommended value is one minute") diff --git a/network/p2p/config/gossipsub.go b/network/p2p/config/gossipsub.go index f1edd8426d8..31b69dd221b 100644 --- a/network/p2p/config/gossipsub.go +++ b/network/p2p/config/gossipsub.go @@ -85,8 +85,6 @@ const ( // ScoringParameters are the parameters for the score option. // Parameters are "numerical values" that are used to compute or build components that compute the score of a peer in GossipSub system. type ScoringParameters struct { - // DecayInterval is the interval at which the counters associated with a peer behavior in GossipSub system are decayed. - DecayInterval time.Duration `validate:"gt=0s" mapstructure:"decay-interval"` PeerScoring PeerScoringParameters `validate:"required" mapstructure:"peer-scoring"` ScoringRegistryParameters ScoringRegistryParameters `validate:"required" mapstructure:"scoring-registry"` } diff --git a/network/p2p/scoring/score_option.go b/network/p2p/scoring/score_option.go index 19507575645..07b948d975e 100644 --- a/network/p2p/scoring/score_option.go +++ b/network/p2p/scoring/score_option.go @@ -141,7 +141,7 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( // DecayInterval is the interval over which we decay the effect of past behavior, so that // a good or bad behavior will not have a permanent effect on the penalty. It is also the interval // that GossipSub uses to refresh the scores of all peers. - DecayInterval: cfg.params.DecayInterval, + DecayInterval: cfg.params.PeerScoring.Internal.DecayInterval, // DecayToZero defines the maximum value below which a peer scoring counter is reset to zero. // This is to prevent the counter from decaying to a very small value. // When a counter hits the DecayToZero threshold, it means that the peer did not exhibit the behavior @@ -189,13 +189,13 @@ func NewScoreOption(cfg *ScoreOptionConfig, provider p2p.SubscriptionProvider) ( Msg("app specific score function is overridden, should never happen in production") } - if cfg.params.DecayInterval > 0 && cfg.params.DecayInterval != s.peerScoreParams.DecayInterval { + if cfg.params.PeerScoring.Internal.DecayInterval > 0 && cfg.params.PeerScoring.Internal.DecayInterval != s.peerScoreParams.DecayInterval { // overrides the default decay interval if the decay interval is set. - s.peerScoreParams.DecayInterval = cfg.params.DecayInterval + s.peerScoreParams.DecayInterval = cfg.params.PeerScoring.Internal.DecayInterval s.logger. Warn(). Str(logging.KeyNetworkingSecurity, "true"). - Dur("decay_interval_ms", cfg.params.DecayInterval). + Dur("decay_interval_ms", cfg.params.PeerScoring.Internal.DecayInterval). Msg("decay interval is overridden, should never happen in production") } From fa090c09a7f2ada495a4754b70e6d02d71b2d5b4 Mon Sep 17 00:00:00 2001 From: Khalil Claybon Date: Fri, 12 Jan 2024 17:17:59 -0500 Subject: [PATCH 19/19] quarantine flakey test TestSubscriptionValidator_Integration --- network/p2p/scoring/registry_test.go | 2 +- network/p2p/scoring/subscription_validator_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/network/p2p/scoring/registry_test.go b/network/p2p/scoring/registry_test.go index e1e78d25247..18650658eb2 100644 --- a/network/p2p/scoring/registry_test.go +++ b/network/p2p/scoring/registry_test.go @@ -978,7 +978,7 @@ func newGossipSubAppSpecificScoreRegistry(t *testing.T, cache := netcache.NewGossipSubSpamRecordCache(100, unittest.Logger(), metrics.NewNoopCollector(), - scoring.InitAppScoreRecordStateFunc(params.ScoringRegistryParameters.SpamRecordCache.Decay.MaximumSpamPenaltyDecayFactor), + initFunction, scoring.DefaultDecayFunction(params.ScoringRegistryParameters.SpamRecordCache.Decay)) appSpecificScoreCache := internal.NewAppSpecificScoreCache(100, unittest.Logger(), metrics.NewNoopCollector()) diff --git a/network/p2p/scoring/subscription_validator_test.go b/network/p2p/scoring/subscription_validator_test.go index 022e2789377..1a6a4b6bfcb 100644 --- a/network/p2p/scoring/subscription_validator_test.go +++ b/network/p2p/scoring/subscription_validator_test.go @@ -164,6 +164,7 @@ func TestSubscriptionValidator_InvalidSubscriptions(t *testing.T) { // 4. Verification node also publishes a chunk request on the RequestChunks channel. // 5. Test checks that consensus node does not receive the chunk request while the other verification node does. func TestSubscriptionValidator_Integration(t *testing.T) { + unittest.SkipUnless(t, unittest.TEST_FLAKY, "flaky test") ctx, cancel := context.WithCancel(context.Background()) signalerCtx := irrecoverable.NewMockSignalerContext(t, ctx)