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

     1  package contractor
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"gitlab.com/NebulousLabs/errors"
    13  	"gitlab.com/NebulousLabs/fastrand"
    14  	"gitlab.com/NebulousLabs/ratelimit"
    15  	"gitlab.com/NebulousLabs/siamux"
    16  
    17  	"gitlab.com/NebulousLabs/encoding"
    18  	"gitlab.com/SkynetLabs/skyd/build"
    19  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    20  	"gitlab.com/SkynetLabs/skyd/skymodules"
    21  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb"
    22  	"go.sia.tech/siad/crypto"
    23  	"go.sia.tech/siad/modules"
    24  	"go.sia.tech/siad/modules/consensus"
    25  	"go.sia.tech/siad/modules/gateway"
    26  	"go.sia.tech/siad/modules/host"
    27  	"go.sia.tech/siad/modules/miner"
    28  	"go.sia.tech/siad/modules/transactionpool"
    29  	modWallet "go.sia.tech/siad/modules/wallet"
    30  	"go.sia.tech/siad/types"
    31  )
    32  
    33  // newTestingWallet is a helper function that creates a ready-to-use wallet
    34  // and mines some coins into it.
    35  func newTestingWallet(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Wallet, closeFn, error) {
    36  	w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
    37  	if err != nil {
    38  		return nil, nil, err
    39  	}
    40  	key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet)
    41  	encrypted, err := w.Encrypted()
    42  	if err != nil {
    43  		return nil, nil, err
    44  	}
    45  	if !encrypted {
    46  		_, err = w.Encrypt(key)
    47  		if err != nil {
    48  			return nil, nil, err
    49  		}
    50  	}
    51  	err = w.Unlock(key)
    52  	if err != nil {
    53  		return nil, nil, err
    54  	}
    55  	// give it some money
    56  	m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
    57  	if err != nil {
    58  		return nil, nil, err
    59  	}
    60  	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
    61  		_, err := m.AddBlock()
    62  		if err != nil {
    63  			return nil, nil, err
    64  		}
    65  	}
    66  
    67  	cf := func() error {
    68  		return errors.Compose(m.Close(), w.Close())
    69  	}
    70  	return w, cf, nil
    71  }
    72  
    73  // newCustomTestingHost is a helper function that creates a ready-to-use host.
    74  func newCustomTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool, mux *siamux.SiaMux, deps modules.Dependencies) (modules.Host, closeFn, error) {
    75  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
    76  	if err != nil {
    77  		return nil, nil, err
    78  	}
    79  	w, walletCF, err := newTestingWallet(testdir, cs, tp)
    80  	if err != nil {
    81  		return nil, nil, err
    82  	}
    83  	h, err := host.NewCustomHost(deps, cs, g, tp, w, mux, "localhost:0", filepath.Join(testdir, modules.HostDir))
    84  	if err != nil {
    85  		return nil, nil, err
    86  	}
    87  
    88  	// configure host to accept contracts
    89  	settings := h.InternalSettings()
    90  	settings.AcceptingContracts = true
    91  	err = h.SetInternalSettings(settings)
    92  	if err != nil {
    93  		return nil, nil, err
    94  	}
    95  
    96  	// add storage to host
    97  	storageFolder := filepath.Join(testdir, "storage")
    98  	err = os.MkdirAll(storageFolder, 0700)
    99  	if err != nil {
   100  		return nil, nil, err
   101  	}
   102  	err = h.AddStorageFolder(storageFolder, modules.SectorSize*64)
   103  	if err != nil {
   104  		return nil, nil, err
   105  	}
   106  
   107  	cf := func() error {
   108  		return errors.Compose(h.Close(), walletCF(), g.Close())
   109  	}
   110  	return h, cf, nil
   111  }
   112  
   113  // newTestingHost is a helper function that creates a ready-to-use host.
   114  func newTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool, mux *siamux.SiaMux) (modules.Host, closeFn, error) {
   115  	return newCustomTestingHost(testdir, cs, tp, mux, modules.ProdDependencies)
   116  }
   117  
   118  // newTestingContractor is a helper function that creates a ready-to-use
   119  // contractor.
   120  func newTestingContractor(testdir string, g modules.Gateway, cs modules.ConsensusSet, tp modules.TransactionPool, rl *ratelimit.RateLimit, deps modules.Dependencies) (*Contractor, closeFn, error) {
   121  	w, walletCF, err := newTestingWallet(testdir, cs, tp)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
   126  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
   127  	if err != nil {
   128  		return nil, nil, err
   129  	}
   130  	hdb, errChan := hostdb.New(g, cs, tp, mux, filepath.Join(testdir, "hostdb"))
   131  	if err := <-errChan; err != nil {
   132  		return nil, nil, err
   133  	}
   134  	contractor, errChan := newWithDeps(cs, w, tp, hdb, rl, filepath.Join(testdir, "contractor"), deps)
   135  	err = <-errChan
   136  	if err != nil {
   137  		return nil, nil, err
   138  	}
   139  	cf := func() error {
   140  		return errors.Compose(contractor.Close(), hdb.Close(), mux.Close(), walletCF())
   141  	}
   142  	return contractor, cf, <-errChan
   143  }
   144  
   145  // newTestingTrio creates a Host, Contractor, and TestMiner that can be
   146  // used for testing host/renter interactions.
   147  func newTestingTrio(name string) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) {
   148  	return newTestingTrioWithContractorDeps(name, modules.ProdDependencies)
   149  }
   150  
   151  // newTestingTrioWithContractorDeps creates a Host, Contractor, and TestMiner
   152  // that can be used for testing host/renter interactions.
   153  func newTestingTrioWithContractorDeps(name string, deps modules.Dependencies) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) {
   154  	testdir := build.TempDir("contractor", name)
   155  
   156  	// create mux
   157  	siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir)
   158  	mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0")
   159  	if err != nil {
   160  		return nil, nil, nil, nil, err
   161  	}
   162  
   163  	return newCustomTestingTrio(name, mux, modules.ProdDependencies, deps)
   164  }
   165  
   166  // newCustomTestingTrio creates a Host, Contractor, and TestMiner that can be
   167  // used for testing host/renter interactions. It allows to pass a custom siamux.
   168  func newCustomTestingTrio(name string, mux *siamux.SiaMux, hdeps, cdeps modules.Dependencies) (modules.Host, *Contractor, modules.TestMiner, closeFn, error) {
   169  	testdir := build.TempDir("contractor", name)
   170  
   171  	// create miner
   172  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
   173  	if err != nil {
   174  		return nil, nil, nil, nil, err
   175  	}
   176  	cs, errChan := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir))
   177  	if err := <-errChan; err != nil {
   178  		return nil, nil, nil, nil, err
   179  	}
   180  	tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir))
   181  	if err != nil {
   182  		return nil, nil, nil, nil, err
   183  	}
   184  	w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
   185  	if err != nil {
   186  		return nil, nil, nil, nil, err
   187  	}
   188  	key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet)
   189  	encrypted, err := w.Encrypted()
   190  	if err != nil {
   191  		return nil, nil, nil, nil, err
   192  	}
   193  	if !encrypted {
   194  		_, err = w.Encrypt(key)
   195  		if err != nil {
   196  			return nil, nil, nil, nil, err
   197  		}
   198  	}
   199  	err = w.Unlock(key)
   200  	if err != nil {
   201  		return nil, nil, nil, nil, err
   202  	}
   203  	m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
   204  	if err != nil {
   205  		return nil, nil, nil, nil, err
   206  	}
   207  
   208  	// create host and contractor, using same consensus set and gateway
   209  	h, hostCF, err := newCustomTestingHost(filepath.Join(testdir, "Host"), cs, tp, mux, hdeps)
   210  	if err != nil {
   211  		return nil, nil, nil, nil, build.ExtendErr("error creating testing host", err)
   212  	}
   213  	c, contractorCF, err := newTestingContractor(filepath.Join(testdir, "Contractor"), g, cs, tp, ratelimit.NewRateLimit(0, 0, 0), cdeps)
   214  	if err != nil {
   215  		return nil, nil, nil, nil, err
   216  	}
   217  
   218  	// announce the host
   219  	err = h.Announce()
   220  	if err != nil {
   221  		return nil, nil, nil, nil, build.ExtendErr("error announcing host", err)
   222  	}
   223  
   224  	// mine a block, processing the announcement
   225  	_, err = m.AddBlock()
   226  	if err != nil {
   227  		return nil, nil, nil, nil, err
   228  	}
   229  
   230  	// wait for hostdb to scan host
   231  	err = build.Retry(100, 100*time.Millisecond, func() error {
   232  		activeHosts, err := c.staticHDB.ActiveHosts()
   233  		if err != nil {
   234  			return err
   235  		}
   236  		if len(activeHosts) == 0 {
   237  			return errors.New("no active hosts")
   238  		}
   239  		complete, scanCheckErr := c.staticHDB.InitialScanComplete()
   240  		if scanCheckErr != nil {
   241  			return scanCheckErr
   242  		}
   243  		if !complete {
   244  			return errors.New("initial scan not complete")
   245  		}
   246  		return nil
   247  	})
   248  	if err != nil {
   249  		return nil, nil, nil, nil, err
   250  	}
   251  
   252  	cf := func() error {
   253  		return errors.Compose(mux.Close(), m.Close(), contractorCF(), hostCF(), w.Close(), tp.Close(), cs.Close(), g.Close())
   254  	}
   255  	return h, c, m, cf, nil
   256  }
   257  
   258  // TestIntegrationFormContract tests that the contractor can form contracts
   259  // with the host module.
   260  func TestIntegrationFormContract(t *testing.T) {
   261  	if testing.Short() {
   262  		t.SkipNow()
   263  	}
   264  	t.Parallel()
   265  	h, c, _, cf, err := newTestingTrio(t.Name())
   266  	if err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	defer tryClose(cf, t)
   270  
   271  	// acquire the contract maintenance lock for the duration of the test. This
   272  	// prevents theadedContractMaintenance from running.
   273  	c.maintenanceLock.Lock()
   274  	defer c.maintenanceLock.Unlock()
   275  
   276  	// get the host's entry from the db
   277  	hostEntry, ok, err := c.staticHDB.Host(h.PublicKey())
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	if !ok {
   282  		t.Fatal("no entry for host in db")
   283  	}
   284  
   285  	// set an allowance but don't use SetAllowance to avoid automatic contract
   286  	// formation.
   287  	c.mu.Lock()
   288  	c.allowance = skymodules.DefaultAllowance
   289  	a := c.allowance
   290  	c.mu.Unlock()
   291  
   292  	// form a contract with the host
   293  	_, _, err = c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  }
   298  
   299  // TestFormContractSmallAllowance tests to make sure that a contract doesn't
   300  // form when there are insufficient funds in the allowance
   301  func TestFormContractSmallAllowance(t *testing.T) {
   302  	if testing.Short() {
   303  		t.SkipNow()
   304  	}
   305  	t.Parallel()
   306  	h, c, _, cf, err := newTestingTrio(t.Name())
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	defer tryClose(cf, t)
   311  
   312  	// get the host's entry from the db
   313  	hostEntry, ok, err := c.staticHDB.Host(h.PublicKey())
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  	if !ok {
   318  		t.Fatal("no entry for host in db")
   319  	}
   320  
   321  	// set an allowance but don't use SetAllowance to avoid automatic contract
   322  	// formation. Setting funds to 1SC to mimic bug report found in production.
   323  	// Using production number of hosts as well
   324  	c.mu.Lock()
   325  	c.allowance = skymodules.DefaultAllowance
   326  	c.allowance.Funds = types.SiacoinPrecision.Mul64(1)
   327  	c.allowance.Hosts = uint64(50)
   328  	a := c.allowance
   329  	initialContractFunds := c.allowance.Funds.Div64(c.allowance.Hosts).Div64(3)
   330  	c.mu.Unlock()
   331  
   332  	// try to form a contract with the host
   333  	_, _, err = c.managedNewContract(hostEntry, a, initialContractFunds, c.blockHeight+100)
   334  	if err == nil {
   335  		t.Fatal("Expected underflow error for insufficient funds")
   336  	}
   337  }
   338  
   339  // TestIntegrationReviseContract tests that the contractor can revise a
   340  // contract previously formed with a host.
   341  func TestIntegrationReviseContract(t *testing.T) {
   342  	if testing.Short() {
   343  		t.SkipNow()
   344  	}
   345  	t.Parallel()
   346  	// create testing trio
   347  	h, c, _, cf, err := newTestingTrio(t.Name())
   348  	if err != nil {
   349  		t.Fatal(err)
   350  	}
   351  	defer tryClose(cf, t)
   352  
   353  	// acquire the contract maintenance lock for the duration of the test. This
   354  	// prevents theadedContractMaintenance from running.
   355  	c.maintenanceLock.Lock()
   356  	defer c.maintenanceLock.Unlock()
   357  
   358  	// get the host's entry from the db
   359  	hostEntry, ok, err := c.staticHDB.Host(h.PublicKey())
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	if !ok {
   364  		t.Fatal("no entry for host in db")
   365  	}
   366  
   367  	// set an allowance but don't use SetAllowance to avoid automatic contract
   368  	// formation.
   369  	c.mu.Lock()
   370  	c.allowance = skymodules.DefaultAllowance
   371  	a := c.allowance
   372  	c.mu.Unlock()
   373  
   374  	// form a contract with the host
   375  	_, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   376  	if err != nil {
   377  		t.Fatal(err)
   378  	}
   379  
   380  	// revise the contract
   381  	editor, err := c.Editor(contract.HostPublicKey, nil)
   382  	if err != nil {
   383  		t.Fatal(err)
   384  	}
   385  	data := fastrand.Bytes(int(modules.SectorSize))
   386  	_, err = editor.Upload(data)
   387  	if err != nil {
   388  		t.Fatal(err)
   389  	}
   390  	err = editor.Close()
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  }
   395  
   396  // TestIntegrationUploadDownload tests that the contractor can upload data to
   397  // a host and download it intact.
   398  func TestIntegrationUploadDownload(t *testing.T) {
   399  	if testing.Short() {
   400  		t.SkipNow()
   401  	}
   402  	t.Parallel()
   403  	// create testing trio
   404  	h, c, _, cf, err := newTestingTrio(t.Name())
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	defer tryClose(cf, t)
   409  
   410  	// get the host's entry from the db
   411  	hostEntry, ok, err := c.staticHDB.Host(h.PublicKey())
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	if !ok {
   416  		t.Fatal("no entry for host in db")
   417  	}
   418  
   419  	// set an allowance but don't use SetAllowance to avoid automatic contract
   420  	// formation.
   421  	c.mu.Lock()
   422  	c.allowance = skymodules.DefaultAllowance
   423  	a := c.allowance
   424  	c.mu.Unlock()
   425  
   426  	// form a contract with the host
   427  	_, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   428  	if err != nil {
   429  		t.Fatal(err)
   430  	}
   431  
   432  	// revise the contract
   433  	editor, err := c.Editor(contract.HostPublicKey, nil)
   434  	if err != nil {
   435  		t.Fatal(err)
   436  	}
   437  	data := fastrand.Bytes(int(modules.SectorSize))
   438  	root, err := editor.Upload(data)
   439  	if err != nil {
   440  		t.Fatal(err)
   441  	}
   442  	err = editor.Close()
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  
   447  	// download the data
   448  	downloader, err := c.Downloader(contract.HostPublicKey, nil)
   449  	if err != nil {
   450  		t.Fatal(err)
   451  	}
   452  	retrieved, err := downloader.Download(root, 0, uint32(modules.SectorSize))
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  	if !bytes.Equal(data, retrieved) {
   457  		t.Fatal("downloaded data does not match original")
   458  	}
   459  	err = downloader.Close()
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  }
   464  
   465  // TestIntegrationRenew tests that the contractor can renew a previously-
   466  // formed file contract.
   467  func TestIntegrationRenew(t *testing.T) {
   468  	if testing.Short() {
   469  		t.SkipNow()
   470  	}
   471  	t.Parallel()
   472  	// create testing trio
   473  	_, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{})
   474  	if err != nil {
   475  		t.Fatal(err)
   476  	}
   477  	defer tryClose(cf, t)
   478  
   479  	// set an allowance and wait for a contract to be formed.
   480  	a := skymodules.DefaultAllowance
   481  	a.Hosts = 1
   482  	if err := c.SetAllowance(a); err != nil {
   483  		t.Fatal(err)
   484  	}
   485  	numRetries := 0
   486  	err = build.Retry(100, 100*time.Millisecond, func() error {
   487  		if numRetries%10 == 0 {
   488  			if _, err := m.AddBlock(); err != nil {
   489  				return err
   490  			}
   491  		}
   492  		numRetries++
   493  		// Check for number of contracts and number of pubKeys as there is a
   494  		// slight delay between the contract being added to the contract set and
   495  		// the pubkey being added to the contractor map
   496  		c.mu.Lock()
   497  		numPubKeys := len(c.pubKeysToContractID)
   498  		c.mu.Unlock()
   499  		numContracts := len(c.Contracts())
   500  		if numContracts != 1 {
   501  			return fmt.Errorf("Expected 1 contracts, found %v", numContracts)
   502  		}
   503  		if numPubKeys != 1 {
   504  			return fmt.Errorf("Expected 1 pubkey, found %v", numPubKeys)
   505  		}
   506  		return nil
   507  	})
   508  	if err != nil {
   509  		t.Fatal(err)
   510  	}
   511  	// get the contract
   512  	contract := c.Contracts()[0]
   513  
   514  	// revise the contract
   515  	editor, err := c.Editor(contract.HostPublicKey, nil)
   516  	if err != nil {
   517  		t.Fatal(err)
   518  	}
   519  	data := fastrand.Bytes(int(modules.SectorSize))
   520  	// insert the sector
   521  	root, err := editor.Upload(data)
   522  	if err != nil {
   523  		t.Fatal(err)
   524  	}
   525  	err = editor.Close()
   526  	if err != nil {
   527  		t.Fatal(err)
   528  	}
   529  
   530  	// Grab the host settings.
   531  	hostSettings := editor.HostSettings()
   532  
   533  	// renew the contract
   534  	err = c.managedAcquireAndUpdateContractUtility(contract.ID, skymodules.ContractUtility{GoodForRenew: true}, false)
   535  	if err != nil {
   536  		t.Fatal(err)
   537  	}
   538  	contract, err = c.managedRenew(contract.ID, a, contract.HostPublicKey, types.SiacoinPrecision.Mul64(50), c.blockHeight+200, hostSettings)
   539  	if err != nil {
   540  		t.Fatal(err)
   541  	}
   542  
   543  	// check renewed contract
   544  	if contract.EndHeight != c.blockHeight+200 {
   545  		t.Fatal(contract.EndHeight)
   546  	}
   547  
   548  	// download the renewed contract
   549  	downloader, err := c.Downloader(contract.HostPublicKey, nil)
   550  	if err != nil {
   551  		t.Fatal(err)
   552  	}
   553  	retrieved, err := downloader.Download(root, 0, uint32(modules.SectorSize))
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  	if !bytes.Equal(data, retrieved) {
   558  		t.Fatal("downloaded data does not match original")
   559  	}
   560  	err = downloader.Close()
   561  	if err != nil {
   562  		t.Fatal(err)
   563  	}
   564  
   565  	// renew to a lower height
   566  	err = c.managedAcquireAndUpdateContractUtility(contract.ID, skymodules.ContractUtility{GoodForRenew: true}, false)
   567  	if err != nil {
   568  		t.Fatal(err)
   569  	}
   570  	contract, err = c.managedRenew(contract.ID, a, contract.HostPublicKey, types.SiacoinPrecision.Mul64(50), c.blockHeight+100, hostSettings)
   571  	if err != nil {
   572  		t.Fatal(err)
   573  	}
   574  	if contract.EndHeight != c.blockHeight+100 {
   575  		t.Fatal(contract.EndHeight)
   576  	}
   577  
   578  	// revise the contract
   579  	editor, err = c.Editor(contract.HostPublicKey, nil)
   580  	if err != nil {
   581  		t.Fatal(err)
   582  	}
   583  	data = fastrand.Bytes(int(modules.SectorSize))
   584  	// insert the sector
   585  	_, err = editor.Upload(data)
   586  	if err != nil {
   587  		t.Fatal(err)
   588  	}
   589  	err = editor.Close()
   590  	if err != nil {
   591  		t.Fatal(err)
   592  	}
   593  }
   594  
   595  // TestIntegrationDownloaderCaching tests that downloaders are properly cached
   596  // by the contractor. When two downloaders are requested for the same
   597  // contract, only one underlying downloader should be created.
   598  func TestIntegrationDownloaderCaching(t *testing.T) {
   599  	if testing.Short() {
   600  		t.SkipNow()
   601  	}
   602  	t.Parallel()
   603  	// create testing trio
   604  	_, c, m, cf, err := newTestingTrio(t.Name())
   605  	if err != nil {
   606  		t.Fatal(err)
   607  	}
   608  	defer tryClose(cf, t)
   609  
   610  	// set an allowance and wait for a contract to be formed.
   611  	if err := c.SetAllowance(skymodules.DefaultAllowance); err != nil {
   612  		t.Fatal(err)
   613  	}
   614  	if err := build.Retry(10, time.Second, func() error {
   615  		_, err := m.AddBlock()
   616  		if err != nil {
   617  			return err
   618  		}
   619  		if len(c.Contracts()) == 0 {
   620  			return errors.New("no contracts were formed")
   621  		}
   622  		return nil
   623  	}); err != nil {
   624  		t.Fatal(err)
   625  	}
   626  	// get the contract
   627  	contract := c.Contracts()[0]
   628  
   629  	// create a downloader
   630  	d1, err := c.Downloader(contract.HostPublicKey, nil)
   631  	if err != nil {
   632  		t.Fatal(err)
   633  	}
   634  
   635  	// create another downloader
   636  	d2, err := c.Downloader(contract.HostPublicKey, nil)
   637  	if err != nil {
   638  		t.Fatal(err)
   639  	}
   640  
   641  	// downloaders should match
   642  	if d1 != d2 {
   643  		t.Fatal("downloader was not cached")
   644  	}
   645  
   646  	// close one of the downloaders; it should not fully close, since d1 is
   647  	// still using it
   648  	if err := d2.Close(); err != nil {
   649  		t.Fatal(err)
   650  	}
   651  
   652  	c.mu.RLock()
   653  	_, ok := c.downloaders[contract.ID]
   654  	_, sok := c.sessions[contract.ID]
   655  	c.mu.RUnlock()
   656  	if !ok && !sok {
   657  		t.Fatal("expected downloader to still be present")
   658  	}
   659  
   660  	// create another downloader
   661  	d3, err := c.Downloader(contract.HostPublicKey, nil)
   662  	if err != nil {
   663  		t.Fatal(err)
   664  	}
   665  
   666  	// downloaders should match
   667  	if d3 != d1 {
   668  		t.Fatal("closing one client should not fully close the downloader")
   669  	}
   670  
   671  	// close both downloaders
   672  	if err := d1.Close(); err != nil {
   673  		t.Fatal(err)
   674  	}
   675  	if err := d2.Close(); err != nil {
   676  		t.Fatal(err)
   677  	}
   678  
   679  	c.mu.RLock()
   680  	_, ok = c.downloaders[contract.ID]
   681  	_, sok = c.sessions[contract.ID]
   682  	c.mu.RUnlock()
   683  	if ok || sok {
   684  		t.Fatal("did not expect downloader to still be present")
   685  	}
   686  
   687  	// create another downloader
   688  	d4, err := c.Downloader(contract.HostPublicKey, nil)
   689  	if err != nil {
   690  		t.Fatal(err)
   691  	}
   692  
   693  	// downloaders should match
   694  	if d4 == d1 {
   695  		t.Fatal("downloader should not have been cached after all clients were closed")
   696  	}
   697  	d4.Close()
   698  }
   699  
   700  // TestIntegrationEditorCaching tests that editors are properly cached
   701  // by the contractor. When two editors are requested for the same
   702  // contract, only one underlying editor should be created.
   703  func TestIntegrationEditorCaching(t *testing.T) {
   704  	if testing.Short() {
   705  		t.SkipNow()
   706  	}
   707  	t.Parallel()
   708  	// create testing trio
   709  	_, c, m, cf, err := newTestingTrio(t.Name())
   710  	if err != nil {
   711  		t.Fatal(err)
   712  	}
   713  	defer tryClose(cf, t)
   714  
   715  	// set an allowance and wait for a contract to be formed.
   716  	if err := c.SetAllowance(skymodules.DefaultAllowance); err != nil {
   717  		t.Fatal(err)
   718  	}
   719  	numRetries := 0
   720  	if err := build.Retry(2000, 100*time.Millisecond, func() error {
   721  		if numRetries%10 == 0 {
   722  			if _, err := m.AddBlock(); err != nil {
   723  				return err
   724  			}
   725  		}
   726  		numRetries++
   727  		if len(c.Contracts()) == 0 {
   728  			return errors.New("no contracts were formed")
   729  		}
   730  		return nil
   731  	}); err != nil {
   732  		t.Fatal(err)
   733  	}
   734  	// get the contract
   735  	contract := c.Contracts()[0]
   736  
   737  	// create an editor
   738  	var d1 Editor
   739  	err = build.Retry(100, 100*time.Millisecond, func() error {
   740  		d1, err = c.Editor(contract.HostPublicKey, nil)
   741  		if err != nil {
   742  			return err
   743  		}
   744  		return nil
   745  	})
   746  	if err != nil {
   747  		t.Fatal(err)
   748  	}
   749  
   750  	// create another editor
   751  	d2, err := c.Editor(contract.HostPublicKey, nil)
   752  	if err != nil {
   753  		t.Fatal(err)
   754  	}
   755  
   756  	// editors should match
   757  	if d1 != d2 {
   758  		t.Fatal("editor was not cached")
   759  	}
   760  
   761  	// close one of the editors; it should not fully close, since d1 is
   762  	// still using it
   763  	if err := d2.Close(); err != nil {
   764  		t.Fatal(err)
   765  	}
   766  
   767  	c.mu.RLock()
   768  	_, ok := c.editors[contract.ID]
   769  	_, sok := c.sessions[contract.ID]
   770  	c.mu.RUnlock()
   771  	if !ok && !sok {
   772  		t.Fatal("expected editor to still be present")
   773  	}
   774  
   775  	// create another editor
   776  	d3, err := c.Editor(contract.HostPublicKey, nil)
   777  	if err != nil {
   778  		t.Fatal(err)
   779  	}
   780  
   781  	// editors should match
   782  	if d3 != d1 {
   783  		t.Fatal("closing one client should not fully close the editor")
   784  	}
   785  
   786  	// close both editors
   787  	if err := d1.Close(); err != nil {
   788  		t.Fatal(err)
   789  	}
   790  	if err := d2.Close(); err != nil {
   791  		t.Fatal(err)
   792  	}
   793  
   794  	c.mu.RLock()
   795  	_, ok = c.editors[contract.ID]
   796  	_, sok = c.sessions[contract.ID]
   797  	c.mu.RUnlock()
   798  	if ok || sok {
   799  		t.Fatal("did not expect editor to still be present")
   800  	}
   801  
   802  	// create another editor
   803  	d4, err := c.Editor(contract.HostPublicKey, nil)
   804  	if err != nil {
   805  		t.Fatal(err)
   806  	}
   807  
   808  	// editors should match
   809  	if d4 == d1 {
   810  		t.Fatal("editor should not have been cached after all clients were closed")
   811  	}
   812  	d4.Close()
   813  }
   814  
   815  // TestContractPresenceLeak tests that a renter can not tell from the response
   816  // of the host to RPCs if the host has the contract if the renter doesn't
   817  // own this contract. See https://gitlab.com/NebulousLabs/Sia/issues/2327.
   818  func TestContractPresenceLeak(t *testing.T) {
   819  	if testing.Short() {
   820  		t.SkipNow()
   821  	}
   822  	t.Parallel()
   823  	// create testing trio
   824  	h, c, _, cf, err := newTestingTrio(t.Name())
   825  	if err != nil {
   826  		t.Fatal(err)
   827  	}
   828  	defer tryClose(cf, t)
   829  
   830  	// get the host's entry from the db
   831  	hostEntry, ok, err := c.staticHDB.Host(h.PublicKey())
   832  	if err != nil {
   833  		t.Fatal(err)
   834  	}
   835  	if !ok {
   836  		t.Fatal("no entry for host in db")
   837  	}
   838  
   839  	// set an allowance but don't use SetAllowance to avoid automatic contract
   840  	// formation.
   841  	c.mu.Lock()
   842  	c.allowance = skymodules.DefaultAllowance
   843  	a := c.allowance
   844  	c.mu.Unlock()
   845  
   846  	// form a contract with the host
   847  	_, contract, err := c.managedNewContract(hostEntry, a, types.SiacoinPrecision.Mul64(10), c.blockHeight+100)
   848  	if err != nil {
   849  		t.Fatal(err)
   850  	}
   851  
   852  	// Connect with bad challenge response. Try correct
   853  	// and incorrect contract IDs. Compare errors.
   854  	wrongID := contract.ID
   855  	wrongID[0] ^= 0x01
   856  	fcids := []types.FileContractID{contract.ID, wrongID}
   857  	var errors []error
   858  
   859  	for _, fcid := range fcids {
   860  		var challenge crypto.Hash
   861  		var signature crypto.Signature
   862  		conn, err := net.Dial("tcp", string(hostEntry.NetAddress))
   863  		if err != nil {
   864  			t.Fatalf("Couldn't dial tpc connection with host @ %v: %v.", string(hostEntry.NetAddress), err)
   865  		}
   866  		if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil {
   867  			t.Fatalf("Couldn't initiate RPC: %v.", err)
   868  		}
   869  		if err := encoding.WriteObject(conn, fcid); err != nil {
   870  			t.Fatalf("Couldn't send fcid: %v.", err)
   871  		}
   872  		if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
   873  			t.Fatalf("Couldn't read challenge: %v.", err)
   874  		}
   875  		if err := encoding.WriteObject(conn, signature); err != nil {
   876  			t.Fatalf("Couldn't send signature: %v.", err)
   877  		}
   878  		err = modules.ReadNegotiationAcceptance(conn)
   879  		if err == nil {
   880  			t.Fatal("Expected an error, got success.")
   881  		}
   882  		errors = append(errors, err)
   883  	}
   884  	if errors[0].Error() != errors[1].Error() {
   885  		t.Fatalf("Expected to get equal errors, got %q and %q.", errors[0], errors[1])
   886  	}
   887  }