gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/contractor_test.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"gitlab.com/SiaPrime/SiaPrime/build"
    11  	"gitlab.com/SiaPrime/SiaPrime/modules"
    12  	"gitlab.com/SiaPrime/SiaPrime/types"
    13  )
    14  
    15  // newStub is used to test the New function. It implements all of the contractor's
    16  // dependencies.
    17  type newStub struct{}
    18  
    19  // consensus set stubs
    20  func (newStub) ConsensusSetSubscribe(modules.ConsensusSetSubscriber, modules.ConsensusChangeID, <-chan struct{}) error {
    21  	return nil
    22  }
    23  func (newStub) Synced() bool                               { return true }
    24  func (newStub) Unsubscribe(modules.ConsensusSetSubscriber) { return }
    25  
    26  // wallet stubs
    27  func (newStub) NextAddress() (uc types.UnlockConditions, err error)          { return }
    28  func (newStub) PrimarySeed() (modules.Seed, uint64, error)                   { return modules.Seed{}, 0, nil }
    29  func (newStub) StartTransaction() (tb modules.TransactionBuilder, err error) { return }
    30  func (newStub) Unlocked() (bool, error)                                      { return true, nil }
    31  
    32  // transaction pool stubs
    33  func (newStub) AcceptTransactionSet([]types.Transaction) error      { return nil }
    34  func (newStub) FeeEstimation() (a types.Currency, b types.Currency) { return }
    35  
    36  // hdb stubs
    37  func (newStub) AllHosts() []modules.HostDBEntry                                { return nil }
    38  func (newStub) ActiveHosts() []modules.HostDBEntry                             { return nil }
    39  func (newStub) CheckForIPViolations([]types.SiaPublicKey) []types.SiaPublicKey { return nil }
    40  func (newStub) Filter() (modules.FilterMode, map[string]types.SiaPublicKey) {
    41  	return 0, make(map[string]types.SiaPublicKey)
    42  }
    43  func (newStub) SetFilterMode(fm modules.FilterMode, hosts []types.SiaPublicKey) error { return nil }
    44  func (newStub) Host(types.SiaPublicKey) (settings modules.HostDBEntry, ok bool)       { return }
    45  func (newStub) IncrementSuccessfulInteractions(key types.SiaPublicKey)                { return }
    46  func (newStub) IncrementFailedInteractions(key types.SiaPublicKey)                    { return }
    47  func (newStub) RandomHosts(int, []types.SiaPublicKey, []types.SiaPublicKey) ([]modules.HostDBEntry, error) {
    48  	return nil, nil
    49  }
    50  func (newStub) ScoreBreakdown(modules.HostDBEntry) (modules.HostScoreBreakdown, error) {
    51  	return modules.HostScoreBreakdown{}, nil
    52  }
    53  func (newStub) SetAllowance(allowance modules.Allowance) error { return nil }
    54  func (newStub) UpdateContracts([]modules.RenterContract) error { return nil }
    55  func (newStub) SetIPViolationCheck(enabled bool)               {}
    56  func (newStub) IPViolationsCheck() bool {
    57  	return false
    58  	//hdb.hostTree.FilterByIPEnabled()
    59  }
    60  
    61  // TestNew tests the New function.
    62  func TestNew(t *testing.T) {
    63  	if testing.Short() {
    64  		t.SkipNow()
    65  	}
    66  	// Using a stub implementation of the dependencies is fine, as long as its
    67  	// non-nil.
    68  	var stub newStub
    69  	dir := build.TempDir("contractor", t.Name())
    70  
    71  	// Sane values.
    72  	_, err := New(stub, stub, stub, stub, dir)
    73  	if err != nil {
    74  		t.Fatalf("expected nil, got %v", err)
    75  	}
    76  
    77  	// Nil consensus set.
    78  	_, err = New(nil, stub, stub, stub, dir)
    79  	if err != errNilCS {
    80  		t.Fatalf("expected %v, got %v", errNilCS, err)
    81  	}
    82  
    83  	// Nil wallet.
    84  	_, err = New(stub, nil, stub, stub, dir)
    85  	if err != errNilWallet {
    86  		t.Fatalf("expected %v, got %v", errNilWallet, err)
    87  	}
    88  
    89  	// Nil transaction pool.
    90  	_, err = New(stub, stub, nil, stub, dir)
    91  	if err != errNilTpool {
    92  		t.Fatalf("expected %v, got %v", errNilTpool, err)
    93  	}
    94  
    95  	// Bad persistDir.
    96  	_, err = New(stub, stub, stub, stub, "")
    97  	if !os.IsNotExist(err) {
    98  		t.Fatalf("expected invalid directory, got %v", err)
    99  	}
   100  }
   101  
   102  // TestAllowance tests the Allowance method.
   103  func TestAllowance(t *testing.T) {
   104  	c := &Contractor{
   105  		allowance: modules.Allowance{
   106  			Funds:  types.NewCurrency64(1),
   107  			Period: 2,
   108  			Hosts:  3,
   109  		},
   110  	}
   111  	a := c.Allowance()
   112  	if a.Funds.Cmp(c.allowance.Funds) != 0 ||
   113  		a.Period != c.allowance.Period ||
   114  		a.Hosts != c.allowance.Hosts {
   115  		t.Fatal("Allowance did not return correct allowance:", a, c.allowance)
   116  	}
   117  }
   118  
   119  // stubHostDB mocks the hostDB dependency using zero-valued implementations of
   120  // its methods.
   121  type stubHostDB struct{}
   122  
   123  func (stubHostDB) AllHosts() (hs []modules.HostDBEntry)                           { return }
   124  func (stubHostDB) ActiveHosts() (hs []modules.HostDBEntry)                        { return }
   125  func (stubHostDB) CheckForIPViolations([]types.SiaPublicKey) []types.SiaPublicKey { return nil }
   126  func (stubHostDB) Filter() (modules.FilterMode, map[string]types.SiaPublicKey) {
   127  	return 0, make(map[string]types.SiaPublicKey)
   128  }
   129  func (stubHostDB) SetFilterMode(fm modules.FilterMode, hosts []types.SiaPublicKey) error { return nil }
   130  func (stubHostDB) Host(types.SiaPublicKey) (h modules.HostDBEntry, ok bool)              { return }
   131  func (stubHostDB) IncrementSuccessfulInteractions(key types.SiaPublicKey)                { return }
   132  func (stubHostDB) IncrementFailedInteractions(key types.SiaPublicKey)                    { return }
   133  func (stubHostDB) PublicKey() (spk types.SiaPublicKey)                                   { return }
   134  func (stubHostDB) RandomHosts(int, []types.SiaPublicKey, []types.SiaPublicKey) (hs []modules.HostDBEntry, _ error) {
   135  	return
   136  }
   137  func (stubHostDB) ScoreBreakdown(modules.HostDBEntry) (modules.HostScoreBreakdown, error) {
   138  	return modules.HostScoreBreakdown{}, nil
   139  }
   140  func (stubHostDB) SetAllowance(allowance modules.Allowance) error { return nil }
   141  func (stubHostDB) UpdateContracts([]modules.RenterContract) error { return nil }
   142  
   143  func (stubHostDB) SetIPViolationCheck(enabled bool) {}
   144  func (stubHostDB) IPViolationsCheck() bool {
   145  	return false
   146  	//hdb.hostTree.FilterByIPEnabled()
   147  }
   148  
   149  // TestAllowanceSpending verifies that the contractor will not spend more or
   150  // less than the allowance if uploading causes repeated early renewal, and that
   151  // correct spending metrics are returned, even across renewals.
   152  func TestAllowanceSpending(t *testing.T) {
   153  	if testing.Short() {
   154  		t.SkipNow()
   155  	}
   156  	t.Parallel()
   157  
   158  	// create testing trio
   159  	h, c, m, err := newTestingTrio(t.Name())
   160  	if err != nil {
   161  		t.Fatal(err)
   162  	}
   163  
   164  	// make the host's upload price very high so this test requires less
   165  	// computation
   166  	settings := h.InternalSettings()
   167  	settings.MinUploadBandwidthPrice = types.SiacoinPrecision.Div64(10)
   168  	err = h.SetInternalSettings(settings)
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	err = h.Announce()
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	_, err = m.AddBlock()
   177  	if err != nil {
   178  		t.Fatal(err)
   179  	}
   180  	err = build.Retry(50, 100*time.Millisecond, func() error {
   181  		hosts, err := c.hdb.RandomHosts(1, nil, nil)
   182  		if err != nil {
   183  			return err
   184  		}
   185  		if len(hosts) == 0 {
   186  			return errors.New("host has not been scanned yet")
   187  		}
   188  		return nil
   189  	})
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  
   194  	// set an allowance
   195  	testAllowance := modules.Allowance{
   196  		Funds:              types.SiacoinPrecision.Mul64(6000),
   197  		RenewWindow:        100,
   198  		Hosts:              1,
   199  		Period:             200,
   200  		ExpectedStorage:    modules.DefaultAllowance.ExpectedStorage,
   201  		ExpectedUpload:     modules.DefaultAllowance.ExpectedUpload,
   202  		ExpectedDownload:   modules.DefaultAllowance.ExpectedDownload,
   203  		ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy,
   204  	}
   205  	err = c.SetAllowance(testAllowance)
   206  	if err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	err = build.Retry(50, 100*time.Millisecond, func() error {
   210  		if len(c.Contracts()) != 1 {
   211  			return errors.New("allowance forming seems to have failed")
   212  		}
   213  		return nil
   214  	})
   215  	if err != nil {
   216  		t.Error(err)
   217  	}
   218  
   219  	// exhaust a contract and add a block several times. Despite repeatedly
   220  	// running out of funds, the contractor should not spend more than the
   221  	// allowance.
   222  	for i := 0; i < 15; i++ {
   223  		for _, contract := range c.Contracts() {
   224  			ed, err := c.Editor(contract.HostPublicKey, nil)
   225  			if err != nil {
   226  				continue
   227  			}
   228  
   229  			// upload 10 sectors to the contract
   230  			for sec := 0; sec < 10; sec++ {
   231  				ed.Upload(make([]byte, modules.SectorSize))
   232  			}
   233  			err = ed.Close()
   234  			if err != nil {
   235  				t.Fatal(err)
   236  			}
   237  		}
   238  		_, err := m.AddBlock()
   239  		if err != nil {
   240  			t.Fatal(err)
   241  		}
   242  	}
   243  
   244  	var minerRewards types.Currency
   245  	w := c.wallet.(*WalletBridge).W.(modules.Wallet)
   246  	txns, err := w.Transactions(0, 1000)
   247  	if err != nil {
   248  		t.Fatal(err)
   249  	}
   250  	for _, txn := range txns {
   251  		for _, so := range txn.Outputs {
   252  			if so.FundType == types.SpecifierMinerPayout && so.RelatedAddress != types.DevFundUnlockHash {
   253  				minerRewards = minerRewards.Add(so.Value)
   254  			}
   255  		}
   256  	}
   257  	balance, _, _, err := w.ConfirmedBalance()
   258  	if err != nil {
   259  		t.Fatal(err)
   260  	}
   261  	spent := minerRewards.Sub(balance)
   262  	if spent.Cmp(testAllowance.Funds) > 0 {
   263  		t.Fatal("contractor spent too much money: spent", spent.HumanString(), "allowance funds:", testAllowance.Funds.HumanString())
   264  	}
   265  
   266  	// we should have spent at least the allowance minus the cost of one more refresh
   267  	refreshCost := c.Contracts()[0].TotalCost.Mul64(2)
   268  	expectedMinSpending := testAllowance.Funds.Sub(refreshCost)
   269  	if spent.Cmp(expectedMinSpending) < 0 {
   270  		t.Fatal("contractor spent to little money: spent", spent.HumanString(), "expected at least:", expectedMinSpending.HumanString())
   271  	}
   272  
   273  	// PeriodSpending should reflect the amount of spending accurately
   274  	reportedSpending := c.PeriodSpending()
   275  	if reportedSpending.TotalAllocated.Cmp(spent) != 0 {
   276  		t.Fatal("reported incorrect spending for this billing cycle: got", reportedSpending.TotalAllocated.HumanString(), "wanted", spent.HumanString())
   277  	}
   278  	// COMPATv132 totalallocated should equal contractspending field.
   279  	if reportedSpending.ContractSpendingDeprecated.Cmp(reportedSpending.TotalAllocated) != 0 {
   280  		t.Fatal("TotalAllocated should be equal to ContractSpending for compatibility")
   281  	}
   282  
   283  	var expectedFees types.Currency
   284  	for _, contract := range c.Contracts() {
   285  		expectedFees = expectedFees.Add(contract.TxnFee)
   286  		expectedFees = expectedFees.Add(contract.SiafundFee)
   287  		expectedFees = expectedFees.Add(contract.ContractFee)
   288  	}
   289  	if expectedFees.Cmp(reportedSpending.ContractFees) != 0 {
   290  		t.Fatalf("expected %v reported fees but was %v",
   291  			expectedFees.HumanString(), reportedSpending.ContractFees.HumanString())
   292  	}
   293  }
   294  
   295  // TestIntegrationSetAllowance tests the SetAllowance method.
   296  func TestIntegrationSetAllowance(t *testing.T) {
   297  	if testing.Short() {
   298  		t.SkipNow()
   299  	}
   300  	// create testing trio
   301  	_, c, m, err := newTestingTrio(t.Name())
   302  	if err != nil {
   303  		t.Fatal(err)
   304  	}
   305  
   306  	// this test requires two hosts: create another one
   307  	h, err := newTestingHost(build.TempDir("hostdata", ""), c.cs.(modules.ConsensusSet), c.tpool.(modules.TransactionPool))
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	// announce the extra host
   313  	err = h.Announce()
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  
   318  	// mine a block, processing the announcement
   319  	_, err = m.AddBlock()
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	// wait for hostdb to scan
   325  	hosts, err := c.hdb.RandomHosts(1, nil, nil)
   326  	if err != nil {
   327  		t.Fatal("failed to get hosts", err)
   328  	}
   329  	for i := 0; i < 100 && len(hosts) == 0; i++ {
   330  		time.Sleep(time.Millisecond * 50)
   331  	}
   332  
   333  	// cancel allowance
   334  	var a modules.Allowance
   335  	err = c.SetAllowance(a)
   336  	if err != nil {
   337  		t.Fatal(err)
   338  	}
   339  
   340  	// bad args
   341  	a.Hosts = 1
   342  	err = c.SetAllowance(a)
   343  	if err != errAllowanceZeroPeriod {
   344  		t.Errorf("expected %q, got %q", errAllowanceZeroPeriod, err)
   345  	}
   346  	a.Period = 20
   347  	err = c.SetAllowance(a)
   348  	if err != ErrAllowanceZeroWindow {
   349  		t.Errorf("expected %q, got %q", ErrAllowanceZeroWindow, err)
   350  	}
   351  	a.RenewWindow = 20
   352  	err = c.SetAllowance(a)
   353  	if err != errAllowanceWindowSize {
   354  		t.Errorf("expected %q, got %q", errAllowanceWindowSize, err)
   355  	}
   356  	a.RenewWindow = 10
   357  	err = c.SetAllowance(a)
   358  	if err != errAllowanceZeroExpectedStorage {
   359  		t.Errorf("expected %q, got %q", errAllowanceZeroExpectedStorage, err)
   360  	}
   361  	a.ExpectedStorage = modules.DefaultAllowance.ExpectedStorage
   362  	err = c.SetAllowance(a)
   363  	if err != errAllowanceZeroExpectedUpload {
   364  		t.Errorf("expected %q, got %q", errAllowanceZeroExpectedUpload, err)
   365  	}
   366  	a.ExpectedUpload = modules.DefaultAllowance.ExpectedUpload
   367  	err = c.SetAllowance(a)
   368  	if err != errAllowanceZeroExpectedDownload {
   369  		t.Errorf("expected %q, got %q", errAllowanceZeroExpectedDownload, err)
   370  	}
   371  	a.ExpectedDownload = modules.DefaultAllowance.ExpectedDownload
   372  	err = c.SetAllowance(a)
   373  	if err != errAllowanceZeroExpectedRedundancy {
   374  		t.Errorf("expected %q, got %q", errAllowanceZeroExpectedRedundancy, err)
   375  	}
   376  	a.ExpectedRedundancy = modules.DefaultAllowance.ExpectedRedundancy
   377  
   378  	// reasonable values; should succeed
   379  	a.Funds = types.SiacoinPrecision.Mul64(100)
   380  	err = c.SetAllowance(a)
   381  	if err != nil {
   382  		t.Fatal(err)
   383  	}
   384  	err = build.Retry(50, 100*time.Millisecond, func() error {
   385  		if len(c.Contracts()) != 1 {
   386  			return errors.New("allowance forming seems to have failed")
   387  		}
   388  		return nil
   389  	})
   390  	if err != nil {
   391  		t.Error(err)
   392  	}
   393  
   394  	// set same allowance; should no-op
   395  	err = c.SetAllowance(a)
   396  	if err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	clen := c.staticContracts.Len()
   400  	if clen != 1 {
   401  		t.Fatal("expected 1 contract, got", clen)
   402  	}
   403  
   404  	_, err = m.AddBlock()
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  
   409  	// set allowance with Hosts = 2; should only form one new contract
   410  	a.Hosts = 2
   411  	err = c.SetAllowance(a)
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	err = build.Retry(50, 100*time.Millisecond, func() error {
   416  		if len(c.Contracts()) != 2 {
   417  			return errors.New("allowance forming seems to have failed")
   418  		}
   419  		return nil
   420  	})
   421  	if err != nil {
   422  		t.Fatal(err)
   423  	}
   424  
   425  	// set allowance with Funds*2; should trigger renewal of both contracts
   426  	a.Funds = a.Funds.Mul64(2)
   427  	err = c.SetAllowance(a)
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  	err = build.Retry(50, 100*time.Millisecond, func() error {
   432  		if len(c.Contracts()) != 2 {
   433  			return errors.New("allowance forming seems to have failed")
   434  		}
   435  		return nil
   436  	})
   437  	if err != nil {
   438  		t.Error(err)
   439  	}
   440  
   441  	// delete one of the contracts and set allowance with Funds*2; should
   442  	// trigger 1 renewal and 1 new contract
   443  	c.mu.Lock()
   444  	ids := c.staticContracts.IDs()
   445  	contract, _ := c.staticContracts.Acquire(ids[0])
   446  	c.staticContracts.Delete(contract)
   447  	c.mu.Unlock()
   448  	a.Funds = a.Funds.Mul64(2)
   449  	err = c.SetAllowance(a)
   450  	if err != nil {
   451  		t.Fatal(err)
   452  	}
   453  	err = build.Retry(50, 100*time.Millisecond, func() error {
   454  		if len(c.Contracts()) != 2 {
   455  			return errors.New("allowance forming seems to have failed")
   456  		}
   457  		return nil
   458  	})
   459  	if err != nil {
   460  		t.Fatal(err)
   461  	}
   462  }
   463  
   464  // TestHostMaxDuration tests that a host will not be used if their max duration
   465  // is not sufficient when renewing contracts
   466  func TestHostMaxDuration(t *testing.T) {
   467  	if testing.Short() {
   468  		t.SkipNow()
   469  	}
   470  	t.Parallel()
   471  
   472  	// create testing trio
   473  	h, c, m, err := newTestingTrio(t.Name())
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  
   478  	// Set host's MaxDuration to 5 to test if host will be skipped when contract
   479  	// is formed
   480  	settings := h.InternalSettings()
   481  	settings.MaxDuration = types.BlockHeight(5)
   482  	if err := h.SetInternalSettings(settings); err != nil {
   483  		t.Fatal(err)
   484  	}
   485  	// Let host settings permeate
   486  	err = build.Retry(50, 100*time.Millisecond, func() error {
   487  		host, _ := c.hdb.Host(h.PublicKey())
   488  		if settings.MaxDuration != host.MaxDuration {
   489  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   490  		}
   491  		return nil
   492  	})
   493  	if err != nil {
   494  		t.Fatal(err)
   495  	}
   496  
   497  	// Create allowance
   498  	a := modules.Allowance{
   499  		Funds:              types.SiacoinPrecision.Mul64(100),
   500  		Hosts:              1,
   501  		Period:             30,
   502  		RenewWindow:        20,
   503  		ExpectedStorage:    modules.DefaultAllowance.ExpectedStorage,
   504  		ExpectedUpload:     modules.DefaultAllowance.ExpectedUpload,
   505  		ExpectedDownload:   modules.DefaultAllowance.ExpectedDownload,
   506  		ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy,
   507  	}
   508  	err = c.SetAllowance(a)
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  
   513  	// Wait for and confirm no Contract creation
   514  	err = build.Retry(50, 100*time.Millisecond, func() error {
   515  		if len(c.Contracts()) == 0 {
   516  			return errors.New("no contract created")
   517  		}
   518  		return nil
   519  	})
   520  	if err == nil {
   521  		t.Fatal("Contract should not have been created")
   522  	}
   523  
   524  	// Set host's MaxDuration to 50 to test if host will now form contract
   525  	settings = h.InternalSettings()
   526  	settings.MaxDuration = types.BlockHeight(50)
   527  	if err := h.SetInternalSettings(settings); err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	// Let host settings permeate
   531  	err = build.Retry(50, 100*time.Millisecond, func() error {
   532  		host, _ := c.hdb.Host(h.PublicKey())
   533  		if settings.MaxDuration != host.MaxDuration {
   534  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   535  		}
   536  		return nil
   537  	})
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  	_, err = m.AddBlock()
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	// Wait for Contract creation
   547  	err = build.Retry(600, 100*time.Millisecond, func() error {
   548  		if len(c.Contracts()) != 1 {
   549  			return errors.New("no contract created")
   550  		}
   551  		return nil
   552  	})
   553  	if err != nil {
   554  		t.Error(err)
   555  	}
   556  
   557  	// Set host's MaxDuration to 5 to test if host will be skipped when contract
   558  	// is renewed
   559  	settings = h.InternalSettings()
   560  	settings.MaxDuration = types.BlockHeight(5)
   561  	if err := h.SetInternalSettings(settings); err != nil {
   562  		t.Fatal(err)
   563  	}
   564  	// Let host settings permeate
   565  	err = build.Retry(50, 100*time.Millisecond, func() error {
   566  		host, _ := c.hdb.Host(h.PublicKey())
   567  		if settings.MaxDuration != host.MaxDuration {
   568  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   569  		}
   570  		return nil
   571  	})
   572  	if err != nil {
   573  		t.Fatal(err)
   574  	}
   575  
   576  	// Mine blocks to renew contract
   577  	for i := types.BlockHeight(0); i <= c.allowance.Period-c.allowance.RenewWindow; i++ {
   578  		_, err = m.AddBlock()
   579  		if err != nil {
   580  			t.Fatal(err)
   581  		}
   582  	}
   583  
   584  	// Confirm Contract is not renewed
   585  	err = build.Retry(50, 100*time.Millisecond, func() error {
   586  		if len(c.OldContracts()) == 0 {
   587  			return errors.New("no contract renewed")
   588  		}
   589  		return nil
   590  	})
   591  	if err == nil {
   592  		t.Fatal("Contract should not have been renewed")
   593  	}
   594  }
   595  
   596  // TestLinkedContracts tests that the contractors maps are updated correctly
   597  // when renewing contracts
   598  func TestLinkedContracts(t *testing.T) {
   599  	if testing.Short() {
   600  		t.SkipNow()
   601  	}
   602  	t.Parallel()
   603  
   604  	// create testing trio
   605  	h, c, m, err := newTestingTrio(t.Name())
   606  	if err != nil {
   607  		t.Fatal(err)
   608  	}
   609  
   610  	// Create allowance
   611  	a := modules.Allowance{
   612  		Funds:              types.SiacoinPrecision.Mul64(100),
   613  		Hosts:              1,
   614  		Period:             20,
   615  		RenewWindow:        10,
   616  		ExpectedStorage:    modules.DefaultAllowance.ExpectedStorage,
   617  		ExpectedUpload:     modules.DefaultAllowance.ExpectedUpload,
   618  		ExpectedDownload:   modules.DefaultAllowance.ExpectedDownload,
   619  		ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy,
   620  	}
   621  	err = c.SetAllowance(a)
   622  	if err != nil {
   623  		t.Fatal(err)
   624  	}
   625  
   626  	// Wait for Contract creation
   627  	err = build.Retry(200, 100*time.Millisecond, func() error {
   628  		if len(c.Contracts()) != 1 {
   629  			return errors.New("no contract created")
   630  		}
   631  		return nil
   632  	})
   633  	if err != nil {
   634  		t.Error(err)
   635  	}
   636  
   637  	// Confirm that maps are empty
   638  	if len(c.renewedFrom) != 0 {
   639  		t.Fatal("renewedFrom map should be empty")
   640  	}
   641  	if len(c.renewedTo) != 0 {
   642  		t.Fatal("renewedTo map should be empty")
   643  	}
   644  
   645  	// Set host's uploadbandwidthprice to zero to test divide by zero check when
   646  	// contracts are renewed
   647  	settings := h.InternalSettings()
   648  	settings.MinUploadBandwidthPrice = types.ZeroCurrency
   649  	if err := h.SetInternalSettings(settings); err != nil {
   650  		t.Fatal(err)
   651  	}
   652  
   653  	// Mine blocks to renew contract
   654  	for i := types.BlockHeight(0); i < c.allowance.Period-c.allowance.RenewWindow; i++ {
   655  		_, err = m.AddBlock()
   656  		if err != nil {
   657  			t.Fatal(err)
   658  		}
   659  	}
   660  
   661  	// Confirm Contracts got renewed
   662  	err = build.Retry(200, 100*time.Millisecond, func() error {
   663  		if len(c.Contracts()) != 1 {
   664  			return errors.New("no contract")
   665  		}
   666  		if len(c.OldContracts()) != 1 {
   667  			return errors.New("no old contract")
   668  		}
   669  		return nil
   670  	})
   671  	if err != nil {
   672  		t.Error(err)
   673  	}
   674  
   675  	// Confirm maps are updated as expected
   676  	if len(c.renewedFrom) != 1 {
   677  		t.Fatalf("renewedFrom map should have 1 entry but has %v", len(c.renewedFrom))
   678  	}
   679  	if len(c.renewedTo) != 1 {
   680  		t.Fatalf("renewedTo map should have 1 entry but has %v", len(c.renewedTo))
   681  	}
   682  	if c.renewedFrom[c.Contracts()[0].ID] != c.OldContracts()[0].ID {
   683  		t.Fatalf(`Map assignment incorrect,
   684  		expected:
   685  		map[%v:%v]
   686  		got:
   687  		%v`, c.Contracts()[0].ID, c.OldContracts()[0].ID, c.renewedFrom)
   688  	}
   689  	if c.renewedTo[c.OldContracts()[0].ID] != c.Contracts()[0].ID {
   690  		t.Fatalf(`Map assignment incorrect,
   691  		expected:
   692  		map[%v:%v]
   693  		got:
   694  		%v`, c.OldContracts()[0].ID, c.Contracts()[0].ID, c.renewedTo)
   695  	}
   696  }
   697  
   698  // testWalletShim is used to test the walletBridge type.
   699  type testWalletShim struct {
   700  	nextAddressCalled bool
   701  	startTxnCalled    bool
   702  }
   703  
   704  // These stub implementations for the walletShim interface set their respective
   705  // booleans to true, allowing tests to verify that they have been called.
   706  func (ws *testWalletShim) NextAddress() (types.UnlockConditions, error) {
   707  	ws.nextAddressCalled = true
   708  	return types.UnlockConditions{}, nil
   709  }
   710  func (ws *testWalletShim) PrimarySeed() (modules.Seed, uint64, error) {
   711  	return modules.Seed{}, 0, nil
   712  }
   713  func (ws *testWalletShim) StartTransaction() (modules.TransactionBuilder, error) {
   714  	ws.startTxnCalled = true
   715  	return nil, nil
   716  }
   717  func (ws *testWalletShim) Unlocked() (bool, error) { return true, nil }
   718  
   719  // TestWalletBridge tests the walletBridge type.
   720  func TestWalletBridge(t *testing.T) {
   721  	shim := new(testWalletShim)
   722  	bridge := WalletBridge{shim}
   723  	bridge.NextAddress()
   724  	if !shim.nextAddressCalled {
   725  		t.Error("NextAddress was not called on the shim")
   726  	}
   727  	bridge.StartTransaction()
   728  	if !shim.startTxnCalled {
   729  		t.Error("StartTransaction was not called on the shim")
   730  	}
   731  }