github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/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: what happens if the renter submits the revision early, before the
     7  // final revision. Will the host mark the contract as complete?
     8  
     9  // TODO: Host and renter are reporting errors where the renter is not adding
    10  // enough fees to the file contract.
    11  
    12  // TODO: Test the safety of the builder, it should be okay to have multiple
    13  // builders open for up to 600 seconds, which means multiple blocks could be
    14  // received in that time period. Should also check what happens if a parent
    15  // gets confirmed on the blockchain before the builder is finished.
    16  
    17  // TODO: Double check that any network connection has a finite deadline -
    18  // handling action items properly requires that the locks held on the
    19  // obligations eventually be released. There's also some more advanced
    20  // implementation that needs to happen with the storage obligation locks to
    21  // make sure that someone who wants a lock is able to get it eventually.
    22  
    23  // TODO: Add contract compensation from form contract to the storage obligation
    24  // financial metrics, and to the host's tracking.
    25  
    26  // TODO: merge the network interfaces stuff, don't forget to include the
    27  // 'announced' variable as one of the outputs.
    28  
    29  // TODO: 'announced' doesn't tell you if the announcement made it to the
    30  // blockchain.
    31  
    32  // TODO: Need to make sure that the revision exchange for the renter and the
    33  // host is being handled correctly. For the host, it's not so difficult. The
    34  // host need only send the most recent revision every time. But, the host
    35  // should not sign a revision unless the renter has explicitly signed such that
    36  // the 'WholeTransaction' fields cover only the revision and that the
    37  // signatures for the revision don't depend on anything else. The renter needs
    38  // to verify the same when checking on a file contract revision from the host.
    39  // If the host has submitted a file contract revision where the signatures have
    40  // signed the whole file contract, there is an issue.
    41  
    42  // TODO: there is a mistake in the file contract revision rpc, the host, if it
    43  // does not have the right file contract id, should be returning an error there
    44  // to the renter (and not just to it's calling function without informing the
    45  // renter what's up).
    46  
    47  // TODO: Need to make sure that the correct height is being used when adding
    48  // sectors to the storage manager - in some places right now WindowStart is
    49  // being used but really it's WindowEnd that should be in use.
    50  
    51  // TODO: The host needs some way to blacklist file contracts that are being
    52  // abusive by repeatedly getting free download batches.
    53  
    54  // TODO: clean up all of the magic numbers in the host.
    55  
    56  // TODO: revamp the finances for the storage obligations.
    57  
    58  // TODO: host_test.go has commented out tests.
    59  
    60  // TODO: network_test.go has commented out tests.
    61  
    62  // TODO: persist_test.go has commented out tests.
    63  
    64  // TODO: update_test.go has commented out tests.
    65  
    66  import (
    67  	"errors"
    68  	"fmt"
    69  	"net"
    70  	"path/filepath"
    71  	"sync"
    72  
    73  	"SiaPrime/build"
    74  	"SiaPrime/crypto"
    75  	"SiaPrime/modules"
    76  	"SiaPrime/modules/host/contractmanager"
    77  	"SiaPrime/persist"
    78  	siasync "SiaPrime/sync"
    79  	"SiaPrime/types"
    80  )
    81  
    82  const (
    83  	// Names of the various persistent files in the host.
    84  	dbFilename   = modules.HostDir + ".db"
    85  	logFile      = modules.HostDir + ".log"
    86  	settingsFile = modules.HostDir + ".json"
    87  )
    88  
    89  var (
    90  	// dbMetadata is a header that gets put into the database to identify a
    91  	// version and indicate that the database holds host information.
    92  	dbMetadata = persist.Metadata{
    93  		Header:  "Sia Host DB",
    94  		Version: "0.5.2",
    95  	}
    96  
    97  	// errHostClosed gets returned when a call is rejected due to the host
    98  	// having been closed.
    99  	errHostClosed = errors.New("call is disabled because the host is closed")
   100  
   101  	// Nil dependency errors.
   102  	errNilCS      = errors.New("host cannot use a nil state")
   103  	errNilTpool   = errors.New("host cannot use a nil transaction pool")
   104  	errNilWallet  = errors.New("host cannot use a nil wallet")
   105  	errNilGateway = errors.New("host cannot use nil gateway")
   106  
   107  	// persistMetadata is the header that gets written to the persist file, and is
   108  	// used to recognize other persist files.
   109  	persistMetadata = persist.Metadata{
   110  		Header:  "Sia Host",
   111  		Version: "1.2.0",
   112  	}
   113  )
   114  
   115  // A Host contains all the fields necessary for storing files for clients and
   116  // performing the storage proofs on the received files.
   117  type Host struct {
   118  	// RPC Metrics - atomic variables need to be placed at the top to preserve
   119  	// compatibility with 32bit systems. These values are not persistent.
   120  	atomicDownloadCalls     uint64
   121  	atomicErroredCalls      uint64
   122  	atomicFormContractCalls uint64
   123  	atomicRenewCalls        uint64
   124  	atomicReviseCalls       uint64
   125  	atomicSettingsCalls     uint64
   126  	atomicUnrecognizedCalls uint64
   127  
   128  	// Error management. There are a few different types of errors returned by
   129  	// the host. These errors intentionally not persistent, so that the logging
   130  	// limits of each error type will be reset each time the host is reset.
   131  	// These values are not persistent.
   132  	atomicCommunicationErrors uint64
   133  	atomicConnectionErrors    uint64
   134  	atomicConsensusErrors     uint64
   135  	atomicInternalErrors      uint64
   136  	atomicNormalErrors        uint64
   137  
   138  	// Dependencies.
   139  	cs           modules.ConsensusSet
   140  	g            modules.Gateway
   141  	tpool        modules.TransactionPool
   142  	wallet       modules.Wallet
   143  	dependencies modules.Dependencies
   144  	modules.StorageManager
   145  
   146  	// Host ACID fields - these fields need to be updated in serial, ACID
   147  	// transactions.
   148  	announced         bool
   149  	announceConfirmed bool
   150  	blockHeight       types.BlockHeight
   151  	publicKey         types.SiaPublicKey
   152  	secretKey         crypto.SecretKey
   153  	recentChange      modules.ConsensusChangeID
   154  	unlockHash        types.UnlockHash // A wallet address that can receive coins.
   155  
   156  	// Host transient fields - these fields are either determined at startup or
   157  	// otherwise are not critical to always be correct.
   158  	autoAddress          modules.NetAddress // Determined using automatic tooling in network.go
   159  	financialMetrics     modules.HostFinancialMetrics
   160  	settings             modules.HostInternalSettings
   161  	revisionNumber       uint64
   162  	workingStatus        modules.HostWorkingStatus
   163  	connectabilityStatus modules.HostConnectabilityStatus
   164  
   165  	// A map of storage obligations that are currently being modified. Locks on
   166  	// storage obligations can be long-running, and each storage obligation can
   167  	// be locked separately.
   168  	lockedStorageObligations map[types.FileContractID]*siasync.TryMutex
   169  
   170  	// Utilities.
   171  	db         *persist.BoltDatabase
   172  	listener   net.Listener
   173  	log        *persist.Logger
   174  	mu         sync.RWMutex
   175  	persistDir string
   176  	port       string
   177  	tg         siasync.ThreadGroup
   178  }
   179  
   180  // checkUnlockHash will check that the host has an unlock hash. If the host
   181  // does not have an unlock hash, an attempt will be made to get an unlock hash
   182  // from the wallet. That may fail due to the wallet being locked, in which case
   183  // an error is returned.
   184  func (h *Host) checkUnlockHash() error {
   185  	addrs, err := h.wallet.AllAddresses()
   186  	if err != nil {
   187  		return err
   188  	}
   189  	hasAddr := false
   190  	for _, addr := range addrs {
   191  		if h.unlockHash == addr {
   192  			hasAddr = true
   193  			break
   194  		}
   195  	}
   196  	if !hasAddr || h.unlockHash == (types.UnlockHash{}) {
   197  		uc, err := h.wallet.NextAddress()
   198  		if err != nil {
   199  			return err
   200  		}
   201  
   202  		// Set the unlock hash and save the host. Saving is important, because
   203  		// the host will be using this unlock hash to establish identity, and
   204  		// losing it will mean silently losing part of the host identity.
   205  		h.unlockHash = uc.UnlockHash()
   206  		err = h.saveSync()
   207  		if err != nil {
   208  			return err
   209  		}
   210  	}
   211  	return nil
   212  }
   213  
   214  // newHost returns an initialized Host, taking a set of dependencies as input.
   215  // By making the dependencies an argument of the 'new' call, the host can be
   216  // mocked such that the dependencies can return unexpected errors or unique
   217  // behaviors during testing, enabling easier testing of the failure modes of
   218  // the Host.
   219  func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, g modules.Gateway, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) {
   220  	// Check that all the dependencies were provided.
   221  	if cs == nil {
   222  		return nil, errNilCS
   223  	}
   224  	if g == nil {
   225  		return nil, errNilGateway
   226  	}
   227  	if tpool == nil {
   228  		return nil, errNilTpool
   229  	}
   230  	if wallet == nil {
   231  		return nil, errNilWallet
   232  	}
   233  
   234  	// Create the host object.
   235  	h := &Host{
   236  		cs:           cs,
   237  		g:            g,
   238  		tpool:        tpool,
   239  		wallet:       wallet,
   240  		dependencies: dependencies,
   241  
   242  		lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex),
   243  
   244  		persistDir: persistDir,
   245  	}
   246  
   247  	// Call stop in the event of a partial startup.
   248  	var err error
   249  	defer func() {
   250  		if err != nil {
   251  			err = composeErrors(h.tg.Stop(), err)
   252  		}
   253  	}()
   254  
   255  	// Create the perist directory if it does not yet exist.
   256  	err = dependencies.MkdirAll(h.persistDir, 0700)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	// Initialize the logger, and set up the stop call that will close the
   262  	// logger.
   263  	h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile))
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	h.tg.AfterStop(func() {
   268  		err = h.log.Close()
   269  		if err != nil {
   270  			// State of the logger is uncertain, a Println will have to
   271  			// suffice.
   272  			fmt.Println("Error when closing the logger:", err)
   273  		}
   274  	})
   275  
   276  	// Add the storage manager to the host, and set up the stop call that will
   277  	// close the storage manager.
   278  	h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager"))
   279  	if err != nil {
   280  		h.log.Println("Could not open the storage manager:", err)
   281  		return nil, err
   282  	}
   283  	h.tg.AfterStop(func() {
   284  		err = h.StorageManager.Close()
   285  		if err != nil {
   286  			h.log.Println("Could not close storage manager:", err)
   287  		}
   288  	})
   289  
   290  	// Load the prior persistence structures, and configure the host to save
   291  	// before shutting down.
   292  	err = h.load()
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  	h.tg.AfterStop(func() {
   297  		err = h.saveSync()
   298  		if err != nil {
   299  			h.log.Println("Could not save host upon shutdown:", err)
   300  		}
   301  	})
   302  
   303  	// Initialize the networking. We need to hold the lock while doing so since
   304  	// the previous load subscribed the host to the consenus set.
   305  	h.mu.Lock()
   306  	err = h.initNetworking(listenerAddress)
   307  	h.mu.Unlock()
   308  	if err != nil {
   309  		h.log.Println("Could not initialize host networking:", err)
   310  		return nil, err
   311  	}
   312  	return h, nil
   313  }
   314  
   315  // New returns an initialized Host.
   316  func New(cs modules.ConsensusSet, g modules.Gateway, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) {
   317  	return newHost(modules.ProdDependencies, cs, g, tpool, wallet, address, persistDir)
   318  }
   319  
   320  // Close shuts down the host.
   321  func (h *Host) Close() error {
   322  	return h.tg.Stop()
   323  }
   324  
   325  // ExternalSettings returns the hosts external settings. These values cannot be
   326  // set by the user (host is configured through InternalSettings), and are the
   327  // values that get displayed to other hosts on the network.
   328  func (h *Host) ExternalSettings() modules.HostExternalSettings {
   329  	h.mu.Lock()
   330  	defer h.mu.Unlock()
   331  	err := h.tg.Add()
   332  	if err != nil {
   333  		build.Critical("Call to ExternalSettings after close")
   334  	}
   335  	defer h.tg.Done()
   336  	return h.externalSettings()
   337  }
   338  
   339  // WorkingStatus returns the working state of the host, where working is
   340  // defined as having received more than workingStatusThreshold settings calls
   341  // over the period of workingStatusFrequency.
   342  func (h *Host) WorkingStatus() modules.HostWorkingStatus {
   343  	h.mu.RLock()
   344  	defer h.mu.RUnlock()
   345  	return h.workingStatus
   346  }
   347  
   348  // ConnectabilityStatus returns the connectability state of the host, whether
   349  // the host can connect to itself on its configured netaddress.
   350  func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus {
   351  	h.mu.RLock()
   352  	defer h.mu.RUnlock()
   353  	return h.connectabilityStatus
   354  }
   355  
   356  // FinancialMetrics returns information about the financial commitments,
   357  // rewards, and activities of the host.
   358  func (h *Host) FinancialMetrics() modules.HostFinancialMetrics {
   359  	h.mu.RLock()
   360  	defer h.mu.RUnlock()
   361  	err := h.tg.Add()
   362  	if err != nil {
   363  		build.Critical("Call to FinancialMetrics after close")
   364  	}
   365  	defer h.tg.Done()
   366  	return h.financialMetrics
   367  }
   368  
   369  // PublicKey returns the public key of the host that is used to facilitate
   370  // relationships between the host and renter.
   371  func (h *Host) PublicKey() types.SiaPublicKey {
   372  	h.mu.RLock()
   373  	defer h.mu.RUnlock()
   374  	return h.publicKey
   375  }
   376  
   377  // SetInternalSettings updates the host's internal HostInternalSettings object.
   378  func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error {
   379  	h.mu.Lock()
   380  	defer h.mu.Unlock()
   381  	err := h.tg.Add()
   382  	if err != nil {
   383  		return err
   384  	}
   385  	defer h.tg.Done()
   386  
   387  	// The host should not be accepting file contracts if it does not have an
   388  	// unlock hash.
   389  	if settings.AcceptingContracts {
   390  		err := h.checkUnlockHash()
   391  		if err != nil {
   392  			return errors.New("internal settings not updated, no unlock hash: " + err.Error())
   393  		}
   394  	}
   395  
   396  	if settings.NetAddress != "" {
   397  		err := settings.NetAddress.IsValid()
   398  		if err != nil {
   399  			return errors.New("internal settings not updated, invalid NetAddress: " + err.Error())
   400  		}
   401  	}
   402  
   403  	// Check if the net address for the host has changed. If it has, and it's
   404  	// not equal to the auto address, then the host is going to need to make
   405  	// another blockchain announcement.
   406  	if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress {
   407  		h.announced = false
   408  	}
   409  
   410  	h.settings = settings
   411  	h.revisionNumber++
   412  
   413  	err = h.saveSync()
   414  	if err != nil {
   415  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   416  	}
   417  	return nil
   418  }
   419  
   420  // InternalSettings returns the settings of a host.
   421  func (h *Host) InternalSettings() modules.HostInternalSettings {
   422  	h.mu.RLock()
   423  	defer h.mu.RUnlock()
   424  	err := h.tg.Add()
   425  	if err != nil {
   426  		return modules.HostInternalSettings{}
   427  	}
   428  	defer h.tg.Done()
   429  	return h.settings
   430  }