gitlab.com/jokerrs1/Sia@v1.3.2/modules/host/host_test.go (about)

     1  package host
     2  
     3  import (
     4  	// "errors"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/NebulousLabs/Sia/build"
    10  	"github.com/NebulousLabs/Sia/crypto"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/Sia/modules/consensus"
    13  	"github.com/NebulousLabs/Sia/modules/gateway"
    14  	"github.com/NebulousLabs/Sia/modules/miner"
    15  	// "github.com/NebulousLabs/Sia/modules/renter"
    16  	"github.com/NebulousLabs/Sia/modules/transactionpool"
    17  	"github.com/NebulousLabs/Sia/modules/wallet"
    18  	siasync "github.com/NebulousLabs/Sia/sync"
    19  	"github.com/NebulousLabs/Sia/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.TwofishKey
    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.GenerateTwofishKey()
    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.ProductionDependencies{}, 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, 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.ProductionDependencies{}, 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.ProductionDependencies{}, ht.cs, 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.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.wallet, "localhost:0", hostDir)
   312  	if err != errNilTpool {
   313  		t.Fatal("could not trigger errNilTpool")
   314  	}
   315  	_, err = New(ht.cs, ht.tpool, nil, "localhost:0", hostDir)
   316  	if err != errNilWallet {
   317  		t.Fatal("Could not trigger errNilWallet")
   318  	}
   319  }
   320  
   321  // TestSetAndGetInternalSettings checks that the functions for interacting with
   322  // the host's internal settings object are working as expected.
   323  func TestSetAndGetInternalSettings(t *testing.T) {
   324  	if testing.Short() {
   325  		t.SkipNow()
   326  	}
   327  	t.Parallel()
   328  
   329  	ht, err := newHostTester("TestSetAndGetInternalSettings")
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  	defer ht.Close()
   334  
   335  	// Check the default settings get returned at first call.
   336  	settings := ht.host.InternalSettings()
   337  	if settings.AcceptingContracts != false {
   338  		t.Error("settings retrieval did not return default value")
   339  	}
   340  	if settings.MaxDuration != defaultMaxDuration {
   341  		t.Error("settings retrieval did not return default value")
   342  	}
   343  	if settings.MaxDownloadBatchSize != uint64(defaultMaxDownloadBatchSize) {
   344  		t.Error("settings retrieval did not return default value")
   345  	}
   346  	if settings.MaxReviseBatchSize != uint64(defaultMaxReviseBatchSize) {
   347  		t.Error("settings retrieval did not return default value")
   348  	}
   349  	if settings.NetAddress != "" {
   350  		t.Error("settings retrieval did not return default value")
   351  	}
   352  	if settings.WindowSize != defaultWindowSize {
   353  		t.Error("settings retrieval did not return default value")
   354  	}
   355  	if !settings.Collateral.Equals(defaultCollateral) {
   356  		t.Error("settings retrieval did not return default value")
   357  	}
   358  	if !settings.CollateralBudget.Equals(defaultCollateralBudget) {
   359  		t.Error("settings retrieval did not return default value")
   360  	}
   361  	if !settings.MaxCollateral.Equals(defaultMaxCollateral) {
   362  		t.Error("settings retrieval did not return default value")
   363  	}
   364  	if !settings.MinContractPrice.Equals(defaultContractPrice) {
   365  		t.Error("settings retrieval did not return default value")
   366  	}
   367  	if !settings.MinDownloadBandwidthPrice.Equals(defaultDownloadBandwidthPrice) {
   368  		t.Error("settings retrieval did not return default value")
   369  	}
   370  	if !settings.MinStoragePrice.Equals(defaultStoragePrice) {
   371  		t.Error("settings retrieval did not return default value")
   372  	}
   373  	if !settings.MinUploadBandwidthPrice.Equals(defaultUploadBandwidthPrice) {
   374  		t.Error("settings retrieval did not return default value")
   375  	}
   376  
   377  	// Check that calling SetInternalSettings with valid settings updates the settings.
   378  	settings.AcceptingContracts = true
   379  	settings.NetAddress = "foo.com:123"
   380  	err = ht.host.SetInternalSettings(settings)
   381  	if err != nil {
   382  		t.Fatal(err)
   383  	}
   384  	settings = ht.host.InternalSettings()
   385  	if settings.AcceptingContracts != true {
   386  		t.Fatal("SetInternalSettings failed to update settings")
   387  	}
   388  	if settings.NetAddress != "foo.com:123" {
   389  		t.Fatal("SetInternalSettings failed to update settings")
   390  	}
   391  
   392  	// Check that calling SetInternalSettings with invalid settings does not update the settings.
   393  	settings.NetAddress = "invalid"
   394  	err = ht.host.SetInternalSettings(settings)
   395  	if err == nil {
   396  		t.Fatal("expected SetInternalSettings to error with invalid settings")
   397  	}
   398  	settings = ht.host.InternalSettings()
   399  	if settings.NetAddress != "foo.com:123" {
   400  		t.Fatal("SetInternalSettings should not modify the settings if the new settings are invalid")
   401  	}
   402  
   403  	// Reload the host and verify that the altered settings persisted.
   404  	err = ht.host.Close()
   405  	if err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	rebootHost, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	rebootSettings := rebootHost.InternalSettings()
   413  	if rebootSettings.AcceptingContracts != settings.AcceptingContracts {
   414  		t.Error("settings retrieval did not return updated value")
   415  	}
   416  	if rebootSettings.NetAddress != settings.NetAddress {
   417  		t.Error("settings retrieval did not return updated value")
   418  	}
   419  
   420  	// Set ht.host to 'rebootHost' so that the 'ht.Close()' method will close
   421  	// everything cleanly.
   422  	ht.host = rebootHost
   423  }
   424  
   425  /*
   426  // TestSetAndGetSettings checks that the functions for interacting with the
   427  // hosts settings object are working as expected.
   428  func TestSetAndGetSettings(t *testing.T) {
   429  	if testing.Short() {
   430  		t.SkipNow()
   431  	}
   432  	ht, err := newHostTester("TestSetAndGetSettings")
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  	defer ht.Close()
   437  
   438  	// Check the default settings get returned at first call.
   439  	settings := ht.host.Settings()
   440  	if settings.MaxDuration != defaultMaxDuration {
   441  		t.Error("settings retrieval did not return default value")
   442  	}
   443  	if settings.WindowSize != defaultWindowSize {
   444  		t.Error("settings retrieval did not return default value")
   445  	}
   446  	if settings.Price.Cmp(defaultPrice) != 0 {
   447  		t.Error("settings retrieval did not return default value")
   448  	}
   449  	if settings.Collateral.Cmp(defaultCollateral) != 0 {
   450  		t.Error("settings retrieval did not return default value")
   451  	}
   452  
   453  	// Submit updated settings and check that the changes stuck.
   454  	settings.TotalStorage += 15
   455  	settings.MaxDuration += 16
   456  	settings.WindowSize += 17
   457  	settings.Price = settings.Price.Add(types.NewCurrency64(18))
   458  	settings.Collateral = settings.Collateral.Add(types.NewCurrency64(19))
   459  	err = ht.host.SetSettings(settings)
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  	newSettings := ht.host.Settings()
   464  	if settings.MaxDuration != newSettings.MaxDuration {
   465  		t.Error("settings retrieval did not return updated value")
   466  	}
   467  	if settings.WindowSize != newSettings.WindowSize {
   468  		t.Error("settings retrieval did not return updated value")
   469  	}
   470  	if settings.Price.Cmp(newSettings.Price) != 0 {
   471  		t.Error("settings retrieval did not return updated value")
   472  	}
   473  	if settings.Collateral.Cmp(newSettings.Collateral) != 0 {
   474  		t.Error("settings retrieval did not return updated value")
   475  	}
   476  
   477  	// Reload the host and verify that the altered settings persisted.
   478  	err = ht.host.Close()
   479  	if err != nil {
   480  		t.Fatal(err)
   481  	}
   482  	rebootHost, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   483  	if err != nil {
   484  		t.Fatal(err)
   485  	}
   486  	rebootSettings := rebootHost.Settings()
   487  	if settings.TotalStorage != rebootSettings.TotalStorage {
   488  		t.Error("settings retrieval did not return updated value")
   489  	}
   490  	if settings.MaxDuration != rebootSettings.MaxDuration {
   491  		t.Error("settings retrieval did not return updated value")
   492  	}
   493  	if settings.WindowSize != rebootSettings.WindowSize {
   494  		t.Error("settings retrieval did not return updated value")
   495  	}
   496  	if settings.Price.Cmp(rebootSettings.Price) != 0 {
   497  		t.Error("settings retrieval did not return updated value")
   498  	}
   499  	if settings.Collateral.Cmp(rebootSettings.Collateral) != 0 {
   500  		t.Error("settings retrieval did not return updated value")
   501  	}
   502  }
   503  
   504  // TestPersistentSettings checks that settings persist between instances of the
   505  // host.
   506  func TestPersistentSettings(t *testing.T) {
   507  	if testing.Short() {
   508  		t.SkipNow()
   509  	}
   510  	ht, err := newHostTester("TestSetPersistentSettings")
   511  	if err != nil {
   512  		t.Fatal(err)
   513  	}
   514  	defer ht.Close()
   515  
   516  	// Submit updated settings.
   517  	settings := ht.host.Settings()
   518  	settings.TotalStorage += 25
   519  	settings.MaxDuration += 36
   520  	settings.WindowSize += 47
   521  	settings.Price = settings.Price.Add(types.NewCurrency64(38))
   522  	settings.Collateral = settings.Collateral.Add(types.NewCurrency64(99))
   523  	err = ht.host.SetSettings(settings)
   524  	if err != nil {
   525  		t.Fatal(err)
   526  	}
   527  
   528  	// Reboot the host and verify that the new settings stuck.
   529  	err = ht.host.Close() // host saves upon closing
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  	h, err := New(ht.cs, ht.tpool, ht.wallet, "localhost:0", filepath.Join(ht.persistDir, modules.HostDir))
   534  	if err != nil {
   535  		t.Fatal(err)
   536  	}
   537  	newSettings := h.Settings()
   538  	if settings.TotalStorage != newSettings.TotalStorage {
   539  		t.Error("settings retrieval did not return updated value:", settings.TotalStorage, "vs", newSettings.TotalStorage)
   540  	}
   541  	if settings.MaxDuration != newSettings.MaxDuration {
   542  		t.Error("settings retrieval did not return updated value")
   543  	}
   544  	if settings.WindowSize != newSettings.WindowSize {
   545  		t.Error("settings retrieval did not return updated value")
   546  	}
   547  	if settings.Price.Cmp(newSettings.Price) != 0 {
   548  		t.Error("settings retrieval did not return updated value")
   549  	}
   550  	if settings.Collateral.Cmp(newSettings.Collateral) != 0 {
   551  		t.Error("settings retrieval did not return updated value")
   552  	}
   553  }
   554  */