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