gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/update_test.go (about)

     1  package contractor
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  
    11  	"gitlab.com/SkynetLabs/skyd/build"
    12  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    13  	"gitlab.com/SkynetLabs/skyd/skymodules"
    14  	"go.sia.tech/siad/crypto"
    15  	"go.sia.tech/siad/modules"
    16  	"go.sia.tech/siad/types"
    17  )
    18  
    19  // TestContractsToDelete is a unit test for contractsToDelete.
    20  func TestContractsToDelete(t *testing.T) {
    21  	t.Parallel()
    22  
    23  	tests := []struct {
    24  		numContracts int
    25  		toDelete     int
    26  	}{
    27  		{
    28  			numContracts: 100,
    29  			toDelete:     1,
    30  		},
    31  		{
    32  			numContracts: 200,
    33  			toDelete:     2,
    34  		},
    35  		{
    36  			numContracts: 199,
    37  			toDelete:     1,
    38  		},
    39  		{
    40  			numContracts: 99,
    41  			toDelete:     1,
    42  		},
    43  		{
    44  			numContracts: 1,
    45  			toDelete:     1,
    46  		},
    47  		{
    48  			numContracts: 0,
    49  			toDelete:     0,
    50  		},
    51  	}
    52  	for _, test := range tests {
    53  		toDelete := contractsToDelete(test.numContracts)
    54  		if toDelete != test.toDelete {
    55  			t.Errorf("unexpted value %v != %v", toDelete, test.toDelete)
    56  		}
    57  	}
    58  }
    59  
    60  // TestIntegrationAutoRenew tests that contracts are automatically renewed at
    61  // the expected block height.
    62  func TestIntegrationAutoRenew(t *testing.T) {
    63  	if testing.Short() {
    64  		t.SkipNow()
    65  	}
    66  	t.Parallel()
    67  	// create testing trio
    68  	_, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{})
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	defer tryClose(cf, t)
    73  
    74  	// form a contract with the host
    75  	a := skymodules.Allowance{
    76  		Funds:              types.SiacoinPrecision.Mul64(100), // 100 SC
    77  		Hosts:              1,
    78  		Period:             50,
    79  		RenewWindow:        10,
    80  		ExpectedStorage:    skymodules.DefaultAllowance.ExpectedStorage,
    81  		ExpectedUpload:     skymodules.DefaultAllowance.ExpectedUpload,
    82  		ExpectedDownload:   skymodules.DefaultAllowance.ExpectedDownload,
    83  		ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy,
    84  		MaxPeriodChurn:     skymodules.DefaultAllowance.MaxPeriodChurn,
    85  	}
    86  	err = c.SetAllowance(a)
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	numRetries := 0
    91  	err = build.Retry(100, 100*time.Millisecond, func() error {
    92  		if numRetries%10 == 0 {
    93  			if _, err := m.AddBlock(); err != nil {
    94  				return err
    95  			}
    96  		}
    97  		numRetries++
    98  		if len(c.Contracts()) == 0 {
    99  			return errors.New("contracts were not formed")
   100  		}
   101  		return nil
   102  	})
   103  	if err != nil {
   104  		t.Fatal(err)
   105  	}
   106  	contract := c.Contracts()[0]
   107  
   108  	// Grab the editor in a retry statement, because there is a race condition
   109  	// between the contract set having contracts in it and the editor having
   110  	// access to the new contract.
   111  	var editor Editor
   112  	err = build.Retry(100, 100*time.Millisecond, func() error {
   113  		editor, err = c.Editor(contract.HostPublicKey, nil)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		return nil
   118  	})
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	data := fastrand.Bytes(int(modules.SectorSize))
   124  	// insert the sector
   125  	_, err = editor.Upload(data)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	err = editor.Close()
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  
   134  	// mine until we enter the renew window
   135  	renewHeight := contract.EndHeight - c.allowance.RenewWindow
   136  	for c.blockHeight < renewHeight {
   137  		_, err := m.AddBlock()
   138  		if err != nil {
   139  			t.Fatal(err)
   140  		}
   141  	}
   142  	// wait for goroutine in ProcessConsensusChange to finish
   143  	time.Sleep(100 * time.Millisecond)
   144  	c.maintenanceLock.Lock()
   145  	c.maintenanceLock.Unlock()
   146  
   147  	// check renewed contract
   148  	contract = c.Contracts()[0]
   149  	endHeight := c.contractEndHeight()
   150  	if contract.EndHeight != endHeight {
   151  		t.Fatalf("Wrong end height, expected %v got %v\n", endHeight, contract.EndHeight)
   152  	}
   153  }
   154  
   155  // TestIntegrationRenewInvalidate tests that editors and downloaders are
   156  // properly invalidated when a renew is queued.
   157  func TestIntegrationRenewInvalidate(t *testing.T) {
   158  	if testing.Short() {
   159  		t.SkipNow()
   160  	}
   161  	t.Parallel()
   162  	// create testing trio
   163  	_, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{})
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  	defer tryClose(cf, t)
   168  
   169  	// form a contract with the host
   170  	a := skymodules.Allowance{
   171  		Funds:              types.SiacoinPrecision.Mul64(100), // 100 SC
   172  		Hosts:              1,
   173  		Period:             50,
   174  		RenewWindow:        10,
   175  		ExpectedStorage:    skymodules.DefaultAllowance.ExpectedStorage,
   176  		ExpectedUpload:     skymodules.DefaultAllowance.ExpectedUpload,
   177  		ExpectedDownload:   skymodules.DefaultAllowance.ExpectedDownload,
   178  		ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy,
   179  		MaxPeriodChurn:     skymodules.DefaultAllowance.MaxPeriodChurn,
   180  	}
   181  	err = c.SetAllowance(a)
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	numRetries := 0
   186  	err = build.Retry(100, 100*time.Millisecond, func() error {
   187  		if numRetries%10 == 0 {
   188  			if _, err := m.AddBlock(); err != nil {
   189  				return err
   190  			}
   191  		}
   192  		numRetries++
   193  		// Check for number of contracts and number of pubKeys as there is a
   194  		// slight delay between the contract being added to the contract set and
   195  		// the pubkey being added to the contractor map
   196  		c.mu.Lock()
   197  		numPubKeys := len(c.pubKeysToContractID)
   198  		c.mu.Unlock()
   199  		numContracts := len(c.Contracts())
   200  		if numContracts != 1 {
   201  			return fmt.Errorf("Expected 1 contracts, found %v", numContracts)
   202  		}
   203  		if numPubKeys != 1 {
   204  			return fmt.Errorf("Expected 1 pubkey, found %v", numPubKeys)
   205  		}
   206  		return nil
   207  	})
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	contract := c.Contracts()[0]
   212  
   213  	// revise the contract
   214  	editor, err := c.Editor(contract.HostPublicKey, nil)
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	data := fastrand.Bytes(int(modules.SectorSize))
   219  	// insert the sector
   220  	_, err = editor.Upload(data)
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  
   225  	// mine until we enter the renew window; the editor should be invalidated
   226  	renewHeight := contract.EndHeight - c.allowance.RenewWindow
   227  	for c.blockHeight < renewHeight {
   228  		_, err := m.AddBlock()
   229  		if err != nil {
   230  			t.Fatal(err)
   231  		}
   232  	}
   233  	// wait for goroutine in ProcessConsensusChange to finish
   234  	time.Sleep(100 * time.Millisecond)
   235  	c.maintenanceLock.Lock()
   236  	c.maintenanceLock.Unlock()
   237  
   238  	// check renewed contract
   239  	contract = c.Contracts()[0]
   240  	endHeight := c.contractEndHeight()
   241  	c.mu.Lock()
   242  	if contract.EndHeight != endHeight {
   243  		t.Fatalf("Wrong end height, expected %v got %v\n", endHeight, contract.EndHeight)
   244  	}
   245  	c.mu.Unlock()
   246  
   247  	// editor should have been invalidated
   248  	_, err = editor.Upload(make([]byte, modules.SectorSize))
   249  	if !errors.Contains(err, errInvalidEditor) && !errors.Contains(err, errInvalidSession) {
   250  		t.Error("expected invalid editor error; got", err)
   251  	}
   252  	editor.Close()
   253  
   254  	// create a downloader
   255  	downloader, err := c.Downloader(contract.HostPublicKey, nil)
   256  	if err != nil {
   257  		t.Fatal(err)
   258  	}
   259  	// mine until we enter the renew window
   260  	renewHeight = contract.EndHeight - c.allowance.RenewWindow
   261  	for c.blockHeight < renewHeight {
   262  		_, err := m.AddBlock()
   263  		if err != nil {
   264  			t.Fatal(err)
   265  		}
   266  	}
   267  
   268  	// downloader should have been invalidated
   269  	err = build.Retry(50, 100*time.Millisecond, func() error {
   270  		// wait for goroutine in ProcessConsensusChange to finish
   271  		c.maintenanceLock.Lock()
   272  		c.maintenanceLock.Unlock()
   273  		_, err2 := downloader.Download(crypto.Hash{}, 0, 0)
   274  		if !errors.Contains(err2, errInvalidDownloader) && !errors.Contains(err2, errInvalidSession) {
   275  			return errors.AddContext(err, "expected invalid downloader error")
   276  		}
   277  		return downloader.Close()
   278  	})
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  }