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  }