github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/scoring/decay_test.go (about) 1 package scoring_test 2 3 import ( 4 "fmt" 5 "math" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 11 "github.com/onflow/flow-go/config" 12 "github.com/onflow/flow-go/network/p2p" 13 "github.com/onflow/flow-go/network/p2p/scoring" 14 ) 15 16 // TestGeometricDecay tests the GeometricDecay function. 17 func TestGeometricDecay(t *testing.T) { 18 type args struct { 19 penalty float64 20 decay float64 21 lastUpdated time.Time 22 } 23 tests := []struct { 24 name string 25 args args 26 want float64 27 wantErr error 28 }{ 29 { 30 name: "valid penalty, decay, and time", 31 args: args{ 32 penalty: 100, 33 decay: 0.9, 34 lastUpdated: time.Now().Add(-10 * time.Second), 35 }, 36 want: 100 * math.Pow(0.9, 10), 37 wantErr: nil, 38 }, 39 { 40 name: "zero decay factor", 41 args: args{ 42 penalty: 100, 43 decay: 0, 44 lastUpdated: time.Now(), 45 }, 46 want: 0, 47 wantErr: fmt.Errorf("decay factor must be in the range [0, 1], got 0"), 48 }, 49 { 50 name: "decay factor of 1", 51 args: args{ 52 penalty: 100, 53 decay: 1, 54 lastUpdated: time.Now().Add(-10 * time.Second), 55 }, 56 want: 100, 57 wantErr: nil, 58 }, 59 { 60 name: "negative decay factor", 61 args: args{ 62 penalty: 100, 63 decay: -0.5, 64 lastUpdated: time.Now(), 65 }, 66 want: 0, 67 wantErr: fmt.Errorf("decay factor must be in the range [0, 1], got %f", -0.5), 68 }, 69 { 70 name: "decay factor greater than 1", 71 args: args{ 72 penalty: 100, 73 decay: 1.2, 74 lastUpdated: time.Now(), 75 }, 76 want: 0, 77 wantErr: fmt.Errorf("decay factor must be in the range [0, 1], got %f", 1.2), 78 }, 79 { 80 name: "large time value causing overflow", 81 args: args{ 82 penalty: 100, 83 decay: 0.999999999999999, 84 lastUpdated: time.Now().Add(-1e5 * time.Second), 85 }, 86 want: 100 * math.Pow(0.999999999999999, 1e5), 87 wantErr: nil, 88 }, 89 { 90 name: "large decay factor and time value causing underflow", 91 args: args{ 92 penalty: 100, 93 decay: 0.999999, 94 lastUpdated: time.Now().Add(-1e9 * time.Second), 95 }, 96 want: 0, 97 wantErr: nil, 98 }, 99 { 100 name: "very small decay factor and time value causing underflow", 101 args: args{ 102 penalty: 100, 103 decay: 0.000001, 104 lastUpdated: time.Now().Add(-1e9 * time.Second), 105 }, 106 want: 0, 107 wantErr: nil, 108 }, 109 { 110 name: "future time value causing an error", 111 args: args{ 112 penalty: 100, 113 decay: 0.999999, 114 lastUpdated: time.Now().Add(+1e9 * time.Second), 115 }, 116 want: 0, 117 wantErr: fmt.Errorf("last updated time cannot be in the future"), 118 }, 119 } 120 121 for _, tt := range tests { 122 t.Run(tt.name, func(t *testing.T) { 123 got, err := scoring.GeometricDecay(tt.args.penalty, tt.args.decay, tt.args.lastUpdated) 124 if tt.wantErr != nil { 125 assert.Errorf(t, err, tt.wantErr.Error()) 126 } 127 assert.LessOrEqual(t, truncateFloat(math.Abs(got-tt.want), 3), 1e-2) 128 }) 129 } 130 } 131 132 // TestDefaultDecayFunction tests the default decay function. 133 // The default decay function is used when no custom decay function is provided. 134 // The test evaluates the following cases: 135 // 1. penalty is non-negative and should not be decayed. 136 // 2. penalty is negative and above the skipDecayThreshold and lastUpdated is too recent. In this case, the penalty should not be decayed. 137 // 3. penalty is negative and above the skipDecayThreshold and lastUpdated is too old. In this case, the penalty should not be decayed. 138 // 4. penalty is negative and below the skipDecayThreshold and lastUpdated is too recent. In this case, the penalty should not be decayed. 139 // 5. penalty is negative and below the skipDecayThreshold and lastUpdated is too old. In this case, the penalty should be decayed. 140 func TestDefaultDecayFunction(t *testing.T) { 141 flowConfig, err := config.DefaultConfig() 142 assert.NoError(t, err) 143 144 type args struct { 145 record p2p.GossipSubSpamRecord 146 lastUpdated time.Time 147 } 148 149 type want struct { 150 record p2p.GossipSubSpamRecord 151 } 152 153 tests := []struct { 154 name string 155 args args 156 want want 157 }{ 158 { 159 // 1. penalty is non-negative and should not be decayed. 160 name: "penalty is non-negative", 161 args: args{ 162 record: p2p.GossipSubSpamRecord{ 163 Penalty: 5, 164 Decay: 0.8, 165 }, 166 lastUpdated: time.Now(), 167 }, 168 want: want{ 169 record: p2p.GossipSubSpamRecord{ 170 Penalty: 5, 171 Decay: 0.8, 172 }, 173 }, 174 }, 175 { 176 // 2. penalty is negative and above the skipDecayThreshold and lastUpdated is too recent. In this case, the penalty should not be decayed, 177 // since less than a second has passed since last update. 178 name: "penalty is negative and but above skipDecayThreshold and lastUpdated is too recent", 179 args: args{ 180 record: p2p.GossipSubSpamRecord{ 181 Penalty: -0.09, // -0.09 is above skipDecayThreshold of -0.1 182 Decay: 0.8, 183 }, 184 lastUpdated: time.Now(), 185 }, 186 want: want{ 187 record: p2p.GossipSubSpamRecord{ 188 Penalty: 0, // penalty is set to 0 189 Decay: 0.8, 190 LastDecayAdjustment: time.Time{}, 191 }, 192 }, 193 }, 194 { 195 // 3. penalty is negative and above the skipDecayThreshold and lastUpdated is too old. In this case, the penalty should not be decayed, 196 // since penalty is between [skipDecayThreshold, 0] and more than a second has passed since last update. 197 name: "penalty is negative and but above skipDecayThreshold and lastUpdated is too old", 198 args: args{ 199 record: p2p.GossipSubSpamRecord{ 200 Penalty: -0.09, // -0.09 is above skipDecayThreshold of -0.1 201 Decay: 0.8, 202 }, 203 lastUpdated: time.Now().Add(-10 * time.Second), 204 }, 205 want: want{ 206 record: p2p.GossipSubSpamRecord{ 207 Penalty: 0, // penalty is set to 0 208 Decay: 0.8, 209 LastDecayAdjustment: time.Time{}, 210 }, 211 }, 212 }, 213 { 214 // 4. penalty is negative and below the skipDecayThreshold and lastUpdated is too recent. In this case, the penalty should not be decayed, 215 // since less than a second has passed since last update. 216 name: "penalty is negative and below skipDecayThreshold but lastUpdated is too recent", 217 args: args{ 218 record: p2p.GossipSubSpamRecord{ 219 Penalty: -5, 220 Decay: 0.8, 221 }, 222 lastUpdated: time.Now(), 223 }, 224 want: want{ 225 record: p2p.GossipSubSpamRecord{ 226 Penalty: -5, 227 Decay: 0.8, 228 }, 229 }, 230 }, 231 { 232 // 5. penalty is negative and below the skipDecayThreshold and lastUpdated is too old. In this case, the penalty should be decayed. 233 name: "penalty is negative and below skipDecayThreshold but lastUpdated is too old", 234 args: args{ 235 record: p2p.GossipSubSpamRecord{ 236 Penalty: -15, 237 Decay: 0.8, 238 }, 239 lastUpdated: time.Now().Add(-10 * time.Second), 240 }, 241 want: want{ 242 record: p2p.GossipSubSpamRecord{ 243 Penalty: -15 * math.Pow(0.8, 10), 244 Decay: 0.8, 245 }, 246 }, 247 }, 248 { 249 // 6. penalty is negative and below slowerDecayPenaltyThreshold record decay should be adjusted. The `LastDecayAdjustment` has not been updated since initialization. 250 name: "penalty is negative and below slowerDecayPenaltyThreshold record decay should be adjusted", 251 args: args{ 252 record: p2p.GossipSubSpamRecord{ 253 Penalty: -100, 254 Decay: 0.8, 255 }, 256 lastUpdated: time.Now(), 257 }, 258 want: want{ 259 record: p2p.GossipSubSpamRecord{ 260 Penalty: -100, 261 Decay: 0.81, 262 }, 263 }, 264 }, 265 { 266 // 7. penalty is negative and below slowerDecayPenaltyThreshold but record.LastDecayAdjustment is too recent. In this case the decay should not be adjusted. 267 name: "penalty is negative and below slowerDecayPenaltyThreshold record decay should not be adjusted", 268 args: args{ 269 record: p2p.GossipSubSpamRecord{ 270 Penalty: -100, 271 Decay: 0.9, 272 LastDecayAdjustment: time.Now().Add(10 * time.Second), 273 }, 274 lastUpdated: time.Now(), 275 }, 276 want: want{ 277 record: p2p.GossipSubSpamRecord{ 278 Penalty: -100, 279 Decay: 0.9, 280 }, 281 }, 282 }, 283 { 284 // 8. penalty is negative and below slowerDecayPenaltyThreshold; and LastDecayAdjustment time passed the decay adjust interval. record decay should be adjusted. 285 name: "penalty is negative and below slowerDecayPenaltyThreshold and LastDecayAdjustment time passed the decay adjust interval. Record decay should be adjusted", 286 args: args{ 287 record: p2p.GossipSubSpamRecord{ 288 Penalty: -100, 289 Decay: 0.8, 290 LastDecayAdjustment: time.Now().Add(-flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters.SpamRecordCache.Decay.PenaltyDecayEvaluationPeriod), 291 }, 292 lastUpdated: time.Now(), 293 }, 294 want: want{ 295 record: p2p.GossipSubSpamRecord{ 296 Penalty: -100, 297 Decay: 0.81, 298 }, 299 }, 300 }, 301 } 302 scoringRegistryConfig := flowConfig.NetworkConfig.GossipSub.ScoringParameters.ScoringRegistryParameters 303 decayFunc := scoring.DefaultDecayFunction(scoringRegistryConfig.SpamRecordCache.Decay) 304 305 for _, tt := range tests { 306 t.Run(tt.name, func(t *testing.T) { 307 got, err := decayFunc(tt.args.record, tt.args.lastUpdated) 308 assert.NoError(t, err) 309 tolerance := 0.01 // 1% tolerance 310 expectedPenalty := tt.want.record.Penalty 311 312 // ensure expectedPenalty is not zero to avoid division by zero 313 if expectedPenalty != 0 { 314 normalizedDifference := math.Abs(got.Penalty-expectedPenalty) / math.Abs(expectedPenalty) 315 assert.Less(t, normalizedDifference, tolerance) 316 } else { 317 // handles the case where expectedPenalty is zero 318 assert.Less(t, math.Abs(got.Penalty), tolerance) 319 } 320 assert.Equal(t, tt.want.record.Decay, got.Decay) 321 }) 322 } 323 } 324 325 func truncateFloat(number float64, decimalPlaces int) float64 { 326 pow := math.Pow(10, float64(decimalPlaces)) 327 return float64(int(number*pow)) / pow 328 }