gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/hostdb/hostweight_test.go (about) 1 package hostdb 2 3 import ( 4 "testing" 5 "time" 6 7 "gitlab.com/NebulousLabs/fastrand" 8 9 "gitlab.com/SiaPrime/SiaPrime/build" 10 "gitlab.com/SiaPrime/SiaPrime/modules" 11 "gitlab.com/SiaPrime/SiaPrime/types" 12 ) 13 14 var ( 15 // Set the default test allowance 16 DefaultTestAllowance = modules.Allowance{ 17 Funds: types.SiacoinPrecision.Mul64(1e5), 18 Hosts: uint64(50), 19 Period: types.BlockHeight(12096), 20 RenewWindow: types.BlockHeight(4032), 21 22 ExpectedStorage: 1e12, // 1 TB 23 ExpectedUpload: uint64(200e9) / types.BlocksPerMonth, // 200 GB per month 24 ExpectedDownload: uint64(100e9) / types.BlocksPerMonth, // 100 GB per month 25 ExpectedRedundancy: 3.0, // default is 10/30 erasure coding 26 } 27 28 // The default entry to use when performing scoring. 29 DefaultHostDBEntry = modules.HostDBEntry{ 30 HostExternalSettings: modules.HostExternalSettings{ 31 AcceptingContracts: true, 32 MaxDuration: 26e3, 33 RemainingStorage: 250e9, 34 WindowSize: 144, 35 36 Collateral: types.NewCurrency64(1e5).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte), 37 MaxCollateral: types.NewCurrency64(5e3).Mul(types.SiacoinPrecision), 38 39 ContractPrice: types.NewCurrency64(5).Mul(types.SiacoinPrecision), 40 StoragePrice: types.NewCurrency64(5e4).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte), 41 42 Version: build.Version, 43 }, 44 } 45 ) 46 47 // calculateWeightFromUInt64Price will fill out a host entry with a bunch of 48 // defaults, and then grab the weight of that host using a set price. 49 func calculateWeightFromUInt64Price(price, collateral uint64) (weight types.Currency) { 50 hdb := bareHostDB() 51 hdb.SetAllowance(DefaultTestAllowance) 52 hdb.blockHeight = 0 53 54 entry := DefaultHostDBEntry 55 entry.StoragePrice = types.NewCurrency64(price).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte) 56 entry.Collateral = types.NewCurrency64(collateral).Mul(types.SiacoinPrecision).Div(modules.BlockBytesPerMonthTerabyte) 57 58 return hdb.weightFunc(entry).Score() 59 } 60 61 // TestHostWeightDistinctPrices ensures that the host weight is different if the 62 // prices are different, and that a higher price has a lower score. 63 func TestHostWeightDistinctPrices(t *testing.T) { 64 if testing.Short() { 65 t.SkipNow() 66 } 67 weight1 := calculateWeightFromUInt64Price(50000, 1e5) 68 weight2 := calculateWeightFromUInt64Price(50100, 1e5) 69 if weight1.Cmp(weight2) <= 0 { 70 t.Log(weight1) 71 t.Log(weight2) 72 t.Error("Weight of expensive host is not the correct value.") 73 } 74 } 75 76 // TestHostWeightDistinctCollateral ensures that the host weight is different if 77 // the collaterals are different, and that a higher collateral has a higher 78 // score. 79 func TestHostWeightDistinctCollateral(t *testing.T) { 80 if testing.Short() { 81 t.SkipNow() 82 } 83 weight1 := calculateWeightFromUInt64Price(300, 100) 84 weight2 := calculateWeightFromUInt64Price(300, 99) 85 if weight1.Cmp(weight2) <= 0 { 86 t.Log(weight1) 87 t.Log(weight2) 88 t.Error("Weight of expensive host is not the correct value.") 89 } 90 } 91 92 // When the collateral is below the cutoff, the collateral should be more 93 // important than the price. 94 func TestHostWeightCollateralBelowCutoff(t *testing.T) { 95 if testing.Short() { 96 t.SkipNow() 97 } 98 weight1 := calculateWeightFromUInt64Price(300, 10) 99 weight2 := calculateWeightFromUInt64Price(150, 5) 100 if weight1.Cmp(weight2) <= 0 { 101 t.Log(weight1) 102 t.Log(weight2) 103 t.Error("Weight of expensive host is not the correct value.") 104 } 105 } 106 107 // When the collateral is below the cutoff, the price should be more important 108 // than the collateral. 109 func TestHostWeightCollateralAboveCutoff(t *testing.T) { 110 if testing.Short() { 111 t.SkipNow() 112 } 113 weight1 := calculateWeightFromUInt64Price(30000, 100000) 114 weight2 := calculateWeightFromUInt64Price(15000, 50000) 115 if weight1.Cmp(weight2) >= 0 { 116 t.Log(weight1) 117 t.Log(weight2) 118 t.Error("Weight of expensive host is not the correct value.") 119 } 120 } 121 122 // TestHostWeightIdenticalPrices checks that the weight function is 123 // deterministic for two hosts that have identical settings - each should get 124 // the same score. 125 func TestHostWeightIdenticalPrices(t *testing.T) { 126 if testing.Short() { 127 t.SkipNow() 128 } 129 weight1 := calculateWeightFromUInt64Price(42, 100) 130 weight2 := calculateWeightFromUInt64Price(42, 100) 131 if weight1.Cmp(weight2) != 0 { 132 t.Error("Weight of identically priced hosts should be equal.") 133 } 134 } 135 136 // TestHostWeightWithOnePricedZero checks that nothing unexpected happens when 137 // there is a zero price, and also checks that the zero priced host scores 138 // higher than the host that charges money. 139 func TestHostWeightWithOnePricedZero(t *testing.T) { 140 if testing.Short() { 141 t.SkipNow() 142 } 143 weight1 := calculateWeightFromUInt64Price(5, 10) 144 weight2 := calculateWeightFromUInt64Price(0, 10) 145 if weight1.Cmp(weight2) >= 0 { 146 t.Log(weight1) 147 t.Log(weight2) 148 t.Error("Zero-priced host should have higher weight than nonzero-priced host.") 149 } 150 } 151 152 // TestHostWeightBothPricesZero checks that there is nondeterminism in the 153 // weight function even with zero value prices. 154 func TestHostWeightWithBothPricesZero(t *testing.T) { 155 if testing.Short() { 156 t.SkipNow() 157 } 158 weight1 := calculateWeightFromUInt64Price(0, 100) 159 weight2 := calculateWeightFromUInt64Price(0, 100) 160 if weight1.Cmp(weight2) != 0 { 161 t.Error("Weight of two zero-priced hosts should be equal.") 162 } 163 } 164 165 // TestHostWeightWithNoCollateral checks that nothing bad (like a panic) happens 166 // when the collateral is set to zero. 167 func TestHostWeightWithNoCollateral(t *testing.T) { 168 if testing.Short() { 169 t.SkipNow() 170 } 171 weight1 := calculateWeightFromUInt64Price(300, 1) 172 weight2 := calculateWeightFromUInt64Price(300, 0) 173 if weight1.Cmp(weight2) <= 0 { 174 t.Log(weight1) 175 t.Log(weight2) 176 t.Error("Weight of lower priced host should be higher") 177 } 178 } 179 180 // TestHostWeightMaxDuration checks that the host with an unacceptable duration 181 // has a lower score. 182 func TestHostWeightMaxDuration(t *testing.T) { 183 if testing.Short() { 184 t.SkipNow() 185 } 186 hdb := bareHostDB() 187 hdb.SetAllowance(DefaultTestAllowance) 188 189 entry := DefaultHostDBEntry 190 entry2 := DefaultHostDBEntry 191 entry2.MaxDuration = 100 // Shorter than the allowance period. 192 193 w1 := hdb.weightFunc(entry).Score() 194 w2 := hdb.weightFunc(entry2).Score() 195 if w1.Cmp(w2) <= 0 { 196 t.Error("Acceptable duration should have more weight", w1, w2) 197 } 198 } 199 200 // TestHostWeightStorageRemainingDifferences checks that the host with more 201 // collateral has more weight. 202 func TestHostWeightCollateralDifferences(t *testing.T) { 203 if testing.Short() { 204 t.SkipNow() 205 } 206 hdb := bareHostDB() 207 hdb.SetAllowance(DefaultTestAllowance) 208 209 entry := DefaultHostDBEntry 210 entry2 := DefaultHostDBEntry 211 entry2.Collateral = types.NewCurrency64(500).Mul(types.SiacoinPrecision) 212 213 w1 := hdb.weightFunc(entry).Score() 214 w2 := hdb.weightFunc(entry2).Score() 215 if w1.Cmp(w2) <= 0 { 216 t.Error("Larger collateral should have more weight") 217 } 218 } 219 220 // TestHostWeightStorageRemainingDifferences checks that hosts with less storage 221 // remaining have a lower weight. 222 func TestHostWeightStorageRemainingDifferences(t *testing.T) { 223 if testing.Short() { 224 t.SkipNow() 225 } 226 hdb := bareHostDB() 227 228 // Create two entries with different host keys. 229 entry := DefaultHostDBEntry 230 entry.PublicKey.Key = fastrand.Bytes(16) 231 entry2 := DefaultHostDBEntry 232 entry2.PublicKey.Key = fastrand.Bytes(16) 233 234 // The first entry has more storage remaining than the second. 235 entry.RemainingStorage = modules.DefaultAllowance.ExpectedStorage // 1e12 236 entry2.RemainingStorage = 1e3 237 238 // The entry with more storage should have the higher score. 239 w1 := hdb.weightFunc(entry).Score() 240 w2 := hdb.weightFunc(entry2).Score() 241 if w1.Cmp(w2) <= 0 { 242 t.Log(w1) 243 t.Log(w2) 244 t.Error("Larger storage remaining should have more weight") 245 } 246 247 // Change both entries to have the same remaining storage but add contractInfo 248 // to the HostDB to make it think that we already uploaded some data to one of 249 // the entries. This entry should have the higher score. 250 entry.RemainingStorage = 1e3 251 entry2.RemainingStorage = 1e3 252 hdb.knownContracts[entry.PublicKey.String()] = contractInfo{ 253 HostPublicKey: entry.PublicKey, 254 StoredData: hdb.allowance.ExpectedStorage, 255 } 256 w1 = hdb.weightFunc(entry).Score() 257 w2 = hdb.weightFunc(entry2).Score() 258 if w1.Cmp(w2) <= 0 { 259 t.Log(w1) 260 t.Log(w2) 261 t.Error("Entry with uploaded data should have higher score") 262 } 263 } 264 265 // TestHostWeightVersionDifferences checks that a host with an out of date 266 // version has a lower score than a host with a more recent version. 267 func TestHostWeightVersionDifferences(t *testing.T) { 268 if testing.Short() { 269 t.SkipNow() 270 } 271 hdb := bareHostDB() 272 273 entry := DefaultHostDBEntry 274 entry2 := DefaultHostDBEntry 275 entry2.Version = "v1.3.2" 276 w1 := hdb.weightFunc(entry) 277 w2 := hdb.weightFunc(entry2) 278 279 if w1.Score().Cmp(w2.Score()) <= 0 { 280 t.Log(w1) 281 t.Log(w2) 282 t.Error("Higher version should have more weight") 283 } 284 } 285 286 // TestHostWeightLifetimeDifferences checks that a host that has been on the 287 // chain for more time has a higher weight than a host that is newer. 288 func TestHostWeightLifetimeDifferences(t *testing.T) { 289 if testing.Short() { 290 t.SkipNow() 291 } 292 hdb := bareHostDB() 293 hdb.blockHeight = 10000 294 295 entry := DefaultHostDBEntry 296 entry2 := DefaultHostDBEntry 297 entry2.FirstSeen = 8100 298 w1 := hdb.weightFunc(entry).Score() 299 w2 := hdb.weightFunc(entry2).Score() 300 301 if w1.Cmp(w2) <= 0 { 302 t.Log(w1) 303 t.Log(w2) 304 t.Error("Been around longer should have more weight") 305 } 306 } 307 308 // TestHostWeightUptimeDifferences checks that hosts with poorer uptimes have 309 // lower weights. 310 func TestHostWeightUptimeDifferences(t *testing.T) { 311 if testing.Short() { 312 t.SkipNow() 313 } 314 hdb := bareHostDB() 315 hdb.blockHeight = 10000 316 317 entry := DefaultHostDBEntry 318 entry.ScanHistory = modules.HostDBScans{ 319 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 320 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 321 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 322 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 323 {Timestamp: time.Now().Add(time.Hour * -20), Success: true}, 324 } 325 326 entry2 := entry 327 entry2.ScanHistory = modules.HostDBScans{ 328 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 329 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 330 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 331 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 332 {Timestamp: time.Now().Add(time.Hour * -20), Success: false}, 333 } 334 w1 := hdb.weightFunc(entry).Score() 335 w2 := hdb.weightFunc(entry2).Score() 336 337 if w1.Cmp(w2) <= 0 { 338 t.Log(w1) 339 t.Log(w2) 340 t.Error("A host with recorded downtime should have a lower score") 341 } 342 } 343 344 // TestHostWeightUptimeDifferences2 checks that hosts with poorer uptimes have 345 // lower weights. 346 func TestHostWeightUptimeDifferences2(t *testing.T) { 347 t.Skip("Hostdb is not currently doing exponentiation on uptime") 348 if testing.Short() { 349 t.SkipNow() 350 } 351 hdb := bareHostDB() 352 hdb.blockHeight = 10000 353 354 entry := DefaultHostDBEntry 355 entry.ScanHistory = modules.HostDBScans{ 356 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 357 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 358 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 359 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 360 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 361 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 362 {Timestamp: time.Now().Add(time.Hour * -80), Success: false}, 363 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 364 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 365 {Timestamp: time.Now().Add(time.Hour * -20), Success: true}, 366 } 367 368 entry2 := entry 369 entry2.ScanHistory = modules.HostDBScans{ 370 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 371 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 372 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 373 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 374 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 375 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 376 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 377 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 378 {Timestamp: time.Now().Add(time.Hour * -40), Success: false}, 379 {Timestamp: time.Now().Add(time.Hour * -20), Success: true}, 380 } 381 w1 := hdb.weightFunc(entry).Score() 382 w2 := hdb.weightFunc(entry2).Score() 383 384 if w1.Cmp(w2) <= 0 { 385 t.Log(w1) 386 t.Log(w2) 387 t.Errorf("Downtime that's further in the past should be penalized less") 388 } 389 } 390 391 // TestHostWeightUptimeDifferences3 checks that hosts with poorer uptimes have 392 // lower weights. 393 func TestHostWeightUptimeDifferences3(t *testing.T) { 394 if testing.Short() { 395 t.SkipNow() 396 } 397 hdb := bareHostDB() 398 hdb.blockHeight = 10000 399 400 entry := DefaultHostDBEntry 401 entry.ScanHistory = modules.HostDBScans{ 402 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 403 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 404 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 405 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 406 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 407 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 408 {Timestamp: time.Now().Add(time.Hour * -80), Success: false}, 409 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 410 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 411 {Timestamp: time.Now().Add(time.Hour * -20), Success: true}, 412 } 413 414 entry2 := entry 415 entry2.ScanHistory = modules.HostDBScans{ 416 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 417 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 418 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 419 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 420 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 421 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 422 {Timestamp: time.Now().Add(time.Hour * -80), Success: false}, 423 {Timestamp: time.Now().Add(time.Hour * -60), Success: false}, 424 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 425 {Timestamp: time.Now().Add(time.Hour * -20), Success: true}, 426 } 427 w1 := hdb.weightFunc(entry).Score() 428 w2 := hdb.weightFunc(entry2).Score() 429 430 if w1.Cmp(w2) <= 0 { 431 t.Log(w1) 432 t.Log(w2) 433 t.Error("A host with longer downtime should have a lower score") 434 } 435 } 436 437 // TestHostWeightUptimeDifferences4 checks that hosts with poorer uptimes have 438 // lower weights. 439 func TestHostWeightUptimeDifferences4(t *testing.T) { 440 if testing.Short() { 441 t.SkipNow() 442 } 443 hdb := bareHostDB() 444 hdb.blockHeight = 10000 445 446 entry := DefaultHostDBEntry 447 entry.ScanHistory = modules.HostDBScans{ 448 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 449 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 450 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 451 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 452 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 453 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 454 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 455 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 456 {Timestamp: time.Now().Add(time.Hour * -40), Success: true}, 457 {Timestamp: time.Now().Add(time.Hour * -20), Success: false}, 458 } 459 460 entry2 := entry 461 entry2.ScanHistory = modules.HostDBScans{ 462 {Timestamp: time.Now().Add(time.Hour * -200), Success: true}, 463 {Timestamp: time.Now().Add(time.Hour * -180), Success: true}, 464 {Timestamp: time.Now().Add(time.Hour * -160), Success: true}, 465 {Timestamp: time.Now().Add(time.Hour * -140), Success: true}, 466 {Timestamp: time.Now().Add(time.Hour * -120), Success: true}, 467 {Timestamp: time.Now().Add(time.Hour * -100), Success: true}, 468 {Timestamp: time.Now().Add(time.Hour * -80), Success: true}, 469 {Timestamp: time.Now().Add(time.Hour * -60), Success: true}, 470 {Timestamp: time.Now().Add(time.Hour * -40), Success: false}, 471 {Timestamp: time.Now().Add(time.Hour * -20), Success: false}, 472 } 473 w1 := hdb.weightFunc(entry).Score() 474 w2 := hdb.weightFunc(entry2).Score() 475 476 if w1.Cmp(w2) <= 0 { 477 t.Log(w1) 478 t.Log(w2) 479 t.Error("longer tail downtime should have a lower score") 480 } 481 } 482 483 // TestHostWeightConstants checks a few relationships between the constants in 484 // the hostdb. 485 func TestHostWeightConstants(t *testing.T) { 486 // Becaues we no longer use a large base weight, we require that the 487 // collateral floor be higher than the price floor, and also that the 488 // collateralExponentiationSmall be larger than the 489 // priceExponentiationSmall. This protects most hosts from going anywhere 490 // near a 0 score. 491 if collateralFloor < priceFloor { 492 t.Error("Collateral floor should be greater than or equal to price floor") 493 } 494 if collateralExponentiationSmall < priceExponentiationSmall { 495 t.Error("small collateral exponentiation should be larger than small price exponentiation") 496 } 497 498 // Try a few hosts and make sure we always end up with a score that is 499 // greater than 1 million. 500 weight := calculateWeightFromUInt64Price(300, 100) 501 if weight.Cmp(types.NewCurrency64(1e9)) < 0 { 502 t.Error("weight is not sufficiently high for hosts") 503 } 504 weight = calculateWeightFromUInt64Price(1000, 1) 505 if weight.Cmp(types.NewCurrency64(1e9)) < 0 { 506 t.Error("weight is not sufficiently high for hosts") 507 } 508 509 hdb := bareHostDB() 510 hdb.SetAllowance(DefaultTestAllowance) 511 hdb.blockHeight = 0 512 513 entry := DefaultHostDBEntry 514 weight = hdb.weightFunc(entry).Score() 515 if weight.Cmp(types.NewCurrency64(1e9)) < 0 { 516 t.Error("weight is not sufficiently high for hosts") 517 } 518 }