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

     1  package contractor
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"gitlab.com/SkynetLabs/skyd/build"
    14  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    15  	"gitlab.com/SkynetLabs/skyd/skymodules"
    16  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb"
    17  	"go.sia.tech/siad/crypto"
    18  	"go.sia.tech/siad/modules"
    19  	"go.sia.tech/siad/modules/consensus"
    20  	"go.sia.tech/siad/modules/gateway"
    21  	"go.sia.tech/siad/modules/transactionpool"
    22  	"go.sia.tech/siad/modules/wallet"
    23  	"go.sia.tech/siad/types"
    24  
    25  	"gitlab.com/NebulousLabs/errors"
    26  	"gitlab.com/NebulousLabs/ratelimit"
    27  	"gitlab.com/NebulousLabs/siamux"
    28  )
    29  
    30  // Create a closeFn type that allows helpers which need to be closed to return
    31  // methods that close the helpers.
    32  type closeFn func() error
    33  
    34  // tryClose is shorthand to run a t.Error() if a closeFn fails.
    35  func tryClose(cf closeFn, t *testing.T) {
    36  	err := cf()
    37  	if err != nil {
    38  		t.Error(err)
    39  	}
    40  }
    41  
    42  // newModules initializes the modules needed to test creating a new contractor
    43  func newModules(testdir string) (modules.ConsensusSet, modules.Wallet, modules.TransactionPool, *siamux.SiaMux, skymodules.HostDB, closeFn, error) {
    44  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
    45  	if err != nil {
    46  		return nil, nil, nil, nil, nil, nil, err
    47  	}
    48  	cs, errChan := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir))
    49  	if err := <-errChan; err != nil {
    50  		return nil, nil, nil, nil, nil, nil, err
    51  	}
    52  	tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir))
    53  	if err != nil {
    54  		return nil, nil, nil, nil, nil, nil, err
    55  	}
    56  	w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
    57  	if err != nil {
    58  		return nil, nil, nil, nil, nil, nil, err
    59  	}
    60  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
    61  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
    62  	if err != nil {
    63  		return nil, nil, nil, nil, nil, nil, err
    64  	}
    65  	hdb, errChanHDB := hostdb.New(g, cs, tp, mux, testdir)
    66  	if err := <-errChanHDB; err != nil {
    67  		return nil, nil, nil, nil, nil, nil, err
    68  	}
    69  	cf := func() error {
    70  		return errors.Compose(hdb.Close(), mux.Close(), w.Close(), tp.Close(), cs.Close(), g.Close())
    71  	}
    72  	return cs, w, tp, mux, hdb, cf, nil
    73  }
    74  
    75  // newStream is a helper to get a ready-to-use stream that is connected to a
    76  // host.
    77  func newStream(mux *siamux.SiaMux, h modules.Host) (siamux.Stream, error) {
    78  	hes := h.ExternalSettings()
    79  	muxAddress := fmt.Sprintf("%s:%s", hes.NetAddress.Host(), hes.SiaMuxPort)
    80  	muxPK := modules.SiaPKToMuxPK(h.PublicKey())
    81  	return mux.NewStream(modules.HostSiaMuxSubscriberName, muxAddress, muxPK)
    82  }
    83  
    84  // TestNew tests the New function.
    85  func TestNew(t *testing.T) {
    86  	if testing.Short() {
    87  		t.SkipNow()
    88  	}
    89  	// Create the skymodules.
    90  	dir := build.TempDir("contractor", t.Name())
    91  	cs, w, tpool, _, hdb, closeFn, err := newModules(dir)
    92  	if err != nil {
    93  		t.Fatal(err)
    94  	}
    95  	defer tryClose(closeFn, t)
    96  
    97  	// Sane values.
    98  	rl := ratelimit.NewRateLimit(0, 0, 0)
    99  	_, errChan := New(cs, w, tpool, hdb, rl, dir)
   100  	if err := <-errChan; err != nil {
   101  		t.Fatalf("expected nil, got %v", err)
   102  	}
   103  
   104  	// Nil consensus set.
   105  	_, errChan = New(nil, w, tpool, hdb, rl, dir)
   106  	if err := <-errChan; !errors.Contains(err, errNilCS) {
   107  		t.Fatalf("expected %v, got %v", errNilCS, err)
   108  	}
   109  
   110  	// Nil wallet.
   111  	_, errChan = New(cs, nil, tpool, hdb, rl, dir)
   112  	if err := <-errChan; !errors.Contains(err, errNilWallet) {
   113  		t.Fatalf("expected %v, got %v", errNilWallet, err)
   114  	}
   115  
   116  	// Nil transaction pool.
   117  	_, errChan = New(cs, w, nil, hdb, rl, dir)
   118  	if err := <-errChan; !errors.Contains(err, errNilTpool) {
   119  		t.Fatalf("expected %v, got %v", errNilTpool, err)
   120  	}
   121  	// Nil hostdb.
   122  	_, errChan = New(cs, w, tpool, nil, rl, dir)
   123  	if err := <-errChan; !errors.Contains(err, errNilHDB) {
   124  		t.Fatalf("expected %v, got %v", errNilHDB, err)
   125  	}
   126  
   127  	// Bad persistDir.
   128  	_, errChan = New(cs, w, tpool, hdb, rl, "")
   129  	if err := <-errChan; !os.IsNotExist(err) {
   130  		t.Fatalf("expected invalid directory, got %v", err)
   131  	}
   132  }
   133  
   134  // TestAllowance tests the Allowance method.
   135  func TestAllowance(t *testing.T) {
   136  	c := &Contractor{
   137  		allowance: skymodules.Allowance{
   138  			Funds:  types.NewCurrency64(1),
   139  			Period: 2,
   140  			Hosts:  3,
   141  		},
   142  	}
   143  	a := c.Allowance()
   144  	if a.Funds.Cmp(c.allowance.Funds) != 0 ||
   145  		a.Period != c.allowance.Period ||
   146  		a.Hosts != c.allowance.Hosts {
   147  		t.Fatal("Allowance did not return correct allowance:", a, c.allowance)
   148  	}
   149  }
   150  
   151  // TestIntegrationSetAllowance tests the SetAllowance method.
   152  func TestIntegrationSetAllowance(t *testing.T) {
   153  	if testing.Short() {
   154  		t.SkipNow()
   155  	}
   156  	t.Parallel()
   157  
   158  	// create a siamux
   159  	testdir := build.TempDir("contractor", t.Name())
   160  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
   161  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	defer tryClose(mux.Close, t)
   166  
   167  	// create testing trio
   168  	h, c, m, cf, err := newTestingTrio(t.Name())
   169  	if err != nil {
   170  		t.Fatal(err)
   171  	}
   172  	defer tryClose(cf, t)
   173  
   174  	// this test requires two hosts: create another one
   175  	h, hostCF, err := newTestingHost(build.TempDir("hostdata", ""), c.staticCS.(modules.ConsensusSet), c.staticTPool.(modules.TransactionPool), mux)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	defer tryClose(hostCF, t)
   180  
   181  	// announce the extra host
   182  	err = h.Announce()
   183  	if err != nil {
   184  		t.Fatal(err)
   185  	}
   186  
   187  	// mine a block, processing the announcement
   188  	_, err = m.AddBlock()
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  
   193  	// wait for hostdb to scan
   194  	hosts, err := c.staticHDB.RandomHosts(1, nil, nil)
   195  	if err != nil {
   196  		t.Fatal("failed to get hosts", err)
   197  	}
   198  	for i := 0; i < 100 && len(hosts) == 0; i++ {
   199  		time.Sleep(time.Millisecond * 50)
   200  	}
   201  
   202  	// cancel allowance
   203  	var a skymodules.Allowance
   204  	err = c.SetAllowance(a)
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	// bad args
   210  	a.Hosts = 1
   211  	err = c.SetAllowance(a)
   212  	if !errors.Contains(err, ErrAllowanceZeroFunds) {
   213  		t.Errorf("expected %q, got %q", ErrAllowanceZeroFunds, err)
   214  	}
   215  	a.Funds = types.SiacoinPrecision
   216  	a.Hosts = 0
   217  	err = c.SetAllowance(a)
   218  	if !errors.Contains(err, ErrAllowanceNoHosts) {
   219  		t.Errorf("expected %q, got %q", ErrAllowanceNoHosts, err)
   220  	}
   221  	a.Hosts = 1
   222  	err = c.SetAllowance(a)
   223  	if !errors.Contains(err, ErrAllowanceZeroPeriod) {
   224  		t.Errorf("expected %q, got %q", ErrAllowanceZeroPeriod, err)
   225  	}
   226  	a.Period = 20
   227  	err = c.SetAllowance(a)
   228  	if !errors.Contains(err, ErrAllowanceZeroWindow) {
   229  		t.Errorf("expected %q, got %q", ErrAllowanceZeroWindow, err)
   230  	}
   231  	// There should not be any errors related to RenewWindow size
   232  	a.RenewWindow = 30
   233  	err = c.SetAllowance(a)
   234  	if !errors.Contains(err, ErrAllowanceZeroExpectedStorage) {
   235  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedStorage, err)
   236  	}
   237  	a.RenewWindow = 20
   238  	err = c.SetAllowance(a)
   239  	if !errors.Contains(err, ErrAllowanceZeroExpectedStorage) {
   240  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedStorage, err)
   241  	}
   242  	a.RenewWindow = 10
   243  	err = c.SetAllowance(a)
   244  	if !errors.Contains(err, ErrAllowanceZeroExpectedStorage) {
   245  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedStorage, err)
   246  	}
   247  	a.ExpectedStorage = skymodules.DefaultAllowance.ExpectedStorage
   248  	err = c.SetAllowance(a)
   249  	if !errors.Contains(err, ErrAllowanceZeroExpectedUpload) {
   250  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedUpload, err)
   251  	}
   252  	a.ExpectedUpload = skymodules.DefaultAllowance.ExpectedUpload
   253  	err = c.SetAllowance(a)
   254  	if !errors.Contains(err, ErrAllowanceZeroExpectedDownload) {
   255  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedDownload, err)
   256  	}
   257  	a.ExpectedDownload = skymodules.DefaultAllowance.ExpectedDownload
   258  	err = c.SetAllowance(a)
   259  	if !errors.Contains(err, ErrAllowanceZeroExpectedRedundancy) {
   260  		t.Errorf("expected %q, got %q", ErrAllowanceZeroExpectedRedundancy, err)
   261  	}
   262  	a.ExpectedRedundancy = skymodules.DefaultAllowance.ExpectedRedundancy
   263  	a.MaxPeriodChurn = skymodules.DefaultAllowance.MaxPeriodChurn
   264  
   265  	// reasonable values; should succeed
   266  	a.Funds = types.SiacoinPrecision.Mul64(100)
   267  	err = c.SetAllowance(a)
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	err = build.Retry(50, 100*time.Millisecond, func() error {
   272  		if len(c.Contracts()) != 1 {
   273  			return errors.New("allowance forming seems to have failed")
   274  		}
   275  		return nil
   276  	})
   277  	if err != nil {
   278  		t.Error(err)
   279  	}
   280  
   281  	// set same allowance; should no-op
   282  	err = c.SetAllowance(a)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	clen := c.staticContracts.Len()
   287  	if clen != 1 {
   288  		t.Fatal("expected 1 contract, got", clen)
   289  	}
   290  
   291  	_, err = m.AddBlock()
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  
   296  	// set allowance with Hosts = 2; should only form one new contract
   297  	a.Hosts = 2
   298  	err = c.SetAllowance(a)
   299  	if err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	err = build.Retry(50, 100*time.Millisecond, func() error {
   303  		if len(c.Contracts()) != 2 {
   304  			return errors.New("allowance forming seems to have failed")
   305  		}
   306  		return nil
   307  	})
   308  	if err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	// set allowance with Funds*2; should trigger renewal of both contracts
   313  	a.Funds = a.Funds.Mul64(2)
   314  	err = c.SetAllowance(a)
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  	err = build.Retry(50, 100*time.Millisecond, func() error {
   319  		if len(c.Contracts()) != 2 {
   320  			return errors.New("allowance forming seems to have failed")
   321  		}
   322  		return nil
   323  	})
   324  	if err != nil {
   325  		t.Error(err)
   326  	}
   327  
   328  	// delete one of the contracts and set allowance with Funds*2; should
   329  	// trigger 1 renewal and 1 new contract
   330  	c.mu.Lock()
   331  	ids := c.staticContracts.IDs()
   332  	contract, _ := c.staticContracts.Acquire(ids[0])
   333  	c.staticContracts.Delete(contract)
   334  	c.mu.Unlock()
   335  	a.Funds = a.Funds.Mul64(2)
   336  	err = c.SetAllowance(a)
   337  	if err != nil {
   338  		t.Fatal(err)
   339  	}
   340  	err = build.Retry(50, 100*time.Millisecond, func() error {
   341  		if len(c.Contracts()) != 2 {
   342  			return errors.New("allowance forming seems to have failed")
   343  		}
   344  		return nil
   345  	})
   346  	if err != nil {
   347  		t.Fatal(err)
   348  	}
   349  }
   350  
   351  // TestHostMaxDuration tests that a host will not be used if their max duration
   352  // is not sufficient when renewing contracts
   353  func TestHostMaxDuration(t *testing.T) {
   354  	if testing.Short() {
   355  		t.SkipNow()
   356  	}
   357  	t.Parallel()
   358  
   359  	// create testing trio
   360  	h, c, m, cf, err := newTestingTrio(t.Name())
   361  	if err != nil {
   362  		t.Fatal(err)
   363  	}
   364  	defer tryClose(cf, t)
   365  
   366  	// Set host's MaxDuration to 5 to test if host will be skipped when contract
   367  	// is formed
   368  	settings := h.InternalSettings()
   369  	settings.MaxDuration = types.BlockHeight(5)
   370  	if err := h.SetInternalSettings(settings); err != nil {
   371  		t.Fatal(err)
   372  	}
   373  	// Let host settings permeate
   374  	err = build.Retry(1000, 100*time.Millisecond, func() error {
   375  		host, _, err := c.staticHDB.Host(h.PublicKey())
   376  		if err != nil {
   377  			return err
   378  		}
   379  		if settings.MaxDuration != host.MaxDuration {
   380  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   381  		}
   382  		return nil
   383  	})
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  
   388  	// Create allowance
   389  	a := skymodules.Allowance{
   390  		Funds:              types.SiacoinPrecision.Mul64(100),
   391  		Hosts:              1,
   392  		Period:             30,
   393  		RenewWindow:        20,
   394  		ExpectedStorage:    skymodules.DefaultAllowance.ExpectedStorage,
   395  		ExpectedUpload:     skymodules.DefaultAllowance.ExpectedUpload,
   396  		ExpectedDownload:   skymodules.DefaultAllowance.ExpectedDownload,
   397  		ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy,
   398  		MaxPeriodChurn:     skymodules.DefaultAllowance.MaxPeriodChurn,
   399  	}
   400  	err = c.SetAllowance(a)
   401  	if err != nil {
   402  		t.Fatal(err)
   403  	}
   404  
   405  	// Wait for and confirm no Contract creation
   406  	err = build.Retry(50, 100*time.Millisecond, func() error {
   407  		if len(c.Contracts()) == 0 {
   408  			return errors.New("no contract created")
   409  		}
   410  		return nil
   411  	})
   412  	if err == nil {
   413  		t.Fatal("Contract should not have been created")
   414  	}
   415  
   416  	// Set host's MaxDuration to 50 to test if host will now form contract
   417  	settings = h.InternalSettings()
   418  	settings.MaxDuration = types.BlockHeight(50)
   419  	if err := h.SetInternalSettings(settings); err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	// Let host settings permeate
   423  	err = build.Retry(50, 100*time.Millisecond, func() error {
   424  		host, _, err := c.staticHDB.Host(h.PublicKey())
   425  		if err != nil {
   426  			return err
   427  		}
   428  		if settings.MaxDuration != host.MaxDuration {
   429  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   430  		}
   431  		return nil
   432  	})
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	_, err = m.AddBlock()
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  
   441  	// Wait for Contract creation
   442  	err = build.Retry(600, 100*time.Millisecond, func() error {
   443  		if len(c.Contracts()) != 1 {
   444  			return errors.New("no contract created")
   445  		}
   446  		return nil
   447  	})
   448  	if err != nil {
   449  		t.Error(err)
   450  	}
   451  
   452  	// Set host's MaxDuration to 5 to test if host will be skipped when contract
   453  	// is renewed
   454  	settings = h.InternalSettings()
   455  	settings.MaxDuration = types.BlockHeight(5)
   456  	if err := h.SetInternalSettings(settings); err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	// Let host settings permeate
   460  	err = build.Retry(50, 100*time.Millisecond, func() error {
   461  		host, _, err := c.staticHDB.Host(h.PublicKey())
   462  		if err != nil {
   463  			return err
   464  		}
   465  		if settings.MaxDuration != host.MaxDuration {
   466  			return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration)
   467  		}
   468  		return nil
   469  	})
   470  	if err != nil {
   471  		t.Fatal(err)
   472  	}
   473  
   474  	// Mine blocks to renew contract
   475  	for i := types.BlockHeight(0); i <= c.allowance.Period-c.allowance.RenewWindow; i++ {
   476  		_, err = m.AddBlock()
   477  		if err != nil {
   478  			t.Fatal(err)
   479  		}
   480  	}
   481  
   482  	// Confirm Contract is not renewed
   483  	err = build.Retry(50, 100*time.Millisecond, func() error {
   484  		if len(c.OldContracts()) == 0 {
   485  			return errors.New("no contract renewed")
   486  		}
   487  		return nil
   488  	})
   489  	if err == nil {
   490  		t.Fatal("Contract should not have been renewed")
   491  	}
   492  }
   493  
   494  // TestPayment verifies the PaymentProvider interface on the contractor. It does
   495  // this by trying to pay the host using a filecontract and verifying if payment
   496  // can be made successfully.
   497  func TestPayment(t *testing.T) {
   498  	if testing.Short() {
   499  		t.SkipNow()
   500  	}
   501  	t.Parallel()
   502  
   503  	// newStream is a helper to get a ready-to-use stream that is connected to a
   504  	// host.
   505  	newStream := func(mux *siamux.SiaMux, h modules.Host) (siamux.Stream, error) {
   506  		hes := h.ExternalSettings()
   507  		muxAddress := fmt.Sprintf("%s:%s", hes.NetAddress.Host(), hes.SiaMuxPort)
   508  		muxPK := modules.SiaPKToMuxPK(h.PublicKey())
   509  		return mux.NewStream(modules.HostSiaMuxSubscriberName, muxAddress, muxPK)
   510  	}
   511  
   512  	// create a siamux
   513  	testdir := build.TempDir("contractor", t.Name())
   514  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
   515  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
   516  	if err != nil {
   517  		t.Fatal(err)
   518  	}
   519  
   520  	// create a testing trio with our mux injected
   521  	h, c, _, cf, err := newCustomTestingTrio(t.Name(), mux, modules.ProdDependencies, modules.ProdDependencies)
   522  	if err != nil {
   523  		t.Fatal(err)
   524  	}
   525  	defer tryClose(cf, t)
   526  	hpk := h.PublicKey()
   527  
   528  	// set an allowance and wait for contracts
   529  	err = c.SetAllowance(skymodules.DefaultAllowance)
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  
   534  	// create a refund account
   535  	aid, _ := modules.NewAccountID()
   536  
   537  	// Fetch the contracts, there's a race condition between contract creation
   538  	// and the contractor knowing the contract exists, so do this in a retry.
   539  	var contract skymodules.RenterContract
   540  	err = build.Retry(200, 100*time.Millisecond, func() error {
   541  		var ok bool
   542  		contract, ok = c.ContractByPublicKey(hpk)
   543  		if !ok {
   544  			return errors.New("contract not found")
   545  		}
   546  		return nil
   547  	})
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  
   552  	// backup the amount renter funds
   553  	initial := contract.RenterFunds
   554  
   555  	// check spending metrics are zero
   556  	if !contract.FundAccountSpending.IsZero() || !contract.MaintenanceSpending.Sum().IsZero() {
   557  		t.Fatal("unexpected spending metrics")
   558  	}
   559  
   560  	// write the rpc id
   561  	stream, err := newStream(mux, h)
   562  	if err != nil {
   563  		t.Fatal(err)
   564  	}
   565  	err = modules.RPCWrite(stream, modules.RPCUpdatePriceTable)
   566  	if err != nil {
   567  		t.Fatal(err)
   568  	}
   569  
   570  	// read the updated response
   571  	var update modules.RPCUpdatePriceTableResponse
   572  	err = modules.RPCRead(stream, &update)
   573  	if err != nil {
   574  		t.Fatal(err)
   575  	}
   576  
   577  	// unmarshal the JSON into a price table
   578  	var pt modules.RPCPriceTable
   579  	err = json.Unmarshal(update.PriceTableJSON, &pt)
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  
   584  	// build payment details
   585  	details := PaymentDetails{
   586  		Host:          contract.HostPublicKey,
   587  		Amount:        pt.UpdatePriceTableCost,
   588  		RefundAccount: aid,
   589  		SpendingDetails: skymodules.SpendingDetails{
   590  			MaintenanceSpending: skymodules.MaintenanceSpending{
   591  				UpdatePriceTableCost: pt.UpdatePriceTableCost,
   592  			},
   593  		},
   594  	}
   595  
   596  	// provide payment
   597  	err = c.ProvidePayment(stream, &pt, details)
   598  	if err != nil {
   599  		t.Fatal(err)
   600  	}
   601  
   602  	// await the track response
   603  	var tracked modules.RPCTrackedPriceTableResponse
   604  	err = modules.RPCRead(stream, &tracked)
   605  	if err != nil {
   606  		t.Fatal(err)
   607  	}
   608  
   609  	// verify the contract was updated
   610  	contract, _ = c.ContractByPublicKey(hpk)
   611  	remaining := contract.RenterFunds
   612  	expected := initial.Sub(pt.UpdatePriceTableCost)
   613  	if !remaining.Equals(expected) {
   614  		t.Fatalf("Expected renter contract to reflect the payment, the renter funds should be %v but were %v", expected.HumanString(), remaining.HumanString())
   615  	}
   616  
   617  	// check maintenance funding metric got updated
   618  	if !contract.MaintenanceSpending.UpdatePriceTableCost.Equals(pt.UpdatePriceTableCost) {
   619  		t.Fatal("unexpected maintenance spending metric", contract.MaintenanceSpending)
   620  	}
   621  	prev := contract.MaintenanceSpending.FundAccountCost
   622  
   623  	// prepare a buffer so we can optimize our writes
   624  	buffer := bytes.NewBuffer(nil)
   625  
   626  	// write the rpc id
   627  	stream, err = newStream(mux, h)
   628  	if err != nil {
   629  		t.Fatal(err)
   630  	}
   631  	err = modules.RPCWrite(buffer, modules.RPCFundAccount)
   632  	if err != nil {
   633  		t.Fatal(err)
   634  	}
   635  
   636  	// write the price table uid
   637  	err = modules.RPCWrite(buffer, pt.UID)
   638  	if err != nil {
   639  		t.Fatal(err)
   640  	}
   641  
   642  	// send fund account request (re-use the refund account)
   643  	err = modules.RPCWrite(buffer, modules.FundAccountRequest{Account: aid})
   644  	if err != nil {
   645  		t.Fatal(err)
   646  	}
   647  
   648  	// write contents of the buffer to the stream
   649  	_, err = stream.Write(buffer.Bytes())
   650  	if err != nil {
   651  		t.Fatal(err)
   652  	}
   653  
   654  	// provide payment
   655  	funding := remaining.Div64(2)
   656  	if funding.Cmp(h.InternalSettings().MaxEphemeralAccountBalance) > 0 {
   657  		funding = h.InternalSettings().MaxEphemeralAccountBalance
   658  	}
   659  
   660  	// build payment details
   661  	details = PaymentDetails{
   662  		Host:          hpk,
   663  		Amount:        funding.Add(pt.FundAccountCost),
   664  		RefundAccount: modules.ZeroAccountID,
   665  		SpendingDetails: skymodules.SpendingDetails{
   666  			FundAccountSpending: funding,
   667  			MaintenanceSpending: skymodules.MaintenanceSpending{
   668  				FundAccountCost: pt.FundAccountCost,
   669  			},
   670  		},
   671  	}
   672  	err = c.ProvidePayment(stream, &pt, details)
   673  	if err != nil {
   674  		t.Fatal(err)
   675  	}
   676  
   677  	// receive response
   678  	var resp modules.FundAccountResponse
   679  	err = modules.RPCRead(stream, &resp)
   680  	if err != nil {
   681  		t.Fatal(err)
   682  	}
   683  
   684  	// verify the receipt
   685  	receipt := resp.Receipt
   686  	err = crypto.VerifyHash(crypto.HashAll(receipt), hpk.ToPublicKey(), resp.Signature)
   687  	if err != nil {
   688  		t.Fatal(err)
   689  	}
   690  	if !receipt.Amount.Equals(funding) {
   691  		t.Fatalf("Unexpected funded amount in the receipt, expected %v but received %v", funding.HumanString(), receipt.Amount.HumanString())
   692  	}
   693  	if receipt.Account != aid {
   694  		t.Fatalf("Unexpected account id in the receipt, expected %v but received %v", aid, receipt.Account)
   695  	}
   696  	if !receipt.Host.Equals(hpk) {
   697  		t.Fatalf("Unexpected host pubkey in the receipt, expected %v but received %v", hpk, receipt.Host)
   698  	}
   699  
   700  	// check fund account metric got updated
   701  	contract, _ = c.ContractByPublicKey(hpk)
   702  	if !contract.FundAccountSpending.Equals(funding) {
   703  		t.Fatalf("unexpected funding spending metric %v != %v", contract.FundAccountSpending, funding)
   704  	}
   705  	if !contract.MaintenanceSpending.FundAccountCost.Equals(prev.Add(pt.FundAccountCost)) {
   706  		t.Fatalf("unexpected maintenance spending metric %v != %v", contract.MaintenanceSpending, prev.Add(pt.FundAccountCost))
   707  	}
   708  }
   709  
   710  // TestLinkedContracts tests that the contractors maps are updated correctly
   711  // when renewing contracts
   712  func TestLinkedContracts(t *testing.T) {
   713  	if testing.Short() {
   714  		t.SkipNow()
   715  	}
   716  	t.Parallel()
   717  
   718  	// create testing trio
   719  	h, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{})
   720  	if err != nil {
   721  		t.Fatal(err)
   722  	}
   723  	defer tryClose(cf, t)
   724  
   725  	// Create allowance
   726  	a := skymodules.Allowance{
   727  		Funds:              types.SiacoinPrecision.Mul64(100),
   728  		Hosts:              1,
   729  		Period:             20,
   730  		RenewWindow:        10,
   731  		ExpectedStorage:    skymodules.DefaultAllowance.ExpectedStorage,
   732  		ExpectedUpload:     skymodules.DefaultAllowance.ExpectedUpload,
   733  		ExpectedDownload:   skymodules.DefaultAllowance.ExpectedDownload,
   734  		ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy,
   735  		MaxPeriodChurn:     skymodules.DefaultAllowance.MaxPeriodChurn,
   736  	}
   737  	err = c.SetAllowance(a)
   738  	if err != nil {
   739  		t.Fatal(err)
   740  	}
   741  
   742  	// Wait for Contract creation
   743  	numRetries := 0
   744  	err = build.Retry(200, 100*time.Millisecond, func() error {
   745  		if numRetries%10 == 0 {
   746  			if _, err := m.AddBlock(); err != nil {
   747  				return err
   748  			}
   749  		}
   750  		numRetries++
   751  		if len(c.Contracts()) != 1 {
   752  			return errors.New("no contract created")
   753  		}
   754  		return nil
   755  	})
   756  	if err != nil {
   757  		t.Error(err)
   758  	}
   759  
   760  	// Confirm that maps are empty
   761  	if len(c.renewedFrom) != 0 {
   762  		t.Fatal("renewedFrom map should be empty")
   763  	}
   764  	if len(c.renewedTo) != 0 {
   765  		t.Fatal("renewedTo map should be empty")
   766  	}
   767  
   768  	// Set host's uploadbandwidthprice to zero to test divide by zero check when
   769  	// contracts are renewed
   770  	settings := h.InternalSettings()
   771  	settings.MinUploadBandwidthPrice = types.ZeroCurrency
   772  	if err := h.SetInternalSettings(settings); err != nil {
   773  		t.Fatal(err)
   774  	}
   775  
   776  	// Mine blocks to renew contract
   777  	for i := types.BlockHeight(0); i < c.allowance.Period-c.allowance.RenewWindow; i++ {
   778  		_, err = m.AddBlock()
   779  		if err != nil {
   780  			t.Fatal(err)
   781  		}
   782  	}
   783  
   784  	// Confirm Contracts got renewed
   785  	err = build.Retry(200, 100*time.Millisecond, func() error {
   786  		if len(c.Contracts()) != 1 {
   787  			return errors.New("no contract")
   788  		}
   789  		if len(c.OldContracts()) != 1 {
   790  			return errors.New("no old contract")
   791  		}
   792  		return nil
   793  	})
   794  	if err != nil {
   795  		t.Error(err)
   796  	}
   797  
   798  	// Confirm maps are updated as expected
   799  	if len(c.renewedFrom) != 1 {
   800  		t.Fatalf("renewedFrom map should have 1 entry but has %v", len(c.renewedFrom))
   801  	}
   802  	if len(c.renewedTo) != 1 {
   803  		t.Fatalf("renewedTo map should have 1 entry but has %v", len(c.renewedTo))
   804  	}
   805  	if c.renewedFrom[c.Contracts()[0].ID] != c.OldContracts()[0].ID {
   806  		t.Fatalf(`Map assignment incorrect,
   807  		expected:
   808  		map[%v:%v]
   809  		got:
   810  		%v`, c.Contracts()[0].ID, c.OldContracts()[0].ID, c.renewedFrom)
   811  	}
   812  	if c.renewedTo[c.OldContracts()[0].ID] != c.Contracts()[0].ID {
   813  		t.Fatalf(`Map assignment incorrect,
   814  		expected:
   815  		map[%v:%v]
   816  		got:
   817  		%v`, c.OldContracts()[0].ID, c.Contracts()[0].ID, c.renewedTo)
   818  	}
   819  }
   820  
   821  // TestPaymentMissingStorageObligation tests the case where a host can't find a
   822  // storage obligation with which to pay.
   823  func TestPaymentMissingStorageObligation(t *testing.T) {
   824  	if testing.Short() {
   825  		t.SkipNow()
   826  	}
   827  	t.Parallel()
   828  
   829  	// create a siamux
   830  	testdir := build.TempDir("contractor", t.Name())
   831  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
   832  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
   833  	if err != nil {
   834  		t.Fatal(err)
   835  	}
   836  
   837  	// create a testing trio with our mux injected
   838  	deps := &dependencies.DependencyStorageObligationNotFound{}
   839  	h, c, _, cf, err := newCustomTestingTrio(t.Name(), mux, deps, modules.ProdDependencies)
   840  	if err != nil {
   841  		t.Fatal(err)
   842  	}
   843  	defer cf()
   844  	hpk := h.PublicKey()
   845  
   846  	// set an allowance and wait for contracts
   847  	err = c.SetAllowance(skymodules.DefaultAllowance)
   848  	if err != nil {
   849  		t.Fatal(err)
   850  	}
   851  
   852  	// create a refund account
   853  	aid, _ := modules.NewAccountID()
   854  
   855  	// Fetch the contracts, there's a race condition between contract creation
   856  	// and the contractor knowing the contract exists, so do this in a retry.
   857  	var contract skymodules.RenterContract
   858  	err = build.Retry(200, 100*time.Millisecond, func() error {
   859  		var ok bool
   860  		contract, ok = c.ContractByPublicKey(hpk)
   861  		if !ok {
   862  			return errors.New("contract not found")
   863  		}
   864  		return nil
   865  	})
   866  	if err != nil {
   867  		t.Fatal(err)
   868  	}
   869  
   870  	// get a stream
   871  	stream, err := newStream(mux, h)
   872  	if err != nil {
   873  		t.Fatal(err)
   874  	}
   875  	// write the rpc id
   876  	err = modules.RPCWrite(stream, modules.RPCUpdatePriceTable)
   877  	if err != nil {
   878  		t.Fatal(err)
   879  	}
   880  
   881  	// read the updated response
   882  	var update modules.RPCUpdatePriceTableResponse
   883  	err = modules.RPCRead(stream, &update)
   884  	if err != nil {
   885  		t.Fatal(err)
   886  	}
   887  
   888  	// unmarshal the JSON into a price table
   889  	var pt modules.RPCPriceTable
   890  	err = json.Unmarshal(update.PriceTableJSON, &pt)
   891  	if err != nil {
   892  		t.Fatal(err)
   893  	}
   894  
   895  	// build payment details
   896  	details := PaymentDetails{
   897  		Host:          contract.HostPublicKey,
   898  		Amount:        pt.UpdatePriceTableCost,
   899  		RefundAccount: aid,
   900  		SpendingDetails: skymodules.SpendingDetails{
   901  			MaintenanceSpending: skymodules.MaintenanceSpending{
   902  				UpdatePriceTableCost: pt.UpdatePriceTableCost,
   903  			},
   904  		},
   905  	}
   906  
   907  	// provide payment
   908  	err = c.ProvidePayment(stream, &pt, details)
   909  	if err == nil || !strings.Contains(err.Error(), "storage obligation not found") {
   910  		t.Fatal("expected storage obligation not found but got", err)
   911  	}
   912  
   913  	// verify the contract was updated
   914  	contract, _ = c.ContractByPublicKey(hpk)
   915  	if contract.Utility.GoodForRenew {
   916  		t.Fatal("GFR should be false")
   917  	}
   918  	if contract.Utility.GoodForUpload {
   919  		t.Fatal("GFU should be false")
   920  	}
   921  	if !contract.Utility.BadContract {
   922  		t.Fatal("Contract should be bad")
   923  	}
   924  	if contract.Utility.Locked {
   925  		t.Fatal("Contract should not be locked")
   926  	}
   927  }