gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/host_test.go (about)

     1  package host
     2  
     3  import (
     4  	// "errors"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"gitlab.com/SiaPrime/SiaPrime/build"
    10  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    11  	"gitlab.com/SiaPrime/SiaPrime/modules"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules/consensus"
    13  	"gitlab.com/SiaPrime/SiaPrime/modules/gateway"
    14  	"gitlab.com/SiaPrime/SiaPrime/modules/miner"
    15  	// "gitlab.com/SiaPrime/SiaPrime/modules/renter"
    16  	"gitlab.com/SiaPrime/SiaPrime/modules/transactionpool"
    17  	"gitlab.com/SiaPrime/SiaPrime/modules/wallet"
    18  	siasync "gitlab.com/SiaPrime/SiaPrime/sync"
    19  	"gitlab.com/SiaPrime/SiaPrime/types"
    20  )
    21  
    22  // A hostTester is the helper object for host testing, including helper modules
    23  // and methods for controlling synchronization.
    24  type hostTester struct {
    25  	cs      modules.ConsensusSet
    26  	gateway modules.Gateway
    27  	miner   modules.TestMiner
    28  	// renter    modules.Renter
    29  	renting   bool
    30  	tpool     modules.TransactionPool
    31  	wallet    modules.Wallet
    32  	walletKey crypto.CipherKey
    33  
    34  	host *Host
    35  
    36  	persistDir string
    37  }
    38  
    39  /*
    40  // initRenting prepares the host tester for uploads and downloads by announcing
    41  // the host to the network and performing other preparational tasks.
    42  // initRenting takes a while because the renter needs to process the host
    43  // announcement, requiring asynchronous network communication between the
    44  // renter and host.
    45  func (ht *hostTester) initRenting() error {
    46  	if ht.renting {
    47  		return nil
    48  	}
    49  
    50  	// Because the renting test takes a long time, it will fail if
    51  	// testing.Short.
    52  	if testing.Short() {
    53  		return errors.New("cannot call initRenting in short tests")
    54  	}
    55  
    56  	// Announce the host.
    57  	err := ht.host.Announce()
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// Mine a block to get the announcement into the blockchain.
    63  	_, err = ht.miner.AddBlock()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	// Wait for the renter to see the host announcement.
    69  	for i := 0; i < 50; i++ {
    70  		time.Sleep(time.Millisecond * 100)
    71  		if len(ht.renter.ActiveHosts()) != 0 {
    72  			break
    73  		}
    74  	}
    75  	if len(ht.renter.ActiveHosts()) == 0 {
    76  		return errors.New("could not start renting in the host tester")
    77  	}
    78  	ht.renting = true
    79  	return nil
    80  }
    81  */
    82  
    83  // initWallet creates a wallet key, initializes the host wallet, unlocks it,
    84  // and then stores the key in the host tester.
    85  func (ht *hostTester) initWallet() error {
    86  	// Create the keys for the wallet and unlock it.
    87  	key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet)
    88  	ht.walletKey = key
    89  	_, err := ht.wallet.Encrypt(key)
    90  	if err != nil {
    91  		return err
    92  	}
    93  	err = ht.wallet.Unlock(key)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	return nil
    98  }
    99  
   100  // blankHostTester creates a host tester where the modules are created but no
   101  // extra initialization has been done, for example no blocks have been mined
   102  // and the wallet keys have not been created.
   103  func blankHostTester(name string) (*hostTester, error) {
   104  	return blankMockHostTester(modules.ProdDependencies, name)
   105  }
   106  
   107  // blankMockHostTester creates a host tester where the modules are created but no
   108  // extra initialization has been done, for example no blocks have been mined
   109  // and the wallet keys have not been created.
   110  func blankMockHostTester(d modules.Dependencies, name string) (*hostTester, error) {
   111  	testdir := build.TempDir(modules.HostDir, name)
   112  
   113  	// Create the modules.
   114  	g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir))
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir))
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir))
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir))
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	h, err := newHost(d, cs, g, tp, w, "localhost:0", filepath.Join(testdir, modules.HostDir))
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	/*
   139  		r, err := renter.New(cs, w, tp, filepath.Join(testdir, modules.RenterDir))
   140  		if err != nil {
   141  			return nil, err
   142  		}
   143  	*/
   144  
   145  	// Assemble all objects into a hostTester
   146  	ht := &hostTester{
   147  		cs:      cs,
   148  		gateway: g,
   149  		miner:   m,
   150  		// renter:  r,
   151  		tpool:  tp,
   152  		wallet: w,
   153  
   154  		host: h,
   155  
   156  		persistDir: testdir,
   157  	}
   158  
   159  	return ht, nil
   160  }
   161  
   162  // newHostTester creates a host tester with an initialized wallet and money in
   163  // that wallet.
   164  func newHostTester(name string) (*hostTester, error) {
   165  	return newMockHostTester(modules.ProdDependencies, name)
   166  }
   167  
   168  // newMockHostTester creates a host tester with an initialized wallet and money
   169  // in that wallet, using the dependencies provided.
   170  func newMockHostTester(d modules.Dependencies, name string) (*hostTester, error) {
   171  	// Create a blank host tester.
   172  	ht, err := blankMockHostTester(d, name)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	// Initialize the wallet and mine blocks until the wallet has money.
   178  	err = ht.initWallet()
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ {
   183  		_, err = ht.miner.AddBlock()
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  	}
   188  
   189  	// Create two storage folder for the host, one the minimum size and one
   190  	// twice the minimum size.
   191  	storageFolderOne := filepath.Join(ht.persistDir, "hostTesterStorageFolderOne")
   192  	err = os.Mkdir(storageFolderOne, 0700)
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	err = ht.host.AddStorageFolder(storageFolderOne, modules.SectorSize*64)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	storageFolderTwo := filepath.Join(ht.persistDir, "hostTesterStorageFolderTwo")
   201  	err = os.Mkdir(storageFolderTwo, 0700)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	err = ht.host.AddStorageFolder(storageFolderTwo, modules.SectorSize*64*2)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	return ht, nil
   210  }
   211  
   212  // Close safely closes the hostTester. It panics if err != nil because there
   213  // isn't a good way to errcheck when deferring a close.
   214  func (ht *hostTester) Close() error {
   215  	errs := []error{
   216  		ht.host.Close(),
   217  		ht.miner.Close(),
   218  		ht.tpool.Close(),
   219  		ht.cs.Close(),
   220  		ht.gateway.Close(),
   221  	}
   222  	if err := build.JoinErrors(errs, "; "); err != nil {
   223  		panic(err)
   224  	}
   225  	return nil
   226  }
   227  
   228  // TestHostInitialization checks that the host initializes to sensible default
   229  // values.
   230  func TestHostInitialization(t *testing.T) {
   231  	if testing.Short() {
   232  		t.SkipNow()
   233  	}
   234  	t.Parallel()
   235  	// Create a blank host tester and check that the height is zero.
   236  	bht, err := blankHostTester("TestHostInitialization")
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  	defer bht.Close()
   241  	if bht.host.blockHeight != 0 {
   242  		t.Error("host initialized to the wrong block height")
   243  	}
   244  
   245  	// Initialize the wallet so that a block can be mined, then mine a block
   246  	// and check that it sets the host height to 1.
   247  	err = bht.initWallet()
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	_, err = bht.miner.AddBlock()
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	if bht.host.blockHeight != 1 {
   256  		t.Fatal("block height did not increase correctly after first block mined:", bht.host.blockHeight, 1)
   257  	}
   258  }
   259  
   260  // TestHostMultiClose checks that the host returns an error if Close is called
   261  // multiple times on the host.
   262  func TestHostMultiClose(t *testing.T) {
   263  	if testing.Short() {
   264  		t.SkipNow()
   265  	}
   266  	t.Parallel()
   267  	ht, err := newHostTester("TestHostMultiClose")
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	defer ht.Close()
   272  
   273  	err = ht.host.Close()
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	err = ht.host.Close()
   278  	if err != siasync.ErrStopped {
   279  		t.Fatal(err)
   280  	}
   281  	err = ht.host.Close()
   282  	if err != siasync.ErrStopped {
   283  		t.Fatal(err)
   284  	}
   285  	// Set ht.host to something non-nil - nil was returned because startup was
   286  	// incomplete. If ht.host is nil at the end of the function, the ht.Close()
   287  	// operation will fail.
   288  	ht.host, err = newHost(modules.ProdDependencies, ht.cs, ht.gateway, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  }
   293  
   294  // TestNilValues tries initializing the host with nil values.
   295  func TestNilValues(t *testing.T) {
   296  	if testing.Short() {
   297  		t.SkipNow()
   298  	}
   299  	t.Parallel()
   300  	ht, err := blankHostTester("TestStartupRescan")
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	defer ht.Close()
   305  
   306  	hostDir := filepath.Join(ht.persistDir, modules.HostDir)
   307  	_, err = New(nil, ht.gateway, ht.tpool, ht.wallet, "localhost:0", hostDir)
   308  	if err != errNilCS {
   309  		t.Fatal("could not trigger errNilCS")
   310  	}
   311  	_, err = New(ht.cs, nil, ht.tpool, ht.wallet, "localhost:0", hostDir)
   312  	if err != errNilGateway {
   313  		t.Fatal("Could not trigger errNilGateay")
   314  	}
   315  	_, err = New(ht.cs, ht.gateway, nil, ht.wallet, "localhost:0", hostDir)
   316  	if err != errNilTpool {
   317  		t.Fatal("could not trigger errNilTpool")
   318  	}
   319  	_, err = New(ht.cs, ht.gateway, ht.tpool, nil, "localhost:0", hostDir)
   320  	if err != errNilWallet {
   321  		t.Fatal("Could not trigger errNilWallet")
   322  	}
   323  }
   324  
   325  // TestSetAndGetInternalSettings checks that the functions for interacting with
   326  // the host's internal settings object are working as expected.
   327  func TestSetAndGetInternalSettings(t *testing.T) {
   328  	if testing.Short() {
   329  		t.SkipNow()
   330  	}
   331  	t.Parallel()
   332  
   333  	ht, err := newHostTester("TestSetAndGetInternalSettings")
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	defer ht.Close()
   338  
   339  	// Check the default settings get returned at first call.
   340  	settings := ht.host.InternalSettings()
   341  	if settings.AcceptingContracts != false {
   342  		t.Error("settings retrieval did not return default value")
   343  	}
   344  	if settings.MaxDuration != defaultMaxDuration {
   345  		t.Error("settings retrieval did not return default value")
   346  	}
   347  	if settings.MaxDownloadBatchSize != uint64(defaultMaxDownloadBatchSize) {
   348  		t.Error("settings retrieval did not return default value")
   349  	}
   350  	if settings.MaxReviseBatchSize != uint64(defaultMaxReviseBatchSize) {
   351  		t.Error("settings retrieval did not return default value")
   352  	}
   353  	if settings.NetAddress != "" {
   354  		t.Error("settings retrieval did not return default value")
   355  	}
   356  	if settings.WindowSize != defaultWindowSize {
   357  		t.Error("settings retrieval did not return default value")
   358  	}
   359  	if !settings.Collateral.Equals(defaultCollateral) {
   360  		t.Error("settings retrieval did not return default value")
   361  	}
   362  	if !settings.CollateralBudget.Equals(defaultCollateralBudget) {
   363  		t.Error("settings retrieval did not return default value")
   364  	}
   365  	if !settings.MaxCollateral.Equals(defaultMaxCollateral) {
   366  		t.Error("settings retrieval did not return default value")
   367  	}
   368  	if !settings.MinContractPrice.Equals(defaultContractPrice) {
   369  		t.Error("settings retrieval did not return default value")
   370  	}
   371  	if !settings.MinDownloadBandwidthPrice.Equals(defaultDownloadBandwidthPrice) {
   372  		t.Error("settings retrieval did not return default value")
   373  	}
   374  	if !settings.MinStoragePrice.Equals(defaultStoragePrice) {
   375  		t.Error("settings retrieval did not return default value")
   376  	}
   377  	if !settings.MinUploadBandwidthPrice.Equals(defaultUploadBandwidthPrice) {
   378  		t.Error("settings retrieval did not return default value")
   379  	}
   380  
   381  	// Check that calling SetInternalSettings with valid settings updates the settings.
   382  	settings.AcceptingContracts = true
   383  	settings.NetAddress = "foo.com:123"
   384  	err = ht.host.SetInternalSettings(settings)
   385  	if err != nil {
   386  		t.Fatal(err)
   387  	}
   388  	settings = ht.host.InternalSettings()
   389  	if settings.AcceptingContracts != true {
   390  		t.Fatal("SetInternalSettings failed to update settings")
   391  	}
   392  	if settings.NetAddress != "foo.com:123" {
   393  		t.Fatal("SetInternalSettings failed to update settings")
   394  	}
   395  
   396  	// Check that calling SetInternalSettings with invalid settings does not update the settings.
   397  	settings.NetAddress = "invalid"
   398  	err = ht.host.SetInternalSettings(settings)
   399  	if err == nil {
   400  		t.Fatal("expected SetInternalSettings to error with invalid settings")
   401  	}
   402  	settings = ht.host.InternalSettings()
   403  	if settings.NetAddress != "foo.com:123" {
   404  		t.Fatal("SetInternalSettings should not modify the settings if the new settings are invalid")
   405  	}
   406  
   407  	// Reload the host and verify that the altered settings persisted.
   408  	err = ht.host.Close()
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	rebootHost, err := New(ht.cs, ht.gateway, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  	rebootSettings := rebootHost.InternalSettings()
   417  	if rebootSettings.AcceptingContracts != settings.AcceptingContracts {
   418  		t.Error("settings retrieval did not return updated value")
   419  	}
   420  	if rebootSettings.NetAddress != settings.NetAddress {
   421  		t.Error("settings retrieval did not return updated value")
   422  	}
   423  
   424  	// Set ht.host to 'rebootHost' so that the 'ht.Close()' method will close
   425  	// everything cleanly.
   426  	ht.host = rebootHost
   427  }
   428  
   429  /*
   430  // TestSetAndGetSettings checks that the functions for interacting with the
   431  // hosts settings object are working as expected.
   432  func TestSetAndGetSettings(t *testing.T) {
   433  	if testing.Short() {
   434  		t.SkipNow()
   435  	}
   436  	ht, err := newHostTester("TestSetAndGetSettings")
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  	defer ht.Close()
   441  
   442  	// Check the default settings get returned at first call.
   443  	settings := ht.host.Settings()
   444  	if settings.MaxDuration != defaultMaxDuration {
   445  		t.Error("settings retrieval did not return default value")
   446  	}
   447  	if settings.WindowSize != defaultWindowSize {
   448  		t.Error("settings retrieval did not return default value")
   449  	}
   450  	if settings.Price.Cmp(defaultPrice) != 0 {
   451  		t.Error("settings retrieval did not return default value")
   452  	}
   453  	if settings.Collateral.Cmp(defaultCollateral) != 0 {
   454  		t.Error("settings retrieval did not return default value")
   455  	}
   456  
   457  	// Submit updated settings and check that the changes stuck.
   458  	settings.TotalStorage += 15
   459  	settings.MaxDuration += 16
   460  	settings.WindowSize += 17
   461  	settings.Price = settings.Price.Add(types.NewCurrency64(18))
   462  	settings.Collateral = settings.Collateral.Add(types.NewCurrency64(19))
   463  	err = ht.host.SetSettings(settings)
   464  	if err != nil {
   465  		t.Fatal(err)
   466  	}
   467  	newSettings := ht.host.Settings()
   468  	if settings.MaxDuration != newSettings.MaxDuration {
   469  		t.Error("settings retrieval did not return updated value")
   470  	}
   471  	if settings.WindowSize != newSettings.WindowSize {
   472  		t.Error("settings retrieval did not return updated value")
   473  	}
   474  	if settings.Price.Cmp(newSettings.Price) != 0 {
   475  		t.Error("settings retrieval did not return updated value")
   476  	}
   477  	if settings.Collateral.Cmp(newSettings.Collateral) != 0 {
   478  		t.Error("settings retrieval did not return updated value")
   479  	}
   480  
   481  	// Reload the host and verify that the altered settings persisted.
   482  	err = ht.host.Close()
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	rebootHost, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   487  	if err != nil {
   488  		t.Fatal(err)
   489  	}
   490  	rebootSettings := rebootHost.Settings()
   491  	if settings.TotalStorage != rebootSettings.TotalStorage {
   492  		t.Error("settings retrieval did not return updated value")
   493  	}
   494  	if settings.MaxDuration != rebootSettings.MaxDuration {
   495  		t.Error("settings retrieval did not return updated value")
   496  	}
   497  	if settings.WindowSize != rebootSettings.WindowSize {
   498  		t.Error("settings retrieval did not return updated value")
   499  	}
   500  	if settings.Price.Cmp(rebootSettings.Price) != 0 {
   501  		t.Error("settings retrieval did not return updated value")
   502  	}
   503  	if settings.Collateral.Cmp(rebootSettings.Collateral) != 0 {
   504  		t.Error("settings retrieval did not return updated value")
   505  	}
   506  }
   507  
   508  // TestPersistentSettings checks that settings persist between instances of the
   509  // host.
   510  func TestPersistentSettings(t *testing.T) {
   511  	if testing.Short() {
   512  		t.SkipNow()
   513  	}
   514  	ht, err := newHostTester("TestSetPersistentSettings")
   515  	if err != nil {
   516  		t.Fatal(err)
   517  	}
   518  	defer ht.Close()
   519  
   520  	// Submit updated settings.
   521  	settings := ht.host.Settings()
   522  	settings.TotalStorage += 25
   523  	settings.MaxDuration += 36
   524  	settings.WindowSize += 47
   525  	settings.Price = settings.Price.Add(types.NewCurrency64(38))
   526  	settings.Collateral = settings.Collateral.Add(types.NewCurrency64(99))
   527  	err = ht.host.SetSettings(settings)
   528  	if err != nil {
   529  		t.Fatal(err)
   530  	}
   531  
   532  	// Reboot the host and verify that the new settings stuck.
   533  	err = ht.host.Close() // host saves upon closing
   534  	if err != nil {
   535  		t.Fatal(err)
   536  	}
   537  	h, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   538  	if err != nil {
   539  		t.Fatal(err)
   540  	}
   541  	newSettings := h.Settings()
   542  	if settings.TotalStorage != newSettings.TotalStorage {
   543  		t.Error("settings retrieval did not return updated value:", settings.TotalStorage, "vs", newSettings.TotalStorage)
   544  	}
   545  	if settings.MaxDuration != newSettings.MaxDuration {
   546  		t.Error("settings retrieval did not return updated value")
   547  	}
   548  	if settings.WindowSize != newSettings.WindowSize {
   549  		t.Error("settings retrieval did not return updated value")
   550  	}
   551  	if settings.Price.Cmp(newSettings.Price) != 0 {
   552  		t.Error("settings retrieval did not return updated value")
   553  	}
   554  	if settings.Collateral.Cmp(newSettings.Collateral) != 0 {
   555  		t.Error("settings retrieval did not return updated value")
   556  	}
   557  }
   558  */