github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/host/host.go (about)

     1  // Package host is an implementation of the host module, and is responsible for
     2  // participating in the storage ecosystem, turning available disk space an
     3  // internet bandwidth into profit for the user.
     4  package host
     5  
     6  // TODO: Test the safety of the builder, it should be okay to have multiple
     7  // builders open for up to 600 seconds, which means multiple blocks could be
     8  // received in that time period. Should also check what happens if a prent gets
     9  // confirmed on the blockchain before the builder is finished.
    10  
    11  // TODO: Would be nice to have some sort of error transport to the user, so
    12  // that the user is notified in ways other than logs via the host that there
    13  // are issues such as disk, etc.
    14  
    15  // TODO: automated_settings.go, a file which can be responsible for
    16  // automatically regulating things like bandwidth price, storage price,
    17  // contract price, etc. One of the features in consideration is that the host
    18  // would start to steeply increase the contract price as it begins to run low
    19  // on collateral. The host would also inform the user that there doesn't seem
    20  // to be enough money to handle all of the file contracts, so that the user
    21  // could make a judgement call on whether to get more.
    22  
    23  // TODO: The host needs to somehow keep an awareness of its bandwidth limits,
    24  // and needs to reject calls if there is not enough bandwidth available.
    25  
    26  // TODO: The synchronization on the port forwarding is not perfect. Sometimes a
    27  // port will be cleared before it was set (if things happen fast enough),
    28  // because the port forwarding call is asynchronous.
    29  
    30  // TODO: Add contract compensation from form contract to the storage obligation
    31  // financial metrics, and to the host's tracking.
    32  
    33  // TODO: merge the network interfaces stuff, don't forget to include the
    34  // 'announced' variable as one of the outputs.
    35  
    36  // TODO: check that the host is doing proper clean shudown, especially
    37  // network.go, a couple of problems with clean shutdown in network.go.
    38  
    39  // TODO: 'announced' doesn't tell you if the announcement made it to the
    40  // blockchain.
    41  
    42  // TODO: Need to make sure that the revision exchange for the renter and the
    43  // host is being handled correctly. For the host, it's not so difficult. The
    44  // host need only send the most recent revision every time. But, the host
    45  // should not sign a revision unless the renter has explicitly signed such that
    46  // the 'WholeTransaction' fields cover only the revision and that the
    47  // signatures for the revision don't depend on anything else. The renter needs
    48  // to verify the same when checking on a file contract revision from the host.
    49  // If the host has submitted a file contract revision where the signatures have
    50  // signed the whole file contract, there is an issue.
    51  
    52  // TODO: there is a mistake in the file contract revision rpc, the host, if it
    53  // does not have the right file contract id, should be returning an error there
    54  // to the renter (and not just to it's calling function without informing the
    55  // renter what's up).
    56  
    57  // TODO: Need to make sure that the correct height is being used when adding
    58  // sectors to the storage manager - in some places right now WindowStart is
    59  // being used but really it's WindowEnd that should be in use.
    60  
    61  // TODO: clean up all of the magic numbers in the host.
    62  
    63  // TODO: revamp the finances for the storage obligations.
    64  
    65  // TODO: host_test.go has commented out tests.
    66  
    67  // TODO: network_test.go has commented out tests.
    68  
    69  // TODO: persist_test.go has commented out tests.
    70  
    71  // TODO: update_test.go has commented out tests.
    72  
    73  import (
    74  	"errors"
    75  	"net"
    76  	"path/filepath"
    77  	"sync"
    78  
    79  	"github.com/NebulousLabs/Sia/crypto"
    80  	"github.com/NebulousLabs/Sia/modules"
    81  	"github.com/NebulousLabs/Sia/modules/host/storagemanager"
    82  	"github.com/NebulousLabs/Sia/persist"
    83  	"github.com/NebulousLabs/Sia/types"
    84  )
    85  
    86  const (
    87  	// Names of the various persistent files in the host.
    88  	dbFilename   = modules.HostDir + ".db"
    89  	logFile      = modules.HostDir + ".log"
    90  	settingsFile = modules.HostDir + ".json"
    91  )
    92  
    93  var (
    94  	// dbMetadata is a header that gets put into the database to identify a
    95  	// version and indicate that the database holds host information.
    96  	dbMetadata = persist.Metadata{
    97  		Header:  "Sia Host DB",
    98  		Version: "0.5.2",
    99  	}
   100  
   101  	// persistMetadata is the header that gets written to the persist file, and is
   102  	// used to recognize other persist files.
   103  	persistMetadata = persist.Metadata{
   104  		Header:  "Sia Host",
   105  		Version: "0.5",
   106  	}
   107  
   108  	// errHostClosed gets returned when a call is rejected due to the host
   109  	// having been closed.
   110  	errHostClosed = errors.New("call is disabled because the host is closed")
   111  
   112  	// Nil dependency errors.
   113  	errNilCS     = errors.New("host cannot use a nil state")
   114  	errNilTpool  = errors.New("host cannot use a nil transaction pool")
   115  	errNilWallet = errors.New("host cannot use a nil wallet")
   116  )
   117  
   118  // A Host contains all the fields necessary for storing files for clients and
   119  // performing the storage proofs on the received files.
   120  type Host struct {
   121  	// RPC Metrics - atomic variables need to be placed at the top to preserve
   122  	// compatibility with 32bit systems.
   123  	atomicDownloadCalls       uint64
   124  	atomicErroredCalls        uint64
   125  	atomicFormContractCalls   uint64
   126  	atomicRenewCalls          uint64
   127  	atomicReviseCalls         uint64
   128  	atomicRecentRevisionCalls uint64
   129  	atomicSettingsCalls       uint64
   130  	atomicUnrecognizedCalls   uint64
   131  
   132  	// Dependencies.
   133  	cs     modules.ConsensusSet
   134  	tpool  modules.TransactionPool
   135  	wallet modules.Wallet
   136  	dependencies
   137  	modules.StorageManager
   138  
   139  	// Consensus Tracking.
   140  	blockHeight  types.BlockHeight
   141  	recentChange modules.ConsensusChangeID
   142  
   143  	// Host Identity
   144  	//
   145  	// The revision number keeps track of the current revision number on the
   146  	// host external settingse
   147  	//
   148  	// The auto address is the address that is fetched automatically by the
   149  	// host. The host will ignore the automatic address if settings.NetAddress
   150  	// has been set by the user. If settings.NetAddress is blank, then the host
   151  	// will track its own ip address and make an announcement on the blockchain
   152  	// every time that the address changes.
   153  	//
   154  	// The announced bool indicates whether the host remembers having a
   155  	// successful announcement with the current address.
   156  	announced        bool
   157  	autoAddress      modules.NetAddress
   158  	financialMetrics modules.HostFinancialMetrics
   159  	publicKey        types.SiaPublicKey
   160  	revisionNumber   uint64
   161  	secretKey        crypto.SecretKey
   162  	settings         modules.HostInternalSettings
   163  	unlockHash       types.UnlockHash // A wallet address that can receive coins.
   164  
   165  	// Storage Obligation Management - different from file management in that
   166  	// the storage obligation management is the new way of handling storage
   167  	// obligations. Is a replacement for the contract obligation logic, but the
   168  	// old logic is being kept for compatibility purposes.
   169  	//
   170  	// Storage is broken up into sectors. The sectors are distributed across a
   171  	// set of storage folders using a strategy that tries to create even
   172  	// distributions, but not aggressively. Uneven distributions could be
   173  	// manufactured by an attacker given sufficient knowledge about the disk
   174  	// layout (knowledge which should be unavailable), but a limited amount of
   175  	// damage can be done even with this attack.
   176  	lockedStorageObligations map[types.FileContractID]struct{} // Which storage obligations are currently being modified.
   177  
   178  	// Utilities.
   179  	db         *persist.BoltDatabase
   180  	listener   net.Listener
   181  	log        *persist.Logger
   182  	mu         sync.RWMutex
   183  	persistDir string
   184  	port       string
   185  
   186  	// The resource lock is held by threaded functions for the duration of
   187  	// their operation. Functions should grab the resource lock as a read lock
   188  	// unless they are planning on manipulating the 'closed' variable.
   189  	// Readlocks are used so that multiple functions can use resources
   190  	// simultaneously, but the resources are not closed until all functions
   191  	// accessing them have returned.
   192  	closed       bool
   193  	resourceLock sync.RWMutex
   194  }
   195  
   196  // checkUnlockHash will check that the host has an unlock hash. If the host
   197  // does not have an unlock hash, an attempt will be made to get an unlock hash
   198  // from the wallet. That may fail due to the wallet being locked, in which case
   199  // an error is returned.
   200  func (h *Host) checkUnlockHash() error {
   201  	if h.unlockHash == (types.UnlockHash{}) {
   202  		uc, err := h.wallet.NextAddress()
   203  		if err != nil {
   204  			return err
   205  		}
   206  
   207  		// Set the unlock hash and save the host. Saving is important, because
   208  		// the host will be using this unlock hash to establish identity, and
   209  		// losing it will mean silently losing part of the host identity.
   210  		h.unlockHash = uc.UnlockHash()
   211  		err = h.save()
   212  		if err != nil {
   213  			return err
   214  		}
   215  	}
   216  	return nil
   217  }
   218  
   219  // newHost returns an initialized Host, taking a set of dependencies as input.
   220  // By making the dependencies an argument of the 'new' call, the host can be
   221  // mocked such that the dependencies can return unexpected errors or unique
   222  // behaviors during testing, enabling easier testing of the failure modes of
   223  // the Host.
   224  func newHost(dependencies dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) {
   225  	// Check that all the dependencies were provided.
   226  	if cs == nil {
   227  		return nil, errNilCS
   228  	}
   229  	if tpool == nil {
   230  		return nil, errNilTpool
   231  	}
   232  	if wallet == nil {
   233  		return nil, errNilWallet
   234  	}
   235  
   236  	// Create the host object.
   237  	h := &Host{
   238  		cs:           cs,
   239  		tpool:        tpool,
   240  		wallet:       wallet,
   241  		dependencies: dependencies,
   242  
   243  		lockedStorageObligations: make(map[types.FileContractID]struct{}),
   244  
   245  		persistDir: persistDir,
   246  	}
   247  
   248  	var err error
   249  
   250  	// Add the storage manager to the host.
   251  	//
   252  	// TODO: instead of hardcoding a storage manager, the storage manager
   253  	// should probably be chosen by the person that calls 'New', same way that
   254  	// the wallet, transaction pool, and consensus set are.
   255  	h.StorageManager, err = storagemanager.New(filepath.Join(persistDir, "storagemanager"))
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	// Create the perist directory if it does not yet exist.
   261  	err = dependencies.mkdirAll(h.persistDir, 0700)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	// Initialize the logger. Logging should be initialized ASAP, because the
   267  	// rest of the initialization makes use of the logger.
   268  	h.log, err = dependencies.newLogger(filepath.Join(h.persistDir, logFile))
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	// Open the database containing the host's storage obligation metadata.
   274  	h.db, err = dependencies.openDatabase(dbMetadata, filepath.Join(h.persistDir, dbFilename))
   275  	if err != nil {
   276  		// An error will be returned if the database has the wrong version, but
   277  		// as of writing there was only one version of the database and all
   278  		// other databases would be incompatible.
   279  		_ = h.log.Close()
   280  		return nil, err
   281  	}
   282  	// After opening the database, it must be initialized. Most commonly,
   283  	// nothing happens. But for new databases, a set of buckets must be
   284  	// created. Initialization is also a good time to run sanity checks.
   285  	err = h.initDB()
   286  	if err != nil {
   287  		_ = h.log.Close()
   288  		_ = h.db.Close()
   289  		return nil, err
   290  	}
   291  
   292  	// Load the prior persistence structures.
   293  	err = h.load()
   294  	if err != nil {
   295  		_ = h.log.Close()
   296  		_ = h.db.Close()
   297  		return nil, err
   298  	}
   299  
   300  	// Get the host established on the network.
   301  	err = h.initNetworking(listenerAddress)
   302  	if err != nil {
   303  		_ = h.log.Close()
   304  		_ = h.db.Close()
   305  		return nil, err
   306  	}
   307  
   308  	return h, nil
   309  }
   310  
   311  // New returns an initialized Host.
   312  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) {
   313  	return newHost(productionDependencies{}, cs, tpool, wallet, address, persistDir)
   314  }
   315  
   316  // Close shuts down the host, preparing it for garbage collection.
   317  func (h *Host) Close() (composedError error) {
   318  	// Unsubscribe the host from the consensus set. Call will not terminate
   319  	// until the last consensus update has been sent to the host.
   320  	// Unsubscription must happen before any resources are released or
   321  	// terminated because the process consensus change function makes use of
   322  	// those resources.
   323  	h.cs.Unsubscribe(h)
   324  
   325  	// Close the listener, which means incoming network connections will be
   326  	// rejected. The listener should be closed before the host resources are
   327  	// disabled, as incoming connections will want to use the hosts resources.
   328  	err := h.listener.Close()
   329  	if err != nil {
   330  		composedError = composeErrors(composedError, err)
   331  	}
   332  
   333  	// Grab the resource lock and indicate that the host is closing. Concurrent
   334  	// functions hold the resource lock until they terminate, meaning that no
   335  	// threaded function will be running by the time the resource lock is
   336  	// acquired.
   337  	h.resourceLock.Lock()
   338  	h.closed = true
   339  	h.resourceLock.Unlock()
   340  
   341  	err = h.StorageManager.Close()
   342  	if err != nil {
   343  		composedError = composeErrors(composedError, err)
   344  	}
   345  
   346  	// Close the bolt database.
   347  	err = h.db.Close()
   348  	if err != nil {
   349  		composedError = composeErrors(composedError, err)
   350  	}
   351  
   352  	// Clear the port that was forwarded at startup. The port handling must
   353  	// happen before the logger is closed, as it leaves a logging message.
   354  	err = h.managedClearPort()
   355  	if err != nil {
   356  		composedError = composeErrors(composedError, err)
   357  	}
   358  
   359  	// Save the latest host state.
   360  	h.mu.Lock()
   361  	err = h.saveSync()
   362  	h.mu.Unlock()
   363  	if err != nil {
   364  		composedError = composeErrors(composedError, err)
   365  	}
   366  
   367  	// Close the logger. The logger should be the last thing to shut down so
   368  	// that all other objects have access to logging while closing.
   369  	err = h.log.Close()
   370  	if err != nil {
   371  		composedError = composeErrors(composedError, err)
   372  	}
   373  	return composedError
   374  }
   375  
   376  // ExternalSettings returns the hosts external settings. These values cannot be
   377  // set by the user (host is configured through InternalSettings), and are the
   378  // values that get displayed to other hosts on the network.
   379  func (h *Host) ExternalSettings() modules.HostExternalSettings {
   380  	h.mu.RLock()
   381  	defer h.mu.RUnlock()
   382  	return h.externalSettings()
   383  }
   384  
   385  // FinancialMetrics returns information about the financial commitments,
   386  // rewards, and activities of the host.
   387  func (h *Host) FinancialMetrics() modules.HostFinancialMetrics {
   388  	h.mu.RLock()
   389  	defer h.mu.RUnlock()
   390  	return h.financialMetrics
   391  }
   392  
   393  // SetInternalSettings updates the host's internal HostInternalSettings object.
   394  func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error {
   395  	h.mu.Lock()
   396  	defer h.mu.Unlock()
   397  	h.resourceLock.RLock()
   398  	defer h.resourceLock.RUnlock()
   399  	if h.closed {
   400  		return errHostClosed
   401  	}
   402  
   403  	// The host should not be accepting file contracts if it does not have an
   404  	// unlock hash.
   405  	if settings.AcceptingContracts {
   406  		err := h.checkUnlockHash()
   407  		if err != nil {
   408  			return errors.New("internal settings not updated, no unlock hash: " + err.Error())
   409  		}
   410  	}
   411  
   412  	if settings.NetAddress != "" {
   413  		err := settings.NetAddress.IsValid()
   414  		if err != nil {
   415  			return errors.New("internal settings not updated, invalid NetAddress: " + err.Error())
   416  		}
   417  	}
   418  
   419  	// Check if the net address for the host has changed. If it has, and it's
   420  	// not equal to the auto address, then the host is going to need to make
   421  	// another blockchain announcement.
   422  	if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress {
   423  		h.announced = false
   424  	}
   425  
   426  	h.settings = settings
   427  	h.revisionNumber++
   428  
   429  	err := h.saveSync()
   430  	if err != nil {
   431  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   432  	}
   433  	return nil
   434  }
   435  
   436  // InternalSettings returns the settings of a host.
   437  func (h *Host) InternalSettings() modules.HostInternalSettings {
   438  	h.mu.RLock()
   439  	defer h.mu.RUnlock()
   440  	return h.settings
   441  }