github.com/nebulouslabs/sia@v1.3.7/modules/renter/contractor/host_integration_test.go (about)

     1  package contractor
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/NebulousLabs/Sia/build"
    13  	"github.com/NebulousLabs/Sia/crypto"
    14  	"github.com/NebulousLabs/Sia/encoding"
    15  	"github.com/NebulousLabs/Sia/modules"
    16  	"github.com/NebulousLabs/Sia/modules/consensus"
    17  	"github.com/NebulousLabs/Sia/modules/gateway"
    18  	"github.com/NebulousLabs/Sia/modules/host"
    19  	"github.com/NebulousLabs/Sia/modules/miner"
    20  	"github.com/NebulousLabs/Sia/modules/renter/hostdb"
    21  	"github.com/NebulousLabs/Sia/modules/transactionpool"
    22  	modWallet "github.com/NebulousLabs/Sia/modules/wallet"
    23  	"github.com/NebulousLabs/Sia/types"
    24  	"github.com/NebulousLabs/fastrand"
    25  )
    26  
    27  // newTestingWallet is a helper function that creates a ready-to-use wallet
    28  // and mines some coins into it.
    29  func newTestingWallet(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Wallet, error) {
    30  	w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	key := crypto.GenerateTwofishKey()
    35  	encrypted, err := w.Encrypted()
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	if !encrypted {
    40  		_, err = w.Encrypt(key)
    41  		if err != nil {
    42  			return nil, err
    43  		}
    44  	}
    45  	err = w.Unlock(key)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	// give it some money
    50  	m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
    55  		_, err := m.AddBlock()
    56  		if err != nil {
    57  			return nil, err
    58  		}
    59  	}
    60  	return w, nil
    61  }
    62  
    63  // newTestingHost is a helper function that creates a ready-to-use host.
    64  func newTestingHost(testdir string, cs modules.ConsensusSet, tp modules.TransactionPool) (modules.Host, error) {
    65  	w, err := newTestingWallet(testdir, cs, tp)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  	h, err := host.New(cs, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir))
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// configure host to accept contracts
    75  	settings := h.InternalSettings()
    76  	settings.AcceptingContracts = true
    77  	err = h.SetInternalSettings(settings)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	// add storage to host
    83  	storageFolder := filepath.Join(testdir, "storage")
    84  	err = os.MkdirAll(storageFolder, 0700)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	err = h.AddStorageFolder(storageFolder, modules.SectorSize*64)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	return h, nil
    94  }
    95  
    96  // newTestingContractor is a helper function that creates a ready-to-use
    97  // contractor.
    98  func newTestingContractor(testdir string, g modules.Gateway, cs modules.ConsensusSet, tp modules.TransactionPool) (*Contractor, error) {
    99  	w, err := newTestingWallet(testdir, cs, tp)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	hdb, err := hostdb.New(g, cs, filepath.Join(testdir, "hostdb"))
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return New(cs, w, tp, hdb, filepath.Join(testdir, "contractor"))
   108  }
   109  
   110  // newTestingTrio creates a Host, Contractor, and TestMiner that can be used
   111  // for testing host/renter interactions.
   112  func newTestingTrio(name string) (modules.Host, *Contractor, modules.TestMiner, error) {
   113  	testdir := build.TempDir("contractor", name)
   114  
   115  	// create miner
   116  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
   117  	if err != nil {
   118  		return nil, nil, nil, err
   119  	}
   120  	cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir))
   121  	if err != nil {
   122  		return nil, nil, nil, err
   123  	}
   124  	tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir))
   125  	if err != nil {
   126  		return nil, nil, nil, err
   127  	}
   128  	w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
   129  	if err != nil {
   130  		return nil, nil, nil, err
   131  	}
   132  	key := crypto.GenerateTwofishKey()
   133  	encrypted, err := w.Encrypted()
   134  	if err != nil {
   135  		return nil, nil, nil, err
   136  	}
   137  	if !encrypted {
   138  		_, err = w.Encrypt(key)
   139  		if err != nil {
   140  			return nil, nil, nil, err
   141  		}
   142  	}
   143  	err = w.Unlock(key)
   144  	if err != nil {
   145  		return nil, nil, nil, err
   146  	}
   147  	m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
   148  	if err != nil {
   149  		return nil, nil, nil, err
   150  	}
   151  
   152  	// create host and contractor, using same consensus set and gateway
   153  	h, err := newTestingHost(filepath.Join(testdir, "Host"), cs, tp)
   154  	if err != nil {
   155  		return nil, nil, nil, build.ExtendErr("error creating testing host", err)
   156  	}
   157  	c, err := newTestingContractor(filepath.Join(testdir, "Contractor"), g, cs, tp)
   158  	if err != nil {
   159  		return nil, nil, nil, err
   160  	}
   161  
   162  	// announce the host
   163  	err = h.Announce()
   164  	if err != nil {
   165  		return nil, nil, nil, build.ExtendErr("error announcing host", err)
   166  	}
   167  
   168  	// mine a block, processing the announcement
   169  	_, err = m.AddBlock()
   170  	if err != nil {
   171  		return nil, nil, nil, err
   172  	}
   173  
   174  	// wait for hostdb to scan host
   175  	for i := 0; i < 50 && len(c.hdb.ActiveHosts()) == 0; i++ {
   176  		time.Sleep(time.Millisecond * 100)
   177  	}
   178  	if len(c.hdb.ActiveHosts()) == 0 {
   179  		return nil, nil, nil, errors.New("host did not make it into the contractor hostdb in time")
   180  	}
   181  
   182  	return h, c, m, nil
   183  }
   184  
   185  // TestIntegrationFormContract tests that the contractor can form contracts
   186  // with the host module.
   187  func TestIntegrationFormContract(t *testing.T) {
   188  	if testing.Short() {
   189  		t.SkipNow()
   190  	}
   191  	t.Parallel()
   192  	h, c, _, err := newTestingTrio(t.Name())
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  	defer h.Close()
   197  	defer c.Close()
   198  
   199  	// get the host's entry from the db
   200  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   201  	if !ok {
   202  		t.Fatal("no entry for host in db")
   203  	}
   204  
   205  	// form a contract with the host
   206  	_, err = c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  }
   211  
   212  // TestIntegrationReviseContract tests that the contractor can revise a
   213  // contract previously formed with a host.
   214  func TestIntegrationReviseContract(t *testing.T) {
   215  	if testing.Short() {
   216  		t.SkipNow()
   217  	}
   218  	t.Parallel()
   219  	// create testing trio
   220  	h, c, _, err := newTestingTrio(t.Name())
   221  	if err != nil {
   222  		t.Fatal(err)
   223  	}
   224  	defer h.Close()
   225  	defer c.Close()
   226  
   227  	// get the host's entry from the db
   228  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   229  	if !ok {
   230  		t.Fatal("no entry for host in db")
   231  	}
   232  
   233  	// form a contract with the host
   234  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  
   239  	// revise the contract
   240  	editor, err := c.Editor(contract.HostPublicKey, nil)
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	data := fastrand.Bytes(int(modules.SectorSize))
   245  	_, err = editor.Upload(data)
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	err = editor.Close()
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  }
   254  
   255  // TestIntegrationUploadDownload tests that the contractor can upload data to
   256  // a host and download it intact.
   257  func TestIntegrationUploadDownload(t *testing.T) {
   258  	if testing.Short() {
   259  		t.SkipNow()
   260  	}
   261  	t.Parallel()
   262  	// create testing trio
   263  	h, c, _, err := newTestingTrio(t.Name())
   264  	if err != nil {
   265  		t.Fatal(err)
   266  	}
   267  	defer h.Close()
   268  	defer c.Close()
   269  
   270  	// get the host's entry from the db
   271  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   272  	if !ok {
   273  		t.Fatal("no entry for host in db")
   274  	}
   275  
   276  	// form a contract with the host
   277  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	// revise the contract
   283  	editor, err := c.Editor(contract.HostPublicKey, nil)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	data := fastrand.Bytes(int(modules.SectorSize))
   288  	root, err := editor.Upload(data)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  	err = editor.Close()
   293  	if err != nil {
   294  		t.Fatal(err)
   295  	}
   296  
   297  	// download the data
   298  	downloader, err := c.Downloader(contract.HostPublicKey, nil)
   299  	if err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	retrieved, err := downloader.Sector(root)
   303  	if err != nil {
   304  		t.Fatal(err)
   305  	}
   306  	if !bytes.Equal(data, retrieved) {
   307  		t.Fatal("downloaded data does not match original")
   308  	}
   309  	err = downloader.Close()
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  }
   314  
   315  // TestIntegrationRenew tests that the contractor can renew a previously-
   316  // formed file contract.
   317  func TestIntegrationRenew(t *testing.T) {
   318  	if testing.Short() {
   319  		t.SkipNow()
   320  	}
   321  	t.Parallel()
   322  	// create testing trio
   323  	h, c, _, err := newTestingTrio(t.Name())
   324  	if err != nil {
   325  		t.Fatal(err)
   326  	}
   327  	defer h.Close()
   328  	defer c.Close()
   329  
   330  	// get the host's entry from the db
   331  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   332  	if !ok {
   333  		t.Fatal("no entry for host in db")
   334  	}
   335  
   336  	// form a contract with the host
   337  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   338  	if err != nil {
   339  		t.Fatal(err)
   340  	}
   341  
   342  	// revise the contract
   343  	editor, err := c.Editor(contract.HostPublicKey, nil)
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	data := fastrand.Bytes(int(modules.SectorSize))
   348  	// insert the sector
   349  	root, err := editor.Upload(data)
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	err = editor.Close()
   354  	if err != nil {
   355  		t.Fatal(err)
   356  	}
   357  
   358  	// renew the contract
   359  	err = c.managedUpdateContractUtility(contract.ID, modules.ContractUtility{GoodForRenew: true})
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	oldContract, ok := c.staticContracts.Acquire(contract.ID)
   364  	if !ok {
   365  		t.Fatal("failed to acquire contract")
   366  	}
   367  	contract, err = c.managedRenew(oldContract, types.SiacoinPrecision.Mul64(50), c.blockHeight+200)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	c.staticContracts.Return(oldContract)
   372  
   373  	// check renewed contract
   374  	if contract.EndHeight != c.blockHeight+200 {
   375  		t.Fatal(contract.EndHeight)
   376  	}
   377  
   378  	// download the renewed contract
   379  	downloader, err := c.Downloader(contract.HostPublicKey, nil)
   380  	if err != nil {
   381  		t.Fatal(err)
   382  	}
   383  	retrieved, err := downloader.Sector(root)
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  	if !bytes.Equal(data, retrieved) {
   388  		t.Fatal("downloaded data does not match original")
   389  	}
   390  	err = downloader.Close()
   391  	if err != nil {
   392  		t.Fatal(err)
   393  	}
   394  
   395  	// renew to a lower height
   396  	err = c.managedUpdateContractUtility(contract.ID, modules.ContractUtility{GoodForRenew: true})
   397  	if err != nil {
   398  		t.Fatal(err)
   399  	}
   400  	oldContract, _ = c.staticContracts.Acquire(contract.ID)
   401  	contract, err = c.managedRenew(oldContract, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   402  	if err != nil {
   403  		t.Fatal(err)
   404  	}
   405  	c.staticContracts.Return(oldContract)
   406  	if contract.EndHeight != c.blockHeight+100 {
   407  		t.Fatal(contract.EndHeight)
   408  	}
   409  
   410  	// revise the contract
   411  	editor, err = c.Editor(contract.HostPublicKey, nil)
   412  	if err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	data = fastrand.Bytes(int(modules.SectorSize))
   416  	// insert the sector
   417  	_, err = editor.Upload(data)
   418  	if err != nil {
   419  		t.Fatal(err)
   420  	}
   421  	err = editor.Close()
   422  	if err != nil {
   423  		t.Fatal(err)
   424  	}
   425  }
   426  
   427  // TestIntegrationDownloaderCaching tests that downloaders are properly cached
   428  // by the contractor. When two downloaders are requested for the same
   429  // contract, only one underlying downloader should be created.
   430  func TestIntegrationDownloaderCaching(t *testing.T) {
   431  	if testing.Short() {
   432  		t.SkipNow()
   433  	}
   434  	t.Parallel()
   435  	// create testing trio
   436  	h, c, _, err := newTestingTrio(t.Name())
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  	defer h.Close()
   441  	defer c.Close()
   442  
   443  	// get the host's entry from the db
   444  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   445  	if !ok {
   446  		t.Fatal("no entry for host in db")
   447  	}
   448  
   449  	// form a contract with the host
   450  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   451  	if err != nil {
   452  		t.Fatal(err)
   453  	}
   454  
   455  	// create a downloader
   456  	d1, err := c.Downloader(contract.HostPublicKey, nil)
   457  	if err != nil {
   458  		t.Fatal(err)
   459  	}
   460  
   461  	// create another downloader
   462  	d2, err := c.Downloader(contract.HostPublicKey, nil)
   463  	if err != nil {
   464  		t.Fatal(err)
   465  	}
   466  
   467  	// downloaders should match
   468  	if d1 != d2 {
   469  		t.Fatal("downloader was not cached")
   470  	}
   471  
   472  	// close one of the downloaders; it should not fully close, since d1 is
   473  	// still using it
   474  	d2.Close()
   475  
   476  	c.mu.RLock()
   477  	_, ok = c.downloaders[contract.ID]
   478  	c.mu.RUnlock()
   479  	if !ok {
   480  		t.Fatal("expected downloader to still be present")
   481  	}
   482  
   483  	// create another downloader
   484  	d3, err := c.Downloader(contract.HostPublicKey, nil)
   485  	if err != nil {
   486  		t.Fatal(err)
   487  	}
   488  
   489  	// downloaders should match
   490  	if d3 != d1 {
   491  		t.Fatal("closing one client should not fully close the downloader")
   492  	}
   493  
   494  	// close both downloaders
   495  	d1.Close()
   496  	d2.Close()
   497  
   498  	c.mu.RLock()
   499  	_, ok = c.downloaders[contract.ID]
   500  	c.mu.RUnlock()
   501  	if ok {
   502  		t.Fatal("did not expect downloader to still be present")
   503  	}
   504  
   505  	// create another downloader
   506  	d4, err := c.Downloader(contract.HostPublicKey, nil)
   507  	if err != nil {
   508  		t.Fatal(err)
   509  	}
   510  
   511  	// downloaders should match
   512  	if d4 == d1 {
   513  		t.Fatal("downloader should not have been cached after all clients were closed")
   514  	}
   515  	d4.Close()
   516  }
   517  
   518  // TestIntegrationEditorCaching tests that editors are properly cached
   519  // by the contractor. When two editors are requested for the same
   520  // contract, only one underlying editor should be created.
   521  func TestIntegrationEditorCaching(t *testing.T) {
   522  	if testing.Short() {
   523  		t.SkipNow()
   524  	}
   525  	t.Parallel()
   526  	// create testing trio
   527  	h, c, _, err := newTestingTrio(t.Name())
   528  	if err != nil {
   529  		t.Fatal(err)
   530  	}
   531  	defer h.Close()
   532  	defer c.Close()
   533  
   534  	// get the host's entry from the db
   535  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   536  	if !ok {
   537  		t.Fatal("no entry for host in db")
   538  	}
   539  
   540  	// form a contract with the host
   541  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(50), c.blockHeight+100)
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  
   546  	// create an editor
   547  	d1, err := c.Editor(contract.HostPublicKey, nil)
   548  	if err != nil {
   549  		t.Fatal(err)
   550  	}
   551  
   552  	// create another editor
   553  	d2, err := c.Editor(contract.HostPublicKey, nil)
   554  	if err != nil {
   555  		t.Fatal(err)
   556  	}
   557  
   558  	// editors should match
   559  	if d1 != d2 {
   560  		t.Fatal("editor was not cached")
   561  	}
   562  
   563  	// close one of the editors; it should not fully close, since d1 is
   564  	// still using it
   565  	d2.Close()
   566  
   567  	c.mu.RLock()
   568  	_, ok = c.editors[contract.ID]
   569  	c.mu.RUnlock()
   570  	if !ok {
   571  		t.Fatal("expected editor to still be present")
   572  	}
   573  
   574  	// create another editor
   575  	d3, err := c.Editor(contract.HostPublicKey, nil)
   576  	if err != nil {
   577  		t.Fatal(err)
   578  	}
   579  
   580  	// editors should match
   581  	if d3 != d1 {
   582  		t.Fatal("closing one client should not fully close the editor")
   583  	}
   584  
   585  	// close both editors
   586  	d1.Close()
   587  	d2.Close()
   588  
   589  	c.mu.RLock()
   590  	_, ok = c.editors[contract.ID]
   591  	c.mu.RUnlock()
   592  	if ok {
   593  		t.Fatal("did not expect editor to still be present")
   594  	}
   595  
   596  	// create another editor
   597  	d4, err := c.Editor(contract.HostPublicKey, nil)
   598  	if err != nil {
   599  		t.Fatal(err)
   600  	}
   601  
   602  	// editors should match
   603  	if d4 == d1 {
   604  		t.Fatal("editor should not have been cached after all clients were closed")
   605  	}
   606  	d4.Close()
   607  }
   608  
   609  // TestContractPresenceLeak tests that a renter can not tell from the response
   610  // of the host to RPCs if the host has the contract if the renter doesn't
   611  // own this contract. See https://github.com/NebulousLabs/Sia/issues/2327.
   612  func TestContractPresenceLeak(t *testing.T) {
   613  	if testing.Short() {
   614  		t.SkipNow()
   615  	}
   616  	t.Parallel()
   617  	// create testing trio
   618  	h, c, _, err := newTestingTrio(t.Name())
   619  	if err != nil {
   620  		t.Fatal(err)
   621  	}
   622  	defer h.Close()
   623  	defer c.Close()
   624  
   625  	// get the host's entry from the db
   626  	hostEntry, ok := c.hdb.Host(h.PublicKey())
   627  	if !ok {
   628  		t.Fatal("no entry for host in db")
   629  	}
   630  
   631  	// form a contract with the host
   632  	contract, err := c.managedNewContract(hostEntry, types.SiacoinPrecision.Mul64(10), c.blockHeight+100)
   633  	if err != nil {
   634  		t.Fatal(err)
   635  	}
   636  
   637  	// Connect with bad challenge response. Try correct
   638  	// and incorrect contract IDs. Compare errors.
   639  	wrongID := contract.ID
   640  	wrongID[0] ^= 0x01
   641  	fcids := []types.FileContractID{contract.ID, wrongID}
   642  	var errors []error
   643  
   644  	for _, fcid := range fcids {
   645  		var challenge crypto.Hash
   646  		var signature crypto.Signature
   647  		conn, err := net.Dial("tcp", string(hostEntry.NetAddress))
   648  		if err != nil {
   649  			t.Fatalf("Couldn't dial tpc connection with host @ %v: %v.", string(hostEntry.NetAddress), err)
   650  		}
   651  		if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil {
   652  			t.Fatalf("Couldn't initiate RPC: %v.", err)
   653  		}
   654  		if err := encoding.WriteObject(conn, fcid); err != nil {
   655  			t.Fatalf("Couldn't send fcid: %v.", err)
   656  		}
   657  		if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
   658  			t.Fatalf("Couldn't read challenge: %v.", err)
   659  		}
   660  		if err := encoding.WriteObject(conn, signature); err != nil {
   661  			t.Fatalf("Couldn't send signature: %v.", err)
   662  		}
   663  		err = modules.ReadNegotiationAcceptance(conn)
   664  		if err == nil {
   665  			t.Fatal("Expected an error, got success.")
   666  		}
   667  		errors = append(errors, err)
   668  	}
   669  	if errors[0].Error() != errors[1].Error() {
   670  		t.Fatalf("Expected to get equal errors, got %q and %q.", errors[0], errors[1])
   671  	}
   672  }