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

     1  package contractor
     2  
     3  import (
     4  	"io/ioutil"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"testing"
     9  
    10  	"gitlab.com/NebulousLabs/fastrand"
    11  	"gitlab.com/NebulousLabs/ratelimit"
    12  
    13  	"gitlab.com/SkynetLabs/skyd/build"
    14  	"gitlab.com/SkynetLabs/skyd/skymodules"
    15  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/proto"
    16  	"go.sia.tech/siad/modules"
    17  	"go.sia.tech/siad/persist"
    18  	"go.sia.tech/siad/types"
    19  )
    20  
    21  // TestSaveLoad tests that the contractor can save and load itself.
    22  func TestSaveLoad(t *testing.T) {
    23  	if testing.Short() {
    24  		t.SkipNow()
    25  	}
    26  	t.Parallel()
    27  	// create contractor with mocked persist dependency
    28  	persistDir := build.TempDir("contractor", "mock")
    29  	os.MkdirAll(persistDir, 0700)
    30  	c := &Contractor{
    31  		persistDir:     persistDir,
    32  		preferredHosts: make(map[string]struct{}),
    33  		synced:         make(chan struct{}),
    34  	}
    35  
    36  	c.staticWatchdog = newWatchdog(c)
    37  	expectedFileContractStatus := &fileContractStatus{
    38  		formationSweepHeight: 543210,
    39  		contractFound:        true,
    40  		revisionFound:        400,
    41  		storageProofFound:    987123,
    42  
    43  		formationTxnSet: []types.Transaction{
    44  			{
    45  				ArbitraryData: [][]byte{{1, 2, 3, 4, 5}},
    46  			},
    47  		},
    48  		parentOutputs: map[types.SiacoinOutputID]struct{}{
    49  			{4}: {},
    50  		},
    51  
    52  		sweepTxn: types.Transaction{
    53  			ArbitraryData: [][]byte{{1, 2, 3}},
    54  		},
    55  
    56  		sweepParents: []types.Transaction{{
    57  			ArbitraryData: [][]byte{{4, 5, 63}},
    58  		}},
    59  
    60  		windowStart: 5,
    61  		windowEnd:   10,
    62  	}
    63  	c.staticWatchdog.contracts = map[types.FileContractID]*fileContractStatus{
    64  		{1}: expectedFileContractStatus,
    65  	}
    66  
    67  	expectedArchivedContract := skymodules.ContractWatchStatus{
    68  		Archived:                  true,
    69  		FormationSweepHeight:      11,
    70  		ContractFound:             true,
    71  		LatestRevisionFound:       3883889,
    72  		StorageProofFoundAtHeight: 12312,
    73  		DoubleSpendHeight:         12333333,
    74  		WindowStart:               1111111231209,
    75  		WindowEnd:                 123808900,
    76  	}
    77  	c.staticWatchdog.archivedContracts = map[types.FileContractID]skymodules.ContractWatchStatus{
    78  		{2}: expectedArchivedContract,
    79  	}
    80  
    81  	c.oldContracts = map[types.FileContractID]skymodules.RenterContract{
    82  		{0}: {ID: types.FileContractID{0}, HostPublicKey: types.SiaPublicKey{Key: []byte("foo")}},
    83  		{1}: {ID: types.FileContractID{1}, HostPublicKey: types.SiaPublicKey{Key: []byte("bar")}},
    84  		{2}: {ID: types.FileContractID{2}, HostPublicKey: types.SiaPublicKey{Key: []byte("baz")}},
    85  	}
    86  
    87  	c.renewedFrom = map[types.FileContractID]types.FileContractID{
    88  		{1}: {2},
    89  	}
    90  	c.renewedTo = map[types.FileContractID]types.FileContractID{
    91  		{1}: {2},
    92  	}
    93  	c.preferredHosts["host"] = struct{}{}
    94  	close(c.synced)
    95  
    96  	c.staticChurnLimiter = newChurnLimiter(c)
    97  	c.staticChurnLimiter.aggregateCurrentPeriodChurn = 123456
    98  	c.staticChurnLimiter.remainingChurnBudget = -789
    99  
   100  	// save, clear, and reload
   101  	err := c.save()
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  	c.oldContracts = make(map[types.FileContractID]skymodules.RenterContract)
   106  	c.renewedFrom = make(map[types.FileContractID]types.FileContractID)
   107  	c.renewedTo = make(map[types.FileContractID]types.FileContractID)
   108  	err = c.load()
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  	// Check that all fields were restored
   113  	_, ok0 := c.oldContracts[types.FileContractID{0}]
   114  	_, ok1 := c.oldContracts[types.FileContractID{1}]
   115  	_, ok2 := c.oldContracts[types.FileContractID{2}]
   116  	if !ok0 || !ok1 || !ok2 {
   117  		t.Fatal("oldContracts were not restored properly:", c.oldContracts)
   118  	}
   119  	id := types.FileContractID{2}
   120  	if c.renewedFrom[types.FileContractID{1}] != id {
   121  		t.Fatal("renewedFrom not restored properly:", c.renewedFrom)
   122  	}
   123  	if c.renewedTo[types.FileContractID{1}] != id {
   124  		t.Fatal("renewedTo not restored properly:", c.renewedTo)
   125  	}
   126  	if _, exists := c.preferredHosts["host"]; !exists {
   127  		t.Fatal("preferred host wasn't loaded")
   128  	}
   129  	if len(c.preferredHosts) != 1 {
   130  		t.Fatal("wrong length")
   131  	}
   132  	select {
   133  	case <-c.synced:
   134  	default:
   135  		t.Fatal("contractor should be synced")
   136  	}
   137  	// use stdPersist instead of mock
   138  	c.persistDir = build.TempDir("contractor", t.Name())
   139  	os.MkdirAll(c.persistDir, 0700)
   140  
   141  	// COMPATv136 save the allowance but make sure that the newly added fields
   142  	// are 0. After loading them from disk they should be set to the default
   143  	// values.
   144  	c.allowance = skymodules.DefaultAllowance
   145  	c.allowance.ExpectedStorage = 0
   146  	c.allowance.ExpectedUpload = 0
   147  	c.allowance.ExpectedDownload = 0
   148  	c.allowance.ExpectedRedundancy = 0
   149  	c.allowance.MaxPeriodChurn = 0
   150  
   151  	// save, clear, and reload
   152  	err = c.save()
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	c.oldContracts = make(map[types.FileContractID]skymodules.RenterContract)
   157  	c.renewedFrom = make(map[types.FileContractID]types.FileContractID)
   158  	c.renewedTo = make(map[types.FileContractID]types.FileContractID)
   159  	c.synced = make(chan struct{})
   160  	err = c.load()
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	// check that all fields were restored
   165  	_, ok0 = c.oldContracts[types.FileContractID{0}]
   166  	_, ok1 = c.oldContracts[types.FileContractID{1}]
   167  	_, ok2 = c.oldContracts[types.FileContractID{2}]
   168  	if !ok0 || !ok1 || !ok2 {
   169  		t.Fatal("oldContracts were not restored properly:", c.oldContracts)
   170  	}
   171  	if c.renewedFrom[types.FileContractID{1}] != id {
   172  		t.Fatal("renewedFrom not restored properly:", c.renewedFrom)
   173  	}
   174  	if c.renewedTo[types.FileContractID{1}] != id {
   175  		t.Fatal("renewedTo not restored properly:", c.renewedTo)
   176  	}
   177  	if _, exists := c.preferredHosts["host"]; !exists {
   178  		t.Fatal("preferred host wasn't loaded")
   179  	}
   180  	if len(c.preferredHosts) != 1 {
   181  		t.Fatal("wrong length")
   182  	}
   183  	select {
   184  	case <-c.synced:
   185  	default:
   186  		t.Fatal("contractor should be synced")
   187  	}
   188  	if c.allowance.ExpectedStorage != skymodules.DefaultAllowance.ExpectedStorage {
   189  		t.Errorf("ExpectedStorage was %v but should be %v",
   190  			c.allowance.ExpectedStorage, skymodules.DefaultAllowance.ExpectedStorage)
   191  	}
   192  	if c.allowance.ExpectedUpload != skymodules.DefaultAllowance.ExpectedUpload {
   193  		t.Errorf("ExpectedUpload was %v but should be %v",
   194  			c.allowance.ExpectedUpload, skymodules.DefaultAllowance.ExpectedUpload)
   195  	}
   196  	if c.allowance.ExpectedDownload != skymodules.DefaultAllowance.ExpectedDownload {
   197  		t.Errorf("ExpectedDownload was %v but should be %v",
   198  			c.allowance.ExpectedDownload, skymodules.DefaultAllowance.ExpectedDownload)
   199  	}
   200  	if c.allowance.ExpectedRedundancy != skymodules.DefaultAllowance.ExpectedRedundancy {
   201  		t.Errorf("ExpectedRedundancy was %v but should be %v",
   202  			c.allowance.ExpectedRedundancy, skymodules.DefaultAllowance.ExpectedRedundancy)
   203  	}
   204  
   205  	// Change the expected* fields of the allowance again, save, clear and reload.
   206  	c.allowance.ExpectedStorage = uint64(fastrand.Intn(100))
   207  	c.allowance.ExpectedUpload = uint64(fastrand.Intn(100))
   208  	c.allowance.ExpectedDownload = uint64(fastrand.Intn(100))
   209  	c.allowance.ExpectedRedundancy = float64(fastrand.Intn(100))
   210  	c.allowance.MaxPeriodChurn = 1357
   211  	a := c.allowance
   212  	// Save
   213  	err = c.save()
   214  	if err != nil {
   215  		t.Fatal(err)
   216  	}
   217  	// Clear allowance.
   218  	c.allowance = skymodules.Allowance{}
   219  	// Load
   220  	err = c.load()
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	// Check if fields were restored.
   225  	if c.allowance.ExpectedStorage != a.ExpectedStorage {
   226  		t.Errorf("ExpectedStorage was %v but should be %v",
   227  			c.allowance.ExpectedStorage, a.ExpectedStorage)
   228  	}
   229  	if c.allowance.ExpectedUpload != a.ExpectedUpload {
   230  		t.Errorf("ExpectedUpload was %v but should be %v",
   231  			c.allowance.ExpectedUpload, a.ExpectedUpload)
   232  	}
   233  	if c.allowance.ExpectedDownload != a.ExpectedDownload {
   234  		t.Errorf("ExpectedDownload was %v but should be %v",
   235  			c.allowance.ExpectedDownload, a.ExpectedDownload)
   236  	}
   237  	if c.allowance.ExpectedRedundancy != a.ExpectedRedundancy {
   238  		t.Errorf("ExpectedRedundancy was %v but should be %v",
   239  			c.allowance.ExpectedRedundancy, a.ExpectedRedundancy)
   240  	}
   241  	if c.allowance.MaxPeriodChurn != a.MaxPeriodChurn {
   242  		t.Errorf("MaxPeriodChurn was %v but should be %v",
   243  			c.allowance.MaxPeriodChurn, a.MaxPeriodChurn)
   244  	}
   245  
   246  	// Check the watchdog settings.
   247  	if c.staticWatchdog == nil {
   248  		t.Fatal("Watchdog not restored")
   249  	}
   250  	contract, ok := c.staticWatchdog.contracts[types.FileContractID{1}]
   251  	if !ok {
   252  		t.Fatal("Contract not found", len(c.staticWatchdog.contracts))
   253  	}
   254  	if contract.formationSweepHeight != expectedFileContractStatus.formationSweepHeight {
   255  		t.Fatal("watchdog not restored properly", contract.formationSweepHeight)
   256  	}
   257  	if contract.contractFound != expectedFileContractStatus.contractFound {
   258  		t.Fatal("watchdog not restored properly")
   259  	}
   260  	if contract.revisionFound != expectedFileContractStatus.revisionFound {
   261  		t.Fatal("watchdog not restored properly", contract.revisionFound)
   262  	}
   263  	if contract.storageProofFound != expectedFileContractStatus.storageProofFound {
   264  		t.Fatal("watchdog not restored properly", contract.storageProofFound)
   265  	}
   266  	if len(contract.formationTxnSet) != 1 {
   267  		t.Fatal("watchdog not restored properly", contract)
   268  	}
   269  	if contract.formationTxnSet[0].ID() != expectedFileContractStatus.formationTxnSet[0].ID() {
   270  		t.Fatal("watchdog not restored properly", contract.formationTxnSet)
   271  	}
   272  	if len(contract.parentOutputs) != 1 {
   273  		t.Fatal("watchdog not restored properly", contract.parentOutputs)
   274  	}
   275  	if _, foundOutput := contract.parentOutputs[types.SiacoinOutputID{4}]; !foundOutput {
   276  		t.Fatal("watchdog not restored properly", contract.parentOutputs)
   277  	}
   278  	if contract.sweepTxn.ID() != expectedFileContractStatus.sweepTxn.ID() {
   279  		t.Fatal("watchdog not restored properly", contract)
   280  	}
   281  	if len(contract.sweepParents) != len(expectedFileContractStatus.sweepParents) {
   282  		t.Fatal("watchdog not restored properly", contract)
   283  	}
   284  	if contract.sweepParents[0].ID() != expectedFileContractStatus.sweepParents[0].ID() {
   285  		t.Fatal("watchdog not restored properly", contract)
   286  	}
   287  	if contract.windowStart != expectedFileContractStatus.windowStart {
   288  		t.Fatal("watchdog not restored properly", contract)
   289  	}
   290  	if contract.windowEnd != expectedFileContractStatus.windowEnd {
   291  		t.Fatal("watchdog not restored properly", contract)
   292  	}
   293  	if len(c.staticWatchdog.archivedContracts) != 1 {
   294  		t.Fatal("watchdog not restored properly", c.staticWatchdog.archivedContracts)
   295  	}
   296  	archivedContract, ok := c.staticWatchdog.archivedContracts[types.FileContractID{2}]
   297  	if !ok {
   298  		t.Fatal("watchdog not restored properly", c.staticWatchdog.archivedContracts)
   299  	}
   300  	if !reflect.DeepEqual(archivedContract, expectedArchivedContract) {
   301  		t.Fatal("Archived contract not restored properly", archivedContract)
   302  	}
   303  
   304  	// Check churnLimiter state.
   305  	aggregateChurn, maxChurn := c.staticChurnLimiter.managedAggregateAndMaxChurn()
   306  	if aggregateChurn != 123456 {
   307  		t.Fatal("Expected 123456 aggregate churn", aggregateChurn)
   308  	}
   309  	if maxChurn != a.MaxPeriodChurn {
   310  		t.Fatal("Expected 1357 max churn", maxChurn)
   311  	}
   312  	remainingChurnBudget, periodBudget := c.staticChurnLimiter.managedChurnBudget()
   313  	if remainingChurnBudget != -789 {
   314  		t.Fatal("Expected -789 remainingChurnBudget", remainingChurnBudget)
   315  	}
   316  	expectedPeriodBudget := 1357 - 123456
   317  	if periodBudget != expectedPeriodBudget {
   318  		t.Fatal("Expected remainingChurnBudget", periodBudget)
   319  	}
   320  }
   321  
   322  // TestConvertPersist tests that contracts previously stored in the
   323  // .journal format can be converted to the .contract format.
   324  func TestConvertPersist(t *testing.T) {
   325  	if testing.Short() {
   326  		t.SkipNow()
   327  	}
   328  
   329  	dir := build.TempDir(filepath.Join("contractor", t.Name()))
   330  	os.MkdirAll(dir, 0700)
   331  	// copy the test data into the temp folder
   332  	testdata, err := ioutil.ReadFile(filepath.Join("testdata", "TestConvertPersist.journal"))
   333  	if err != nil {
   334  		t.Fatal(err)
   335  	}
   336  	err = ioutil.WriteFile(filepath.Join(dir, "contractor.journal"), testdata, 0600)
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  
   341  	// convert the journal
   342  	err = convertPersist(dir, ratelimit.NewRateLimit(0, 0, 0))
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	// load the persist
   348  	var p contractorPersist
   349  	err = persist.LoadJSON(persistMeta, &p, filepath.Join(dir, PersistFilename))
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	if !p.Allowance.Funds.Equals64(10) || p.Allowance.Hosts != 7 || p.Allowance.Period != 3 || p.Allowance.RenewWindow != 20 {
   354  		t.Fatal("recovered allowance was wrong:", p.Allowance)
   355  	}
   356  
   357  	// load the contracts
   358  	cs, err := proto.NewContractSet(filepath.Join(dir, "contracts"), ratelimit.NewRateLimit(0, 0, 0), modules.ProdDependencies)
   359  	if err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	if cs.Len() != 1 {
   363  		t.Fatal("expected 1 contract, got", cs.Len())
   364  	}
   365  	m := cs.ViewAll()[0]
   366  	if m.ID.String() != "792b5eec683819d78416a9e80cba454ebcb5a52eeac4f17b443d177bd425fc5c" {
   367  		t.Fatal("recovered contract has wrong ID", m.ID)
   368  	}
   369  }