package blossomsub import ( "math" "testing" "time" "github.com/libp2p/go-libp2p/core/peer" ) 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{ SkipAtomicValidation: skipAtomicValidation, PublishThreshold: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: 0, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: 0, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, AcceptPXThreshold: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, OpportunisticGraftThreshold: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2}).validate() != nil { t.Fatal("expected validation success") } 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{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: math.Inf(-1), GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: 2, }).validate() == nil { t.Fatal("expected validation error") } 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{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: math.NaN(), OpportunisticGraftThreshold: 2, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreThresholds{ SkipAtomicValidation: skipAtomicValidation, GossipThreshold: -1, PublishThreshold: -2, GraylistThreshold: -3, AcceptPXThreshold: 1, OpportunisticGraftThreshold: math.Inf(0), }).validate() == nil { t.Fatal("expected validation error") } } func TestBitmaskScoreParamsValidation_InvalidParams_AtomicValidation(t *testing.T) { testBitmaskScoreParamsValidationWithInvalidParameters(t, false) } func TestBitmaskScoreParamsValidation_InvalidParams_SkipAtomicValidation(t *testing.T) { testBitmaskScoreParamsValidationWithInvalidParameters(t, true) } func testBitmaskScoreParamsValidationWithInvalidParameters(t *testing.T, skipAtomicValidation bool) { if skipAtomicValidation { if (&BitmaskScoreParams{ SkipAtomicValidation: true}).validate() != nil { t.Fatal("expected validation success") } } else { if (&BitmaskScoreParams{}).validate() == nil { t.Fatal("expected validation failure") } } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskWeight: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshWeight: -1, TimeInMeshQuantum: time.Second, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshWeight: 1, TimeInMeshQuantum: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshWeight: 1, TimeInMeshQuantum: time.Second, TimeInMeshCap: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 2, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: .5, FirstMessageDeliveriesCap: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 2}).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: -3, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: .5, MeshMessageDeliveriesCap: 5, MeshMessageDeliveriesThreshold: 3, MeshMessageDeliveriesWindow: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ 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 (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 2, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&BitmaskScoreParams{ SkipAtomicValidation: skipAtomicValidation, TimeInMeshQuantum: time.Second, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 2, }).validate() == nil { t.Fatal("expected validation error") } } func TestBitmaskScoreParamsValidation_ValidParams_AtomicValidation(t *testing.T) { // Don't use these params in production! if (&BitmaskScoreParams{ SkipAtomicValidation: false, BitmaskWeight: 1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 0.5, FirstMessageDeliveriesCap: 10, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 0.5, MeshMessageDeliveriesCap: 10, MeshMessageDeliveriesThreshold: 5, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 0.5, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 0.5, }).validate() != nil { t.Fatal("expected validation success") } } func TestBitmaskScoreParamsValidation_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 := &BitmaskScoreParams{} setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { params.SkipAtomicValidation = true }) // including bitmask weight. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { params.BitmaskWeight = 1 }) // including time in mesh parameters. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { params.TimeInMeshWeight = 0.01 params.TimeInMeshQuantum = time.Second params.TimeInMeshCap = 10 }) // including first message delivery parameters. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { params.FirstMessageDeliveriesWeight = 1 params.FirstMessageDeliveriesDecay = 0.5 params.FirstMessageDeliveriesCap = 10 }) // including mesh message delivery parameters. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { 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. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { params.MeshFailurePenaltyWeight = -1 params.MeshFailurePenaltyDecay = 0.5 }) // including invalid message delivery parameters. setBitmaskParamAndValidate(t, p, func(params *BitmaskScoreParams) { 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{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: -1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, }).validate() == nil { t.Fatal("expected validation error") } if skipAtomicValidation { if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, DecayInterval: time.Second, DecayToZero: 0.01, }).validate() != nil { t.Fatal("expected validation success") } } else { if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, DecayInterval: time.Second, DecayToZero: 0.01, }).validate() == nil { t.Fatal("expected validation error") } } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: -1}).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Millisecond, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: -1, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 2, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: 1}).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1, }).validate() == nil { t.Fatal("expected validation error") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, BehaviourPenaltyWeight: -1, BehaviourPenaltyDecay: 2, }).validate() == nil { t.Fatal("expected validation error") } // Checks the bitmask parameters for invalid values such as infinite and // NaN numbers. if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, Bitmasks: map[string]*BitmaskScoreParams{ "test": { BitmaskWeight: 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{ SkipAtomicValidation: skipAtomicValidation, 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") } if (&PeerScoreParams{ SkipAtomicValidation: skipAtomicValidation, BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, Bitmasks: map[string]*BitmaskScoreParams{ "test": { BitmaskWeight: -1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 0.5, FirstMessageDeliveriesCap: 10, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 0.5, MeshMessageDeliveriesCap: 10, MeshMessageDeliveriesThreshold: 5, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 0.5, InvalidMessageDeliveriesWeight: -1, 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{ BitmaskScoreCap: 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") } if (&PeerScoreParams{ BitmaskScoreCap: 1, AppSpecificScore: appScore, DecayInterval: time.Second, DecayToZero: 0.01, IPColocationFactorWeight: -1, IPColocationFactorThreshold: 1, Bitmasks: map[string]*BitmaskScoreParams{ "test": { BitmaskWeight: 1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 0.5, FirstMessageDeliveriesCap: 10, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 0.5, MeshMessageDeliveriesCap: 10, MeshMessageDeliveriesThreshold: 5, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 0.5, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 0.5, }, }, }).validate() != nil { t.Fatal("expected validation success") } } func TestPeerScoreParamsValidation_ValidParams_SkipAtomicValidation(t *testing.T) { appScore := func(peer.ID) float64 { return 0 } // 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 }) p = &PeerScoreParams{SkipAtomicValidation: true, AppSpecificScore: appScore} setParamAndValidate(t, p, func(params *PeerScoreParams) { params.BitmaskScoreCap = 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.Bitmasks = map[string]*BitmaskScoreParams{ "test": { BitmaskWeight: 1, TimeInMeshWeight: 0.01, TimeInMeshQuantum: time.Second, TimeInMeshCap: 10, FirstMessageDeliveriesWeight: 1, FirstMessageDeliveriesDecay: 0.5, FirstMessageDeliveriesCap: 10, MeshMessageDeliveriesWeight: -1, MeshMessageDeliveriesDecay: 0.5, MeshMessageDeliveriesCap: 10, MeshMessageDeliveriesThreshold: 5, MeshMessageDeliveriesWindow: time.Millisecond, MeshMessageDeliveriesActivation: time.Second, MeshFailurePenaltyWeight: -1, MeshFailurePenaltyDecay: 0.5, InvalidMessageDeliveriesWeight: -1, InvalidMessageDeliveriesDecay: 0.5, }, } }) } func TestScoreParameterDecay(t *testing.T) { decay1hr := ScoreParameterDecay(time.Hour) if decay1hr != .9987216039048303 { 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 setBitmaskParamAndValidate(t *testing.T, params *BitmaskScoreParams, set func(bitmask *BitmaskScoreParams)) { set(params) if err := params.validate(); err != nil { t.Fatalf("expected validation success, got: %s", err) } }