diff --git a/score_params.go b/score_params.go index 827f0456..99c9526d 100644 --- a/score_params.go +++ b/score_params.go @@ -10,7 +10,10 @@ import ( ) type PeerScoreThresholds struct { - // GossipThreshold is the score threshold below which gossip propagation is supressed; + // whether it is allowed to just set some params and not all of them. + SkipAtomicValidation bool + + // GossipThreshold is the score threshold below which gossip propagation is suppressed; // should be negative. GossipThreshold float64 @@ -18,8 +21,8 @@ type PeerScoreThresholds struct { // publishing (also applies to fanout and floodsub peers); should be negative and <= GossipThreshold. PublishThreshold float64 - // GraylistThreshold is the score threshold below which message processing is supressed altogether, - // implementing an effective graylist according to peer score; should be negative and <= PublisThreshold. + // GraylistThreshold is the score threshold below which message processing is suppressed altogether, + // implementing an effective gray list according to peer score; should be negative and <= PublishThreshold. GraylistThreshold float64 // AcceptPXThreshold is the score threshold below which PX will be ignored; this should be positive @@ -32,25 +35,38 @@ type PeerScoreThresholds struct { } func (p *PeerScoreThresholds) validate() error { - if p.GossipThreshold > 0 || isInvalidNumber(p.GossipThreshold) { - return fmt.Errorf("invalid gossip threshold; it must be <= 0 and a valid number") - } - if p.PublishThreshold > 0 || p.PublishThreshold > p.GossipThreshold || isInvalidNumber(p.PublishThreshold) { - return fmt.Errorf("invalid publish threshold; it must be <= 0 and <= gossip threshold and a valid number") - } - if p.GraylistThreshold > 0 || p.GraylistThreshold > p.PublishThreshold || isInvalidNumber(p.GraylistThreshold) { - return fmt.Errorf("invalid graylist threshold; it must be <= 0 and <= publish threshold and a valid number") + + if !p.SkipAtomicValidation || p.PublishThreshold != 0 || p.GossipThreshold != 0 || p.GraylistThreshold != 0 { + if p.GossipThreshold > 0 || isInvalidNumber(p.GossipThreshold) { + return fmt.Errorf("invalid gossip threshold; it must be <= 0 and a valid number") + } + if p.PublishThreshold > 0 || p.PublishThreshold > p.GossipThreshold || isInvalidNumber(p.PublishThreshold) { + return fmt.Errorf("invalid publish threshold; it must be <= 0 and <= gossip threshold and a valid number") + } + if p.GraylistThreshold > 0 || p.GraylistThreshold > p.PublishThreshold || isInvalidNumber(p.GraylistThreshold) { + return fmt.Errorf("invalid graylist threshold; it must be <= 0 and <= publish threshold and a valid number") + } } - if p.AcceptPXThreshold < 0 || isInvalidNumber(p.AcceptPXThreshold) { - return fmt.Errorf("invalid accept PX threshold; it must be >= 0 and a valid number") + + if !p.SkipAtomicValidation || p.AcceptPXThreshold != 0 { + if p.AcceptPXThreshold < 0 || isInvalidNumber(p.AcceptPXThreshold) { + return fmt.Errorf("invalid accept PX threshold; it must be >= 0 and a valid number") + } } - if p.OpportunisticGraftThreshold < 0 || isInvalidNumber(p.OpportunisticGraftThreshold) { - return fmt.Errorf("invalid opportunistic grafting threshold; it must be >= 0 and a valid number") + + if !p.SkipAtomicValidation || p.OpportunisticGraftThreshold != 0 { + if p.OpportunisticGraftThreshold < 0 || isInvalidNumber(p.OpportunisticGraftThreshold) { + return fmt.Errorf("invalid opportunistic grafting threshold; it must be >= 0 and a valid number") + } } + return nil } type PeerScoreParams struct { + // whether it is allowed to just set some params and not all of them. + SkipAtomicValidation bool + // Score parameters per topic. Topics map[string]*TopicScoreParams @@ -99,12 +115,15 @@ type PeerScoreParams struct { } type TopicScoreParams struct { + // whether it is allowed to just set some params and not all of them. + SkipAtomicValidation bool + // The weight of the topic. TopicWeight float64 // P1: time in the mesh - // This is the time the peer has ben grafted in the mesh. - // The value of of the parameter is the time/TimeInMeshQuantum, capped by TimeInMeshCap + // This is the time the peer has been grafted in the mesh. + // The value of the parameter is the time/TimeInMeshQuantum, capped by TimeInMeshCap. // The weight of the parameter MUST be positive (or zero to disable). TimeInMeshWeight float64 TimeInMeshQuantum time.Duration @@ -124,7 +143,7 @@ type TopicScoreParams struct { // when validation succeeds. // This window accounts for the minimum time before a hostile mesh peer trying to game the score // could replay back a valid message we just sent them. - // It effectively tracks first and near-first deliveries, ie a message seen from a mesh peer + // It effectively tracks first and near-first deliveries, i.e., a message seen from a mesh peer // before we have forwarded it to them. // The parameter has an associated counter, decaying with MeshMessageDeliveriesDecay. // If the counter exceeds the threshold, its value is 0. @@ -159,41 +178,55 @@ func (p *PeerScoreParams) validate() error { } } - // check that the topic score is 0 or something positive - if p.TopicScoreCap < 0 || isInvalidNumber(p.TopicScoreCap) { - return fmt.Errorf("invalid topic score cap; must be positive (or 0 for no cap) and a valid number") + if !p.SkipAtomicValidation || p.TopicScoreCap != 0 { + // check that the topic score is 0 or something positive + if p.TopicScoreCap < 0 || isInvalidNumber(p.TopicScoreCap) { + return fmt.Errorf("invalid topic score cap; must be positive (or 0 for no cap) and a valid number") + } } // check that we have an app specific score; the weight can be anything (but expected positive) if p.AppSpecificScore == nil { - return fmt.Errorf("missing application specific score function") + if p.SkipAtomicValidation { + p.AppSpecificScore = func(p peer.ID) float64 { + return 0 + } + } else { + return fmt.Errorf("missing application specific score function") + } } - // check the IP colocation factor - if p.IPColocationFactorWeight > 0 || isInvalidNumber(p.IPColocationFactorWeight) { - return fmt.Errorf("invalid IPColocationFactorWeight; must be negative (or 0 to disable) and a valid number") - } - if p.IPColocationFactorWeight != 0 && p.IPColocationFactorThreshold < 1 { - return fmt.Errorf("invalid IPColocationFactorThreshold; must be at least 1") + if !p.SkipAtomicValidation || p.IPColocationFactorWeight != 0 { + // check the IP collocation factor + if p.IPColocationFactorWeight > 0 || isInvalidNumber(p.IPColocationFactorWeight) { + return fmt.Errorf("invalid IPColocationFactorWeight; must be negative (or 0 to disable) and a valid number") + } + if p.IPColocationFactorWeight != 0 && p.IPColocationFactorThreshold < 1 { + return fmt.Errorf("invalid IPColocationFactorThreshold; must be at least 1") + } } // check the behaviour penalty - if p.BehaviourPenaltyWeight > 0 || isInvalidNumber(p.BehaviourPenaltyWeight) { - return fmt.Errorf("invalid BehaviourPenaltyWeight; must be negative (or 0 to disable) and a valid number") - } - if p.BehaviourPenaltyWeight != 0 && (p.BehaviourPenaltyDecay <= 0 || p.BehaviourPenaltyDecay >= 1 || isInvalidNumber(p.BehaviourPenaltyDecay)) { - return fmt.Errorf("invalid BehaviourPenaltyDecay; must be between 0 and 1") - } - if p.BehaviourPenaltyThreshold < 0 || isInvalidNumber(p.BehaviourPenaltyThreshold) { - return fmt.Errorf("invalid BehaviourPenaltyThreshold; must be >= 0 and a valid number") + if !p.SkipAtomicValidation || p.BehaviourPenaltyWeight != 0 || p.BehaviourPenaltyThreshold != 0 { + if p.BehaviourPenaltyWeight > 0 || isInvalidNumber(p.BehaviourPenaltyWeight) { + return fmt.Errorf("invalid BehaviourPenaltyWeight; must be negative (or 0 to disable) and a valid number") + } + if p.BehaviourPenaltyWeight != 0 && (p.BehaviourPenaltyDecay <= 0 || p.BehaviourPenaltyDecay >= 1 || isInvalidNumber(p.BehaviourPenaltyDecay)) { + return fmt.Errorf("invalid BehaviourPenaltyDecay; must be between 0 and 1") + } + if p.BehaviourPenaltyThreshold < 0 || isInvalidNumber(p.BehaviourPenaltyThreshold) { + return fmt.Errorf("invalid BehaviourPenaltyThreshold; must be >= 0 and a valid number") + } } // check the decay parameters - if p.DecayInterval < time.Second { - return fmt.Errorf("invalid DecayInterval; must be at least 1s") - } - if p.DecayToZero <= 0 || p.DecayToZero >= 1 || isInvalidNumber(p.DecayToZero) { - return fmt.Errorf("invalid DecayToZero; must be between 0 and 1") + if !p.SkipAtomicValidation || p.DecayInterval != 0 || p.DecayToZero != 0 { + if p.DecayInterval < time.Second { + return fmt.Errorf("invalid DecayInterval; must be at least 1s") + } + if p.DecayToZero <= 0 || p.DecayToZero >= 1 || isInvalidNumber(p.DecayToZero) { + return fmt.Errorf("invalid DecayToZero; must be between 0 and 1") + } } // no need to check the score retention; a value of 0 means that we don't retain scores @@ -207,6 +240,43 @@ func (p *TopicScoreParams) validate() error { } // check P1 + if err := p.validateTimeInMeshParams(); err != nil { + return err + } + + // check P2 + if err := p.validateMessageDeliveryParams(); err != nil { + return err + } + // check P3 + if err := p.validateMeshMessageDeliveryParams(); err != nil { + return err + } + + // check P3b + if err := p.validateMessageFailurePenaltyParams(); err != nil { + return err + } + + // check P4 + if err := p.validateInvalidMessageDeliveryParams(); err != nil { + return err + } + + return nil +} + +func (p *TopicScoreParams) validateTimeInMeshParams() error { + if p.SkipAtomicValidation { + // in non-atomic mode, parameters at their zero values are dismissed from validation. + if p.TimeInMeshWeight == 0 && p.TimeInMeshQuantum == 0 && p.TimeInMeshCap == 0 { + return nil + } + } + + // either atomic validation mode, or some parameters have been set a value, + // hence, proceed with normal validation of all related parameters in this context. + if p.TimeInMeshQuantum == 0 { return fmt.Errorf("invalid TimeInMeshQuantum; must be non zero") } @@ -220,7 +290,20 @@ func (p *TopicScoreParams) validate() error { return fmt.Errorf("invalid TimeInMeshCap; must be positive and a valid number") } - // check P2 + return nil +} + +func (p *TopicScoreParams) validateMessageDeliveryParams() error { + if p.SkipAtomicValidation { + // in non-atomic mode, parameters at their zero values are dismissed from validation. + if p.FirstMessageDeliveriesWeight == 0 && p.FirstMessageDeliveriesCap == 0 && p.FirstMessageDeliveriesDecay == 0 { + return nil + } + } + + // either atomic validation mode, or some parameters have been set a value, + // hence, proceed with normal validation of all related parameters in this context. + if p.FirstMessageDeliveriesWeight < 0 || isInvalidNumber(p.FirstMessageDeliveriesWeight) { return fmt.Errorf("invallid FirstMessageDeliveriesWeight; must be positive (or 0 to disable) and a valid number") } @@ -231,7 +314,25 @@ func (p *TopicScoreParams) validate() error { return fmt.Errorf("invalid FirstMessageDeliveriesCap; must be positive and a valid number") } - // check P3 + return nil +} + +func (p *TopicScoreParams) validateMeshMessageDeliveryParams() error { + if p.SkipAtomicValidation { + // in non-atomic mode, parameters at their zero values are dismissed from validation. + if p.MeshMessageDeliveriesWeight == 0 && + p.MeshMessageDeliveriesCap == 0 && + p.MeshMessageDeliveriesDecay == 0 && + p.MeshMessageDeliveriesThreshold == 0 && + p.MeshMessageDeliveriesWindow == 0 && + p.MeshMessageDeliveriesActivation == 0 { + return nil + } + } + + // either atomic validation mode, or some parameters have been set a value, + // hence, proceed with normal validation of all related parameters in this context. + if p.MeshMessageDeliveriesWeight > 0 || isInvalidNumber(p.MeshMessageDeliveriesWeight) { return fmt.Errorf("invalid MeshMessageDeliveriesWeight; must be negative (or 0 to disable) and a valid number") } @@ -251,7 +352,20 @@ func (p *TopicScoreParams) validate() error { return fmt.Errorf("invalid MeshMessageDeliveriesActivation; must be at least 1s") } - // check P3b + return nil +} + +func (p *TopicScoreParams) validateMessageFailurePenaltyParams() error { + if p.SkipAtomicValidation { + // in selective mode, parameters at their zero values are dismissed from validation. + if p.MeshFailurePenaltyDecay == 0 && p.MeshFailurePenaltyWeight == 0 { + return nil + } + } + + // either atomic validation mode, or some parameters have been set a value, + // hence, proceed with normal validation of all related parameters in this context. + if p.MeshFailurePenaltyWeight > 0 || isInvalidNumber(p.MeshFailurePenaltyWeight) { return fmt.Errorf("invalid MeshFailurePenaltyWeight; must be negative (or 0 to disable) and a valid number") } @@ -259,7 +373,20 @@ func (p *TopicScoreParams) validate() error { return fmt.Errorf("invalid MeshFailurePenaltyDecay; must be between 0 and 1") } - // check P4 + return nil +} + +func (p *TopicScoreParams) validateInvalidMessageDeliveryParams() error { + if p.SkipAtomicValidation { + // in selective mode, parameters at their zero values are dismissed from validation. + if p.InvalidMessageDeliveriesDecay == 0 && p.InvalidMessageDeliveriesWeight == 0 { + return nil + } + } + + // either atomic validation mode, or some parameters have been set a value, + // hence, proceed with normal validation of all related parameters in this context. + if p.InvalidMessageDeliveriesWeight > 0 || isInvalidNumber(p.InvalidMessageDeliveriesWeight) { return fmt.Errorf("invalid InvalidMessageDeliveriesWeight; must be negative (or 0 to disable) and a valid number") } @@ -281,7 +408,7 @@ func ScoreParameterDecay(decay time.Duration) float64 { return ScoreParameterDecayWithBase(decay, DefaultDecayInterval, DefaultDecayToZero) } -// ScoreParameterDecay computes the decay factor for a parameter using base as the DecayInterval +// ScoreParameterDecayWithBase computes the decay factor for a parameter using base as the DecayInterval func ScoreParameterDecayWithBase(decay time.Duration, base time.Duration, decayToZero float64) float64 { // the decay is linear, so after n ticks the value is factor^n // so factor^n = decayToZero => factor = decayToZero^(1/n) diff --git a/score_params_test.go b/score_params_test.go index e91a6832..89f216e1 100644 --- a/score_params_test.go +++ b/score_params_test.go @@ -8,121 +8,317 @@ import ( "github.com/libp2p/go-libp2p/core/peer" ) -func TestPeerScoreThresholdsValidation(t *testing.T) { - if (&PeerScoreThresholds{GossipThreshold: 1}).validate() == nil { +func TestPeerScoreThreshold_AtomicValidation(t *testing.T) { + testPeerScoreThresholdsValidation(t, false) +} + +func TestPeerScoreThreshold_SkipAtomicValidation(t *testing.T) { + testPeerScoreThresholdsValidation(t, true) +} + +func testPeerScoreThresholdsValidation(t *testing.T, skipAtomicValidation bool) { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{PublishThreshold: 1}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + PublishThreshold: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: 0}).validate() == nil { + + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: 0, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: 0}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: 0, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{AcceptPXThreshold: -1}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + AcceptPXThreshold: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{OpportunisticGraftThreshold: -1}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + OpportunisticGraftThreshold: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() != nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: -3, + AcceptPXThreshold: 1, + OpportunisticGraftThreshold: 2}).validate() != nil { t.Fatal("expected validation success") } - if (&PeerScoreThresholds{GossipThreshold: math.Inf(-1), PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: math.Inf(-1), + PublishThreshold: -2, + GraylistThreshold: -3, + AcceptPXThreshold: 1, + OpportunisticGraftThreshold: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: math.Inf(-1), GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: math.Inf(-1), + GraylistThreshold: -3, + AcceptPXThreshold: 1, + OpportunisticGraftThreshold: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: math.Inf(-1), AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: math.Inf(-1), + AcceptPXThreshold: 1, + OpportunisticGraftThreshold: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: math.NaN(), OpportunisticGraftThreshold: 2}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: -3, + AcceptPXThreshold: math.NaN(), + OpportunisticGraftThreshold: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreThresholds{GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: math.Inf(0)}).validate() == nil { + if (&PeerScoreThresholds{ + SkipAtomicValidation: skipAtomicValidation, + GossipThreshold: -1, + PublishThreshold: -2, + GraylistThreshold: -3, + AcceptPXThreshold: 1, + OpportunisticGraftThreshold: math.Inf(0), + }).validate() == nil { t.Fatal("expected validation error") } } -func TestTopicScoreParamsValidation(t *testing.T) { - if (&TopicScoreParams{}).validate() == nil { - t.Fatal("expected validation error") +func TestTopicScoreParamsValidation_InvalidParams_AtomicValidation(t *testing.T) { + testTopicScoreParamsValidationWithInvalidParameters(t, false) +} + +func TestTopicScoreParamsValidation_InvalidParams_SkipAtomicValidation(t *testing.T) { + testTopicScoreParamsValidationWithInvalidParameters(t, true) +} + +func testTopicScoreParamsValidationWithInvalidParameters(t *testing.T, skipAtomicValidation bool) { + + if skipAtomicValidation { + if (&TopicScoreParams{ + SkipAtomicValidation: true}).validate() != nil { + t.Fatal("expected validation success") + } + } else { + if (&TopicScoreParams{}).validate() == nil { + t.Fatal("expected validation failure") + } } - if (&TopicScoreParams{TopicWeight: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicWeight: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshWeight: -1, TimeInMeshQuantum: time.Second}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshWeight: -1, + TimeInMeshQuantum: time.Second, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshWeight: 1, TimeInMeshQuantum: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshWeight: 1, + TimeInMeshQuantum: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshWeight: 1, TimeInMeshQuantum: time.Second, TimeInMeshCap: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshWeight: 1, + TimeInMeshQuantum: time.Second, + TimeInMeshCap: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 2}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: .5, FirstMessageDeliveriesCap: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + FirstMessageDeliveriesWeight: 1, + FirstMessageDeliveriesDecay: .5, + FirstMessageDeliveriesCap: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: 1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 2}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: 2}).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: .5, + MeshMessageDeliveriesCap: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: -3}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: .5, + MeshMessageDeliveriesCap: 5, + MeshMessageDeliveriesThreshold: -3, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: 3, MeshMessageDeliveriesWindow: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: .5, + MeshMessageDeliveriesCap: 5, + MeshMessageDeliveriesThreshold: 3, + MeshMessageDeliveriesWindow: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: 3, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Millisecond}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: .5, + MeshMessageDeliveriesCap: 5, + MeshMessageDeliveriesThreshold: 3, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Millisecond}).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: 1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshFailurePenaltyWeight: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 2}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: 2, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: 1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: -1}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&TopicScoreParams{TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 2}).validate() == nil { + if (&TopicScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TimeInMeshQuantum: time.Second, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 2, + }).validate() == nil { t.Fatal("expected validation error") } +} +func TestTopicScoreParamsValidation_ValidParams_AtomicValidation(t *testing.T) { // Don't use these params in production! if (&TopicScoreParams{ + SkipAtomicValidation: false, TopicWeight: 1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, @@ -145,67 +341,223 @@ func TestTopicScoreParamsValidation(t *testing.T) { } } -func TestPeerScoreParamsValidation(t *testing.T) { +func TestTopicScoreParamsValidation_NonAtomicValidation(t *testing.T) { + // Don't use these params in production! + // In non-atomic (selective) validation mode, the subset of parameters passes + // validation if the individual parameters values pass validation. + p := &TopicScoreParams{} + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.SkipAtomicValidation = true + }) + // including topic weight. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.TopicWeight = 1 + }) + // including time in mesh parameters. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.TimeInMeshWeight = 0.01 + params.TimeInMeshQuantum = time.Second + params.TimeInMeshCap = 10 + }) + // including first message delivery parameters. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.FirstMessageDeliveriesWeight = 1 + params.FirstMessageDeliveriesDecay = 0.5 + params.FirstMessageDeliveriesCap = 10 + }) + // including mesh message delivery parameters. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.MeshMessageDeliveriesWeight = -1 + params.MeshMessageDeliveriesDecay = 0.5 + params.MeshMessageDeliveriesCap = 10 + params.MeshMessageDeliveriesThreshold = 5 + params.MeshMessageDeliveriesWindow = time.Millisecond + params.MeshMessageDeliveriesActivation = time.Second + }) + // including mesh failure penalty parameters. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.MeshFailurePenaltyWeight = -1 + params.MeshFailurePenaltyDecay = 0.5 + }) + // including invalid message delivery parameters. + setTopicParamAndValidate(t, p, func(params *TopicScoreParams) { + params.InvalidMessageDeliveriesWeight = -1 + params.InvalidMessageDeliveriesDecay = 0.5 + }) +} + +func TestPeerScoreParamsValidation_InvalidParams_AtomicValidation(t *testing.T) { + testPeerScoreParamsValidationWithInvalidParams(t, false) +} + +func TestPeerScoreParamsValidation_InvalidParams_SkipAtomicValidation(t *testing.T) { + testPeerScoreParamsValidationWithInvalidParams(t, true) +} + +func testPeerScoreParamsValidationWithInvalidParams(t *testing.T, skipAtomicValidation bool) { appScore := func(peer.ID) float64 { return 0 } - if (&PeerScoreParams{TopicScoreCap: -1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: -1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{TopicScoreCap: 1, DecayInterval: time.Second, DecayToZero: 0.01}).validate() == nil { - t.Fatal("expected validation error") + + if skipAtomicValidation { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + DecayInterval: time.Second, + DecayToZero: 0.01, + }).validate() != nil { + t.Fatal("expected validation success") + } + } else { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + DecayInterval: time.Second, + DecayToZero: 0.01, + }).validate() == nil { + t.Fatal("expected validation error") + } } - if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: 1}).validate() == nil { + + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: -1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: -1}).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Millisecond, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Millisecond, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: -1, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: -1, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 2, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 2, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: 1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + BehaviourPenaltyWeight: 1}).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + BehaviourPenaltyWeight: -1, + }).validate() == nil { t.Fatal("expected validation error") } - if (&PeerScoreParams{AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1, BehaviourPenaltyDecay: 2}).validate() == nil { + if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 2, + }).validate() == nil { t.Fatal("expected validation error") } - // don't use these params in production! + // Checks the topic parameters for invalid values such as infinite and + // NaN numbers. if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, + TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, - BehaviourPenaltyWeight: -1, - BehaviourPenaltyDecay: 0.999, - }).validate() != nil { - t.Fatal("expected validation success") + Topics: map[string]*TopicScoreParams{ + "test": { + TopicWeight: math.Inf(0), + TimeInMeshWeight: math.NaN(), + TimeInMeshQuantum: time.Second, + TimeInMeshCap: 10, + FirstMessageDeliveriesWeight: math.Inf(1), + FirstMessageDeliveriesDecay: 0.5, + FirstMessageDeliveriesCap: 10, + MeshMessageDeliveriesWeight: math.Inf(-1), + MeshMessageDeliveriesDecay: math.NaN(), + MeshMessageDeliveriesCap: math.Inf(0), + MeshMessageDeliveriesThreshold: 5, + MeshMessageDeliveriesWindow: time.Millisecond, + MeshMessageDeliveriesActivation: time.Second, + MeshFailurePenaltyWeight: -1, + MeshFailurePenaltyDecay: math.NaN(), + InvalidMessageDeliveriesWeight: math.Inf(0), + InvalidMessageDeliveriesDecay: math.NaN(), + }, + }, + }).validate() == nil { + t.Fatal("expected validation failure") } if (&PeerScoreParams{ - TopicScoreCap: 1, + SkipAtomicValidation: skipAtomicValidation, AppSpecificScore: appScore, DecayInterval: time.Second, - DecayToZero: 0.01, - IPColocationFactorWeight: -1, + DecayToZero: math.Inf(0), + IPColocationFactorWeight: math.Inf(-1), IPColocationFactorThreshold: 1, - BehaviourPenaltyWeight: -1, - BehaviourPenaltyDecay: 0.999, - }).validate() != nil { - t.Fatal("expected validation success") + BehaviourPenaltyWeight: math.Inf(0), + BehaviourPenaltyDecay: math.NaN(), + }).validate() == nil { + t.Fatal("expected validation failure") } if (&PeerScoreParams{ + SkipAtomicValidation: skipAtomicValidation, TopicScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, @@ -213,8 +565,8 @@ func TestPeerScoreParamsValidation(t *testing.T) { IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, Topics: map[string]*TopicScoreParams{ - "test": &TopicScoreParams{ - TopicWeight: 1, + "test": { + TopicWeight: -1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, @@ -233,11 +585,40 @@ func TestPeerScoreParamsValidation(t *testing.T) { InvalidMessageDeliveriesDecay: 0.5, }, }, + }).validate() == nil { + t.Fatal("expected validation failure") + } +} + +func TestPeerScoreParamsValidation_ValidParams_AtomicValidation(t *testing.T) { + appScore := func(peer.ID) float64 { return 0 } + + // don't use these params in production! + if (&PeerScoreParams{ + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 0.999, + }).validate() != nil { + t.Fatal("expected validation success") + } + + if (&PeerScoreParams{ + TopicScoreCap: 1, + AppSpecificScore: appScore, + DecayInterval: time.Second, + DecayToZero: 0.01, + IPColocationFactorWeight: -1, + IPColocationFactorThreshold: 1, + BehaviourPenaltyWeight: -1, + BehaviourPenaltyDecay: 0.999, }).validate() != nil { t.Fatal("expected validation success") } - // don't use these params in production! if (&PeerScoreParams{ TopicScoreCap: 1, AppSpecificScore: appScore, @@ -246,8 +627,8 @@ func TestPeerScoreParamsValidation(t *testing.T) { IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, Topics: map[string]*TopicScoreParams{ - "test": &TopicScoreParams{ - TopicWeight: -1, + "test": { + TopicWeight: 1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, @@ -266,58 +647,74 @@ func TestPeerScoreParamsValidation(t *testing.T) { InvalidMessageDeliveriesDecay: 0.5, }, }, - }).validate() == nil { - t.Fatal("expected validation failure") + }).validate() != nil { + t.Fatal("expected validation success") } +} - // Checks the topic parameters for invalid values such as infinite and - // NaN numbers. +func TestPeerScoreParamsValidation_ValidParams_SkipAtomicValidation(t *testing.T) { + appScore := func(peer.ID) float64 { return 0 } - // Don't use these params in production! - if (&PeerScoreParams{ - AppSpecificScore: appScore, - DecayInterval: time.Second, - DecayToZero: math.Inf(0), - IPColocationFactorWeight: math.Inf(-1), - IPColocationFactorThreshold: 1, - BehaviourPenaltyWeight: math.Inf(0), - BehaviourPenaltyDecay: math.NaN(), - }).validate() == nil { - t.Fatal("expected validation failure") - } + // don't use these params in production! + p := &PeerScoreParams{} + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.SkipAtomicValidation = true + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.AppSpecificScore = appScore + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.DecayInterval = time.Second + params.DecayToZero = 0.01 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.IPColocationFactorWeight = -1 + params.IPColocationFactorThreshold = 1 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.BehaviourPenaltyWeight = -1 + params.BehaviourPenaltyDecay = 0.999 + }) - if (&PeerScoreParams{ - TopicScoreCap: 1, - AppSpecificScore: appScore, - DecayInterval: time.Second, - DecayToZero: 0.01, - IPColocationFactorWeight: -1, - IPColocationFactorThreshold: 1, - Topics: map[string]*TopicScoreParams{ - "test": &TopicScoreParams{ - TopicWeight: math.Inf(0), - TimeInMeshWeight: math.NaN(), + p = &PeerScoreParams{SkipAtomicValidation: true, AppSpecificScore: appScore} + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.TopicScoreCap = 1 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.DecayInterval = time.Second + params.DecayToZero = 0.01 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.IPColocationFactorWeight = -1 + params.IPColocationFactorThreshold = 1 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.BehaviourPenaltyWeight = -1 + params.BehaviourPenaltyDecay = 0.999 + }) + setParamAndValidate(t, p, func(params *PeerScoreParams) { + params.Topics = map[string]*TopicScoreParams{ + "test": { + TopicWeight: 1, + TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, - FirstMessageDeliveriesWeight: math.Inf(1), + FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 0.5, FirstMessageDeliveriesCap: 10, - MeshMessageDeliveriesWeight: math.Inf(-1), - MeshMessageDeliveriesDecay: math.NaN(), - MeshMessageDeliveriesCap: math.Inf(0), + MeshMessageDeliveriesWeight: -1, + MeshMessageDeliveriesDecay: 0.5, + MeshMessageDeliveriesCap: 10, MeshMessageDeliveriesThreshold: 5, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Second, MeshFailurePenaltyWeight: -1, - MeshFailurePenaltyDecay: math.NaN(), - InvalidMessageDeliveriesWeight: math.Inf(0), - InvalidMessageDeliveriesDecay: math.NaN(), + MeshFailurePenaltyDecay: 0.5, + InvalidMessageDeliveriesWeight: -1, + InvalidMessageDeliveriesDecay: 0.5, }, - }, - }).validate() == nil { - t.Fatal("expected validation failure") - } - + } + }) } func TestScoreParameterDecay(t *testing.T) { @@ -326,3 +723,17 @@ func TestScoreParameterDecay(t *testing.T) { t.Fatalf("expected .9987216039048303, got %f", decay1hr) } } + +func setParamAndValidate(t *testing.T, params *PeerScoreParams, set func(*PeerScoreParams)) { + set(params) + if err := params.validate(); err != nil { + t.Fatalf("expected validation success, got: %s", err) + } +} + +func setTopicParamAndValidate(t *testing.T, params *TopicScoreParams, set func(topic *TopicScoreParams)) { + set(params) + if err := params.validate(); err != nil { + t.Fatalf("expected validation success, got: %s", err) + } +}