gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/refcounter_test.go (about) 1 package proto 2 3 import ( 4 "encoding/binary" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "math" 9 "os" 10 "path/filepath" 11 "testing" 12 "time" 13 14 "gitlab.com/NebulousLabs/fastrand" 15 16 "gitlab.com/NebulousLabs/writeaheadlog" 17 18 "gitlab.com/SkynetLabs/skyd/skymodules" 19 20 "gitlab.com/NebulousLabs/errors" 21 22 "gitlab.com/SkynetLabs/skyd/build" 23 "go.sia.tech/siad/crypto" 24 "go.sia.tech/siad/types" 25 ) 26 27 // testWAL is the WAL instance we're going to use across this test. This would 28 // typically come from the calling functions. 29 var ( 30 testWAL, _ = newTestWAL() 31 32 // errTimeoutOnLock is returned when we timeout on getting a lock 33 errTimeoutOnLock = errors.New("timeout while acquiring a lock ") 34 ) 35 36 // managedStartUpdateWithTimeout acquires a lock, ensuring the caller is the only one 37 // currently allowed to perform updates on this refcounter file. Returns an 38 // error if the supplied timeout is <= 0 - use `callStartUpdate` instead. 39 func (rc *refCounter) managedStartUpdateWithTimeout(timeout time.Duration) error { 40 if timeout <= 0 { 41 return errors.New("non-positive timeout") 42 } 43 if ok := rc.muUpdate.TryLockTimed(timeout); !ok { 44 return errTimeoutOnLock 45 } 46 return rc.managedStartUpdate() 47 } 48 49 // TestRefCounterCount tests that the Count method always returns the correct 50 // counter value, either from disk or from in-mem storage. 51 func TestRefCounterCount(t *testing.T) { 52 if testing.Short() { 53 t.SkipNow() 54 } 55 t.Parallel() 56 57 // prepare a refcounter for the tests 58 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 59 sec := uint64(1) 60 val := uint16(21) 61 62 // set up the expected value on disk 63 err := writeVal(rc.filepath, sec, val) 64 if err != nil { 65 t.Fatal("Failed to write a count to disk:", err) 66 } 67 68 // verify we can read it correctly 69 rval, err := rc.callCount(sec) 70 if err != nil { 71 t.Fatal("Failed to read count from disk:", err) 72 } 73 if rval != val { 74 t.Fatalf("read wrong value from disk: expected %d, got %d", val, rval) 75 } 76 77 // check behaviour on bad sector number 78 _, err = rc.callCount(math.MaxInt64) 79 if !errors.Contains(err, ErrInvalidSectorNumber) { 80 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 81 } 82 83 // set up a temporary override 84 ov := uint16(12) 85 rc.newSectorCounts[sec] = ov 86 87 // verify we can read it correctly 88 rov, err := rc.callCount(sec) 89 if err != nil { 90 t.Fatal("Failed to read count from disk:", err) 91 } 92 if rov != ov { 93 t.Fatalf("read wrong override value from disk: expected %d, got %d", ov, rov) 94 } 95 } 96 97 // TestRefCounterAppend tests that the callDecrement method behaves correctly 98 func TestRefCounterAppend(t *testing.T) { 99 if testing.Short() { 100 t.SkipNow() 101 } 102 t.Parallel() 103 104 // prepare a refcounter for the tests 105 numSec := fastrand.Uint64n(10) 106 rc := testPrepareRefCounter(numSec, t) 107 stats, err := os.Stat(rc.filepath) 108 if err != nil { 109 t.Fatal("refCounter creation finished successfully but the file is not accessible:", err) 110 } 111 err = rc.callStartUpdate() 112 if err != nil { 113 t.Fatal("Failed to start an update session", err) 114 } 115 116 // test Append 117 u, err := rc.callAppend() 118 if err != nil { 119 t.Fatal("Failed to create an append update", err) 120 } 121 expectNumSec := numSec + 1 122 if rc.numSectors != expectNumSec { 123 t.Fatalf("append failed to properly increase the numSectors counter. Expected %d, got %d", expectNumSec, rc.numSectors) 124 } 125 if rc.newSectorCounts[rc.numSectors-1] != 1 { 126 t.Fatalf("append failed to properly initialise the new coutner. Expected 1, got %d", rc.newSectorCounts[rc.numSectors-1]) 127 } 128 129 // apply the update 130 err = rc.callCreateAndApplyTransaction(u) 131 if err != nil { 132 t.Fatal("Failed to apply append update:", err) 133 } 134 err = rc.callUpdateApplied() 135 if err != nil { 136 t.Fatal("Failed to finish the update session:", err) 137 } 138 139 // verify: we expect the file size to have grown by 2 bytes 140 endStats, err := os.Stat(rc.filepath) 141 if err != nil { 142 t.Fatal("Failed to get file stats:", err) 143 } 144 expectSize := stats.Size() + 2 145 actualSize := endStats.Size() 146 if actualSize != expectSize { 147 t.Fatalf("File size did not grow as expected. Expected size: %d, actual size: %d", expectSize, actualSize) 148 } 149 // verify that the added count has the right value 150 val, err := rc.readCount(rc.numSectors - 1) 151 if err != nil { 152 t.Fatal("Failed to read counter value after append:", err) 153 } 154 if val != 1 { 155 t.Fatalf("read wrong counter value from disk after append. Expected 1, got %d", val) 156 } 157 } 158 159 // TestRefCounterCreateAndApplyTransaction test that callCreateAndApplyTransaction 160 // panics and restores the original in-memory structures on a failure to apply 161 // updates. 162 func TestRefCounterCreateAndApplyTransaction(t *testing.T) { 163 if testing.Short() { 164 t.SkipNow() 165 } 166 t.Parallel() 167 168 // prepare a refcounter for the tests 169 numSec := 2 + fastrand.Uint64n(10) 170 rc := testPrepareRefCounter(numSec, t) 171 err := rc.callStartUpdate() 172 if err != nil { 173 t.Fatal("Failed to start an update session", err) 174 } 175 176 // add some valid updates 177 var updates []writeaheadlog.Update 178 u, err := rc.callAppend() 179 if err != nil { 180 t.Fatal("Failed to create an append update", err) 181 } 182 updates = append(updates, u) 183 u, err = rc.callIncrement(0) 184 if err != nil { 185 t.Fatal("Failed to create an increment update", err) 186 } 187 updates = append(updates, u) 188 189 // add an invalid update that will cause an error 190 u = writeaheadlog.Update{ 191 Name: "InvalidUpdate", 192 } 193 updates = append(updates, u) 194 195 // add another valid update that will change the rc.numSectors, which change 196 // must be reverted when we recover from the panic when applying the updates 197 u, err = rc.callDropSectors(1) 198 if err != nil { 199 t.Fatal("Failed to create a drop sectors update", err) 200 } 201 updates = append(updates, u) 202 203 // make sure we panic because of the invalid update and that we restore the 204 // count of sector number to the right value 205 defer func() { 206 // recover from a panic 207 if r := recover(); r == nil { 208 t.Fatal("Did not panic on an invalid update") 209 } 210 }() 211 212 // apply the updates 213 err = rc.callCreateAndApplyTransaction(updates...) 214 if err != nil { 215 t.Fatal("Did not panic on invalid update, only returned an err:", err) 216 } else { 217 t.Fatal("Applied an invalid update without panicking or an error") 218 } 219 } 220 221 // TestRefCounterDecrement tests that the callDecrement method behaves correctly 222 func TestRefCounterDecrement(t *testing.T) { 223 if testing.Short() { 224 t.SkipNow() 225 } 226 t.Parallel() 227 228 // prepare a refcounter for the tests 229 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 230 err := rc.callStartUpdate() 231 if err != nil { 232 t.Fatal("Failed to start an update session", err) 233 } 234 235 // test callDecrement 236 secIdx := rc.numSectors - 2 237 u, err := rc.callDecrement(secIdx) 238 if err != nil { 239 t.Fatal("Failed to create an decrement update:", err) 240 } 241 242 // verify: we expect the value to have decreased the base from 1 to 0 243 val, err := rc.readCount(secIdx) 244 if err != nil { 245 t.Fatal("Failed to read value after decrement:", err) 246 } 247 if val != 0 { 248 t.Fatalf("read wrong value after decrement. Expected %d, got %d", 2, val) 249 } 250 251 // check behaviour on bad sector number 252 _, err = rc.callDecrement(math.MaxInt64) 253 if !errors.Contains(err, ErrInvalidSectorNumber) { 254 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 255 } 256 257 // apply the update 258 err = rc.callCreateAndApplyTransaction(u) 259 if err != nil { 260 t.Fatal("Failed to apply decrement update:", err) 261 } 262 err = rc.callUpdateApplied() 263 if err != nil { 264 t.Fatal("Failed to finish the update session:", err) 265 } 266 // check the value on disk (the in-mem map is now gone) 267 val, err = rc.readCount(secIdx) 268 if err != nil { 269 t.Fatal("Failed to read value after decrement:", err) 270 } 271 if val != 0 { 272 t.Fatalf("read wrong value from disk after decrement. Expected 0, got %d", val) 273 } 274 } 275 276 // TestRefCounterDelete tests that the Delete method behaves correctly 277 func TestRefCounterDelete(t *testing.T) { 278 if testing.Short() { 279 t.SkipNow() 280 } 281 t.Parallel() 282 283 // prepare a refcounter for the tests 284 rc := testPrepareRefCounter(fastrand.Uint64n(10), t) 285 err := rc.callStartUpdate() 286 if err != nil { 287 t.Fatal("Failed to start an update session", err) 288 } 289 290 // delete the ref counter 291 u, err := rc.callDeleteRefCounter() 292 if err != nil { 293 t.Fatal("Failed to create a delete update", err) 294 } 295 296 // apply the update 297 err = rc.callCreateAndApplyTransaction(u) 298 if err != nil { 299 t.Fatal("Failed to apply a delete update:", err) 300 } 301 err = rc.callUpdateApplied() 302 if err != nil { 303 t.Fatal("Failed to finish the update session:", err) 304 } 305 306 // verify 307 _, err = os.Stat(rc.filepath) 308 if !os.IsNotExist(err) { 309 t.Fatal("refCounter deletion finished successfully but the file is still on disk", err) 310 } 311 } 312 313 // TestRefCounterDropSectors tests that the callDropSectors method behaves 314 // correctly and the file's size is properly adjusted 315 func TestRefCounterDropSectors(t *testing.T) { 316 if testing.Short() { 317 t.SkipNow() 318 } 319 t.Parallel() 320 321 // prepare a refcounter for the tests 322 numSec := 2 + fastrand.Uint64n(10) 323 rc := testPrepareRefCounter(numSec, t) 324 stats, err := os.Stat(rc.filepath) 325 if err != nil { 326 t.Fatal("refCounter creation finished successfully but the file is not accessible:", err) 327 } 328 err = rc.callStartUpdate() 329 if err != nil { 330 t.Fatal("Failed to start an update session", err) 331 } 332 var updates []writeaheadlog.Update 333 // update both counters we intend to drop 334 secIdx1 := rc.numSectors - 1 335 secIdx2 := rc.numSectors - 2 336 u, err := rc.callIncrement(secIdx1) 337 if err != nil { 338 t.Fatal("Failed to create truncate update:", err) 339 } 340 updates = append(updates, u) 341 u, err = rc.callIncrement(secIdx2) 342 if err != nil { 343 t.Fatal("Failed to create truncate update:", err) 344 } 345 updates = append(updates, u) 346 347 // check behaviour on bad sector number 348 // (trying to drop more sectors than we have) 349 _, err = rc.callDropSectors(math.MaxInt64) 350 if !errors.Contains(err, ErrInvalidSectorNumber) { 351 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 352 } 353 354 // test callDropSectors by dropping two counters 355 u, err = rc.callDropSectors(2) 356 if err != nil { 357 t.Fatal("Failed to create truncate update:", err) 358 } 359 updates = append(updates, u) 360 expectNumSec := numSec - 2 361 if rc.numSectors != expectNumSec { 362 t.Fatalf("wrong number of counters after Truncate. Expected %d, got %d", expectNumSec, rc.numSectors) 363 } 364 365 // apply the update 366 err = rc.callCreateAndApplyTransaction(updates...) 367 if err != nil { 368 t.Fatal("Failed to apply truncate update:", err) 369 } 370 err = rc.callUpdateApplied() 371 if err != nil { 372 t.Fatal("Failed to finish the update session:", err) 373 } 374 375 //verify: we expect the file size to have shrunk with 2*2 bytes 376 endStats, err := os.Stat(rc.filepath) 377 if err != nil { 378 t.Fatal("Failed to get file stats:", err) 379 } 380 expectSize := stats.Size() - 4 381 actualSize := endStats.Size() 382 if actualSize != expectSize { 383 t.Fatalf("File size did not shrink as expected. Expected size: %d, actual size: %d", expectSize, actualSize) 384 } 385 // verify that we cannot read the values of the dropped counters 386 _, err = rc.readCount(secIdx1) 387 if !errors.Contains(err, ErrInvalidSectorNumber) { 388 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 389 } 390 _, err = rc.readCount(secIdx2) 391 if !errors.Contains(err, ErrInvalidSectorNumber) { 392 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 393 } 394 } 395 396 // TestRefCounterIncrement tests that the callIncrement method behaves correctly 397 func TestRefCounterIncrement(t *testing.T) { 398 if testing.Short() { 399 t.SkipNow() 400 } 401 t.Parallel() 402 403 // prepare a refcounter for the tests 404 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 405 err := rc.callStartUpdate() 406 if err != nil { 407 t.Fatal("Failed to start an update session", err) 408 } 409 410 // test callIncrement 411 secIdx := rc.numSectors - 2 412 u, err := rc.callIncrement(secIdx) 413 if err != nil { 414 t.Fatal("Failed to create an increment update:", err) 415 } 416 417 // verify that the value of the counter has increased by 1 and is currently 2 418 val, err := rc.readCount(secIdx) 419 if err != nil { 420 t.Fatal("Failed to read value after increment:", err) 421 } 422 if val != 2 { 423 t.Fatalf("read wrong value after increment. Expected 2, got %d", val) 424 } 425 426 // check behaviour on bad sector number 427 _, err = rc.callIncrement(math.MaxInt64) 428 if !errors.Contains(err, ErrInvalidSectorNumber) { 429 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 430 } 431 432 // apply the update 433 err = rc.callCreateAndApplyTransaction(u) 434 if err != nil { 435 t.Fatal("Failed to apply increment update:", err) 436 } 437 err = rc.callUpdateApplied() 438 if err != nil { 439 t.Fatal("Failed to finish the update session:", err) 440 } 441 // check the value on disk (the in-mem map is now gone) 442 val, err = rc.readCount(secIdx) 443 if err != nil { 444 t.Fatal("Failed to read value after increment:", err) 445 } 446 if val != 2 { 447 t.Fatalf("read wrong value from disk after increment. Expected 2, got %d", val) 448 } 449 } 450 451 // TestRefCounterLoad specifically tests refcounter's Load method 452 func TestRefCounterLoad(t *testing.T) { 453 if testing.Short() { 454 t.SkipNow() 455 } 456 t.Parallel() 457 458 // prepare a refcounter to load 459 rc := testPrepareRefCounter(fastrand.Uint64n(10), t) 460 461 // happy case 462 _, err := loadRefCounter(rc.filepath, testWAL) 463 if err != nil { 464 t.Fatal("Failed to load refcounter:", err) 465 } 466 467 // fails with os.ErrNotExist for a non-existent file 468 _, err = loadRefCounter("there-is-no-such-file.rc", testWAL) 469 if !errors.Contains(err, ErrRefCounterNotExist) { 470 t.Fatal("Expected ErrRefCounterNotExist, got something else:", err) 471 } 472 } 473 474 // TestRefCounterLoadInvalidHeader checks that loading a refcounters file with 475 // invalid header fails. 476 func TestRefCounterLoadInvalidHeader(t *testing.T) { 477 if testing.Short() { 478 t.SkipNow() 479 } 480 t.Parallel() 481 482 // prepare 483 cid := types.FileContractID(crypto.HashBytes([]byte("contractId"))) 484 d := build.TempDir(t.Name()) 485 err := os.MkdirAll(d, skymodules.DefaultDirPerm) 486 if err != nil { 487 t.Fatal("Failed to create test directory:", err) 488 } 489 path := filepath.Join(d, cid.String()+refCounterExtension) 490 491 // Create a file that contains a corrupted header. This basically means 492 // that the file is too short to have the entire header in there. 493 f, err := os.Create(path) 494 if err != nil { 495 t.Fatal("Failed to create test file:", err) 496 } 497 498 // The version number is 8 bytes. We'll only write 4. 499 if _, err = f.Write(fastrand.Bytes(4)); err != nil { 500 err = errors.Compose(err, f.Close()) 501 t.Fatal("Failed to write to test file:", err) 502 } 503 if err := f.Close(); err != nil { 504 t.Fatal(err) 505 } 506 507 // Make sure we fail to load from that file and that we fail with the right 508 // error 509 _, err = loadRefCounter(path, testWAL) 510 if !errors.Contains(err, io.EOF) { 511 t.Fatal(fmt.Sprintf("Should not be able to read file with bad header, expected `%s` error, got:", io.EOF.Error()), err) 512 } 513 } 514 515 // TestRefCounterLoadInvalidVersion checks that loading a refcounters file 516 // with invalid version fails. 517 func TestRefCounterLoadInvalidVersion(t *testing.T) { 518 if testing.Short() { 519 t.SkipNow() 520 } 521 t.Parallel() 522 523 // prepare 524 cid := types.FileContractID(crypto.HashBytes([]byte("contractId"))) 525 d := build.TempDir(t.Name()) 526 err := os.MkdirAll(d, skymodules.DefaultDirPerm) 527 if err != nil { 528 t.Fatal("Failed to create test directory:", err) 529 } 530 path := filepath.Join(d, cid.String()+refCounterExtension) 531 532 // create a file with a header that encodes a bad version number 533 f, err := os.Create(path) 534 if err != nil { 535 t.Fatal("Failed to create test file:", err) 536 } 537 defer func() { 538 if err := f.Close(); err != nil { 539 t.Fatal(err) 540 } 541 }() 542 543 // The first 8 bytes are the version number. Write down an invalid one 544 // followed 4 counters (another 8 bytes). 545 _, err = f.Write(fastrand.Bytes(16)) 546 if err != nil { 547 t.Fatal("Failed to write to test file:", err) 548 } 549 550 // ensure that we cannot load it and we return the correct error 551 _, err = loadRefCounter(path, testWAL) 552 if !errors.Contains(err, ErrInvalidVersion) { 553 t.Fatal(fmt.Sprintf("Should not be able to read file with wrong version, expected `%s` error, got:", ErrInvalidVersion.Error()), err) 554 } 555 } 556 557 // TestRefCounterSetCount tests that the callSetCount method behaves correctly 558 func TestRefCounterSetCount(t *testing.T) { 559 if testing.Short() { 560 t.SkipNow() 561 } 562 t.Parallel() 563 564 // prepare a refcounter for the tests 565 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 566 err := rc.callStartUpdate() 567 if err != nil { 568 t.Fatal("Failed to start an update session", err) 569 } 570 571 // test callSetCount on an existing sector counter 572 oldNumSec := rc.numSectors 573 secIdx := rc.numSectors - 2 574 count := uint16(fastrand.Intn(10_000)) 575 u, err := rc.callSetCount(secIdx, count) 576 if err != nil { 577 t.Fatal("Failed to create a set count update:", err) 578 } 579 // verify that the number of sectors did not change 580 if rc.numSectors != oldNumSec { 581 t.Fatalf("wrong number of sectors after setting the value of an existing sector. Expected %d number of sectors, got %d", oldNumSec, rc.numSectors) 582 } 583 // verify that the counter value was correctly set 584 val, err := rc.readCount(secIdx) 585 if err != nil { 586 t.Fatal("Failed to read value after set count:", err) 587 } 588 if val != count { 589 t.Fatalf("read wrong value after increment. Expected %d, got %d", count, val) 590 } 591 // apply the update 592 err = rc.callCreateAndApplyTransaction(u) 593 if err != nil { 594 t.Fatal("Failed to apply a set count update:", err) 595 } 596 err = rc.callUpdateApplied() 597 if err != nil { 598 t.Fatal("Failed to finish the update session:", err) 599 } 600 // check the value on disk (the in-mem map is now gone) 601 val, err = rc.readCount(secIdx) 602 if err != nil { 603 t.Fatal("Failed to read value after set count:", err) 604 } 605 if val != count { 606 t.Fatalf("read wrong value from disk after set count. Expected %d, got %d", count, val) 607 } 608 609 // test callSetCount on a sector beyond the current last sector 610 err = rc.callStartUpdate() 611 if err != nil { 612 t.Fatal("Failed to start an update session", err) 613 } 614 oldNumSec = rc.numSectors 615 secIdx = rc.numSectors + 2 616 count = uint16(fastrand.Intn(10_000)) 617 u, err = rc.callSetCount(secIdx, count) 618 if err != nil { 619 t.Fatal("Failed to create a set count update:", err) 620 } 621 // verify that the number of sectors increased by 3 622 if rc.numSectors != oldNumSec+3 { 623 t.Fatalf("wrong number of sectors after setting the value of a sector beyond the current last sector. Expected %d number of sectors, got %d", oldNumSec+3, rc.numSectors) 624 } 625 // verify that the counter value was correctly set 626 val, err = rc.readCount(secIdx) 627 if err != nil { 628 t.Fatal("Failed to read value after set count:", err) 629 } 630 if val != count { 631 t.Fatalf("read wrong value after increment. Expected %d, got %d", count, val) 632 } 633 // apply the update 634 err = rc.callCreateAndApplyTransaction(u) 635 if err != nil { 636 t.Fatal("Failed to apply a set count update:", err) 637 } 638 err = rc.callUpdateApplied() 639 if err != nil { 640 t.Fatal("Failed to finish the update session:", err) 641 } 642 // check the value on disk (the in-mem map is now gone) 643 val, err = rc.readCount(secIdx) 644 if err != nil { 645 t.Fatal("Failed to read value after set count:", err) 646 } 647 if val != count { 648 t.Fatalf("read wrong value from disk after set count. Expected %d, got %d", count, val) 649 } 650 } 651 652 // TestRefCounterStartUpdate tests that the callStartUpdate method respects the 653 // timeout limits set for it. 654 func TestRefCounterStartUpdate(t *testing.T) { 655 if testing.Short() { 656 t.SkipNow() 657 } 658 t.Parallel() 659 660 // prepare a refcounter for the tests 661 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 662 err := rc.callStartUpdate() 663 if err != nil { 664 t.Fatal("Failed to start an update session", err) 665 } 666 667 // try to lock again with a timeout and see the timout trigger 668 locked := make(chan error) 669 timeout := time.After(time.Second) 670 go func() { 671 locked <- rc.managedStartUpdateWithTimeout(500 * time.Millisecond) 672 }() 673 select { 674 case err = <-locked: 675 if !errors.Contains(err, errTimeoutOnLock) { 676 t.Fatal("Failed to timeout, expected errTimeoutOnLock, got:", err) 677 } 678 case <-timeout: 679 t.Fatal("Failed to timeout, missed the deadline.") 680 } 681 682 err = rc.callUpdateApplied() 683 if err != nil { 684 t.Fatal("Failed to finish the update session:", err) 685 } 686 } 687 688 // TestRefCounterSwap tests that the callSwap method results in correct values 689 func TestRefCounterSwap(t *testing.T) { 690 if testing.Short() { 691 t.SkipNow() 692 } 693 t.Parallel() 694 695 // prepare a refcounter for the tests 696 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 697 var updates []writeaheadlog.Update 698 err := rc.callStartUpdate() 699 if err != nil { 700 t.Fatal("Failed to start an update session", err) 701 } 702 703 // increment one of the sectors, so we can tell the values apart 704 u, err := rc.callIncrement(rc.numSectors - 1) 705 if err != nil { 706 t.Fatal("Failed to create increment update", err) 707 } 708 updates = append(updates, u) 709 710 // test callSwap 711 us, err := rc.callSwap(rc.numSectors-2, rc.numSectors-1) 712 updates = append(updates, us...) 713 if err != nil { 714 t.Fatal("Failed to create swap update", err) 715 } 716 var v1, v2 uint16 717 v1, err = rc.readCount(rc.numSectors - 2) 718 if err != nil { 719 t.Fatal("Failed to read value after swap", err) 720 } 721 v2, err = rc.readCount(rc.numSectors - 1) 722 if err != nil { 723 t.Fatal("Failed to read value after swap", err) 724 } 725 if v1 != 2 || v2 != 1 { 726 t.Fatalf("read wrong value after swap. Expected %d and %d, got %d and %d", 2, 1, v1, v2) 727 } 728 729 // check behaviour on bad sector number 730 _, err = rc.callSwap(math.MaxInt64, 0) 731 if !errors.Contains(err, ErrInvalidSectorNumber) { 732 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 733 } 734 735 // apply the updates and check the values again 736 err = rc.callCreateAndApplyTransaction(updates...) 737 if err != nil { 738 t.Fatal("Failed to apply updates", err) 739 } 740 err = rc.callUpdateApplied() 741 if err != nil { 742 t.Fatal("Failed to finish the update session:", err) 743 } 744 // verify values on disk (the in-mem map is now gone) 745 v1, err = rc.readCount(rc.numSectors - 2) 746 if err != nil { 747 t.Fatal("Failed to read value from disk after swap", err) 748 } 749 v2, err = rc.readCount(rc.numSectors - 1) 750 if err != nil { 751 t.Fatal("Failed to read value from disk after swap", err) 752 } 753 if v1 != 2 || v2 != 1 { 754 t.Fatalf("read wrong value from disk after swap. Expected %d and %d, got %d and %d", 2, 1, v1, v2) 755 } 756 } 757 758 // TestRefCounterUpdateApplied tests that the callUpdateApplied method cleans up 759 // after itself 760 func TestRefCounterUpdateApplied(t *testing.T) { 761 if testing.Short() { 762 t.SkipNow() 763 } 764 t.Parallel() 765 766 // prepare a refcounter for the tests 767 rc := testPrepareRefCounter(2+fastrand.Uint64n(10), t) 768 var updates []writeaheadlog.Update 769 err := rc.callStartUpdate() 770 if err != nil { 771 t.Fatal("Failed to start an update session", err) 772 } 773 774 // generate some update 775 secIdx := rc.numSectors - 1 776 u, err := rc.callIncrement(secIdx) 777 if err != nil { 778 t.Fatal("Failed to create increment update", err) 779 } 780 updates = append(updates, u) 781 // verify that the override map reflects the update 782 if _, ok := rc.newSectorCounts[secIdx]; !ok { 783 t.Fatal("Failed to update the in-mem override map.") 784 } 785 786 // apply the updates and check the values again 787 err = rc.callCreateAndApplyTransaction(updates...) 788 if err != nil { 789 t.Fatal("Failed to apply updates", err) 790 } 791 err = rc.callUpdateApplied() 792 if err != nil { 793 t.Fatal("Failed to finish the update session:", err) 794 } 795 // verify that the in-mem override map is now cleaned up 796 if len(rc.newSectorCounts) != 0 { 797 t.Fatalf("updateApplied failed to clean up the newSectorCounts. Expected len 0, got %d", len(rc.newSectorCounts)) 798 } 799 } 800 801 // TestRefCounterUpdateSessionConstraints ensures that callStartUpdate() and callUpdateApplied() 802 // enforce all applicable restrictions to update creation and execution 803 func TestRefCounterUpdateSessionConstraints(t *testing.T) { 804 if testing.Short() { 805 t.SkipNow() 806 } 807 t.Parallel() 808 809 // prepare a refcounter for the tests 810 rc := testPrepareRefCounter(fastrand.Uint64n(10), t) 811 812 var u writeaheadlog.Update 813 // make sure we cannot create updates outside of an update session 814 _, err1 := rc.callAppend() 815 _, err2 := rc.callDecrement(1) 816 _, err3 := rc.callDeleteRefCounter() 817 _, err4 := rc.callDropSectors(1) 818 _, err5 := rc.callIncrement(1) 819 _, err6 := rc.callSwap(1, 2) 820 err7 := rc.callCreateAndApplyTransaction(u) 821 for i, err := range []error{err1, err2, err3, err4, err5, err6, err7} { 822 if !errors.Contains(err, ErrUpdateWithoutUpdateSession) { 823 t.Fatalf("err%v: expected %v but was %v", i+1, ErrUpdateWithoutUpdateSession, err) 824 } 825 } 826 827 // start an update session 828 err := rc.callStartUpdate() 829 if err != nil { 830 t.Fatal("Failed to start an update session", err) 831 } 832 // delete the ref counter 833 u, err = rc.callDeleteRefCounter() 834 if err != nil { 835 t.Fatal("Failed to create a delete update", err) 836 } 837 // make sure we cannot create any updates after a deletion has been triggered 838 _, err1 = rc.callAppend() 839 _, err2 = rc.callDecrement(1) 840 _, err3 = rc.callDeleteRefCounter() 841 _, err4 = rc.callDropSectors(1) 842 _, err5 = rc.callIncrement(1) 843 _, err6 = rc.callSwap(1, 2) 844 for i, err := range []error{err1, err2, err3, err4, err5, err6} { 845 if !errors.Contains(err, ErrUpdateAfterDelete) { 846 t.Fatalf("err%v: expected %v but was %v", i+1, ErrUpdateAfterDelete, err) 847 } 848 } 849 850 // apply the update 851 err = rc.callCreateAndApplyTransaction(u) 852 if err != nil { 853 t.Fatal("Failed to apply a delete update:", err) 854 } 855 err = rc.callUpdateApplied() 856 if err != nil { 857 t.Fatal("Failed to finish the update session:", err) 858 } 859 860 // make sure we cannot start an update session on a deleted counter 861 if err = rc.callStartUpdate(); !errors.Contains(err, ErrUpdateAfterDelete) { 862 t.Fatal("Failed to prevent an update creation after a deletion", err) 863 } 864 } 865 866 // TestRefCounterWALFunctions tests refCounter's functions for creating and 867 // reading WAL updates 868 func TestRefCounterWALFunctions(t *testing.T) { 869 t.Parallel() 870 871 // test creating and reading updates 872 wpath := "test/writtenPath" 873 wsec := uint64(2) 874 wval := uint16(12) 875 u := createWriteAtUpdate(wpath, wsec, wval) 876 rpath, rsec, rval, err := readWriteAtUpdate(u) 877 if err != nil { 878 t.Fatal("Failed to read writeAt update:", err) 879 } 880 if wpath != rpath || wsec != rsec || wval != rval { 881 t.Fatalf("wrong values read from WriteAt update. Expected %s, %d, %d, found %s, %d, %d", wpath, wsec, wval, rpath, rsec, rval) 882 } 883 884 u = createTruncateUpdate(wpath, wsec) 885 rpath, rsec, err = readTruncateUpdate(u) 886 if err != nil { 887 t.Fatal("Failed to read a truncate update:", err) 888 } 889 if wpath != rpath || wsec != rsec { 890 t.Fatalf("wrong values read from Truncate update. Expected %s, %d found %s, %d", wpath, wsec, rpath, rsec) 891 } 892 } 893 894 // TestRefCounterNumSectorsUnderflow tests for and guards against an NDF that 895 // can happen in various methods when numSectors is zero and we check the sector 896 // index to be read against numSectors-1. 897 func TestRefCounterNumSectorsUnderflow(t *testing.T) { 898 if testing.Short() { 899 t.SkipNow() 900 } 901 t.Parallel() 902 903 // prepare a refcounter with zero sectors for the tests 904 rc := testPrepareRefCounter(0, t) 905 906 // try to read the nonexistent sector with index 0 907 _, err := rc.readCount(0) 908 // when checking if the sector we want to read is valid we compare it to 909 // numSectors. If we do it by comparing `secNum > numSectors - 1` we will 910 // hit an underflow which will result in the check passing and us getting 911 // an EOF error instead of the correct ErrInvalidSectorNumber 912 if errors.Contains(err, io.EOF) { 913 t.Fatal("Unexpected EOF error instead of ErrInvalidSectorNumber. Underflow!") 914 } 915 // we should get an ErrInvalidSectorNumber 916 if !errors.Contains(err, ErrInvalidSectorNumber) { 917 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 918 } 919 920 err = rc.callStartUpdate() 921 if err != nil { 922 t.Fatal("Failed to initiate an update session:", err) 923 } 924 925 // check for the same underflow during callDecrement 926 _, err = rc.callDecrement(0) 927 if errors.Contains(err, io.EOF) { 928 t.Fatal("Unexpected EOF error instead of ErrInvalidSectorNumber. Underflow!") 929 } 930 if !errors.Contains(err, ErrInvalidSectorNumber) { 931 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 932 } 933 934 // check for the same underflow during callIncrement 935 _, err = rc.callIncrement(0) 936 if errors.Contains(err, io.EOF) { 937 t.Fatal("Unexpected EOF error instead of ErrInvalidSectorNumber. Underflow!") 938 } 939 if !errors.Contains(err, ErrInvalidSectorNumber) { 940 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 941 } 942 943 // check for the same underflow during callSwap 944 _, err1 := rc.callSwap(0, 1) 945 _, err2 := rc.callSwap(1, 0) 946 err = errors.Compose(err1, err2) 947 if errors.Contains(err, io.EOF) { 948 t.Fatal("Unexpected EOF error instead of ErrInvalidSectorNumber. Underflow!") 949 } 950 if !errors.Contains(err, ErrInvalidSectorNumber) { 951 t.Fatal("Expected ErrInvalidSectorNumber, got:", err) 952 } 953 954 // cleanup the update session 955 err = rc.callUpdateApplied() 956 if err != nil { 957 t.Fatal("Failed to wrap up an empty update session:", err) 958 } 959 } 960 961 // newTestWal is a helper method to create a WAL for testing. 962 func newTestWAL() (*writeaheadlog.WAL, string) { 963 // Create the wal. 964 wd := filepath.Join(os.TempDir(), "rc-wals") 965 if err := os.MkdirAll(wd, skymodules.DefaultDirPerm); err != nil { 966 panic(err) 967 } 968 walFilePath := filepath.Join(wd, hex.EncodeToString(fastrand.Bytes(8))) 969 _, wal, err := writeaheadlog.New(walFilePath) 970 if err != nil { 971 panic(err) 972 } 973 return wal, walFilePath 974 } 975 976 // testPrepareRefCounter is a helper that creates a refcounter and fails the 977 // test if that is not successful 978 func testPrepareRefCounter(numSec uint64, t *testing.T) *refCounter { 979 tcid := types.FileContractID(crypto.HashBytes([]byte("contractId"))) 980 td := build.TempDir(t.Name()) 981 err := os.MkdirAll(td, skymodules.DefaultDirPerm) 982 if err != nil { 983 t.Fatal("Failed to create test directory:", err) 984 } 985 path := filepath.Join(td, tcid.String()+refCounterExtension) 986 // create a ref counter 987 rc, err := newRefCounter(path, numSec, testWAL) 988 if err != nil { 989 t.Fatal("Failed to create a reference counter:", err) 990 } 991 return rc 992 } 993 994 // writeVal is a helper method that writes a certain counter value to disk. This 995 // method does not do any validations or checks, the caller must make certain 996 // that the input parameters are valid. 997 func writeVal(path string, secIdx uint64, val uint16) (err error) { 998 f, err := os.OpenFile(path, os.O_RDWR, skymodules.DefaultFilePerm) 999 if err != nil { 1000 return errors.AddContext(err, "failed to open refcounter file") 1001 } 1002 defer func() { 1003 err = errors.Compose(err, f.Close()) 1004 }() 1005 var b u16 1006 binary.LittleEndian.PutUint16(b[:], val) 1007 if _, err = f.WriteAt(b[:], int64(offset(secIdx))); err != nil { 1008 return errors.AddContext(err, "failed to write to refcounter file") 1009 } 1010 return nil 1011 }