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