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