github.com/nebulouslabs/sia@v1.3.7/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  	"github.com/NebulousLabs/Sia/build"
    74  	"github.com/NebulousLabs/Sia/crypto"
    75  	"github.com/NebulousLabs/Sia/modules"
    76  	"github.com/NebulousLabs/Sia/modules/host/contractmanager"
    77  	"github.com/NebulousLabs/Sia/persist"
    78  	siasync "github.com/NebulousLabs/Sia/sync"
    79  	"github.com/NebulousLabs/Sia/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  
   106  	// persistMetadata is the header that gets written to the persist file, and is
   107  	// used to recognize other persist files.
   108  	persistMetadata = persist.Metadata{
   109  		Header:  "Sia Host",
   110  		Version: "1.2.0",
   111  	}
   112  )
   113  
   114  // A Host contains all the fields necessary for storing files for clients and
   115  // performing the storage proofs on the received files.
   116  type Host struct {
   117  	// RPC Metrics - atomic variables need to be placed at the top to preserve
   118  	// compatibility with 32bit systems. These values are not persistent.
   119  	atomicDownloadCalls     uint64
   120  	atomicErroredCalls      uint64
   121  	atomicFormContractCalls uint64
   122  	atomicRenewCalls        uint64
   123  	atomicReviseCalls       uint64
   124  	atomicSettingsCalls     uint64
   125  	atomicUnrecognizedCalls uint64
   126  
   127  	// Error management. There are a few different types of errors returned by
   128  	// the host. These errors intentionally not persistent, so that the logging
   129  	// limits of each error type will be reset each time the host is reset.
   130  	// These values are not persistent.
   131  	atomicCommunicationErrors uint64
   132  	atomicConnectionErrors    uint64
   133  	atomicConsensusErrors     uint64
   134  	atomicInternalErrors      uint64
   135  	atomicNormalErrors        uint64
   136  
   137  	// Dependencies.
   138  	cs           modules.ConsensusSet
   139  	tpool        modules.TransactionPool
   140  	wallet       modules.Wallet
   141  	dependencies modules.Dependencies
   142  	modules.StorageManager
   143  
   144  	// Host ACID fields - these fields need to be updated in serial, ACID
   145  	// transactions.
   146  	announced         bool
   147  	announceConfirmed bool
   148  	blockHeight       types.BlockHeight
   149  	publicKey         types.SiaPublicKey
   150  	secretKey         crypto.SecretKey
   151  	recentChange      modules.ConsensusChangeID
   152  	unlockHash        types.UnlockHash // A wallet address that can receive coins.
   153  
   154  	// Host transient fields - these fields are either determined at startup or
   155  	// otherwise are not critical to always be correct.
   156  	autoAddress          modules.NetAddress // Determined using automatic tooling in network.go
   157  	financialMetrics     modules.HostFinancialMetrics
   158  	settings             modules.HostInternalSettings
   159  	revisionNumber       uint64
   160  	workingStatus        modules.HostWorkingStatus
   161  	connectabilityStatus modules.HostConnectabilityStatus
   162  
   163  	// A map of storage obligations that are currently being modified. Locks on
   164  	// storage obligations can be long-running, and each storage obligation can
   165  	// be locked separately.
   166  	lockedStorageObligations map[types.FileContractID]*siasync.TryMutex
   167  
   168  	// Utilities.
   169  	db         *persist.BoltDatabase
   170  	listener   net.Listener
   171  	log        *persist.Logger
   172  	mu         sync.RWMutex
   173  	persistDir string
   174  	port       string
   175  	tg         siasync.ThreadGroup
   176  }
   177  
   178  // checkUnlockHash will check that the host has an unlock hash. If the host
   179  // does not have an unlock hash, an attempt will be made to get an unlock hash
   180  // from the wallet. That may fail due to the wallet being locked, in which case
   181  // an error is returned.
   182  func (h *Host) checkUnlockHash() error {
   183  	addrs, err := h.wallet.AllAddresses()
   184  	if err != nil {
   185  		return err
   186  	}
   187  	hasAddr := false
   188  	for _, addr := range addrs {
   189  		if h.unlockHash == addr {
   190  			hasAddr = true
   191  			break
   192  		}
   193  	}
   194  	if !hasAddr || h.unlockHash == (types.UnlockHash{}) {
   195  		uc, err := h.wallet.NextAddress()
   196  		if err != nil {
   197  			return err
   198  		}
   199  
   200  		// Set the unlock hash and save the host. Saving is important, because
   201  		// the host will be using this unlock hash to establish identity, and
   202  		// losing it will mean silently losing part of the host identity.
   203  		h.unlockHash = uc.UnlockHash()
   204  		err = h.saveSync()
   205  		if err != nil {
   206  			return err
   207  		}
   208  	}
   209  	return nil
   210  }
   211  
   212  // newHost returns an initialized Host, taking a set of dependencies as input.
   213  // By making the dependencies an argument of the 'new' call, the host can be
   214  // mocked such that the dependencies can return unexpected errors or unique
   215  // behaviors during testing, enabling easier testing of the failure modes of
   216  // the Host.
   217  func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) {
   218  	// Check that all the dependencies were provided.
   219  	if cs == nil {
   220  		return nil, errNilCS
   221  	}
   222  	if tpool == nil {
   223  		return nil, errNilTpool
   224  	}
   225  	if wallet == nil {
   226  		return nil, errNilWallet
   227  	}
   228  
   229  	// Create the host object.
   230  	h := &Host{
   231  		cs:           cs,
   232  		tpool:        tpool,
   233  		wallet:       wallet,
   234  		dependencies: dependencies,
   235  
   236  		lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex),
   237  
   238  		persistDir: persistDir,
   239  	}
   240  
   241  	// Call stop in the event of a partial startup.
   242  	var err error
   243  	defer func() {
   244  		if err != nil {
   245  			err = composeErrors(h.tg.Stop(), err)
   246  		}
   247  	}()
   248  
   249  	// Create the perist directory if it does not yet exist.
   250  	err = dependencies.MkdirAll(h.persistDir, 0700)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	// Initialize the logger, and set up the stop call that will close the
   256  	// logger.
   257  	h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile))
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	h.tg.AfterStop(func() {
   262  		err = h.log.Close()
   263  		if err != nil {
   264  			// State of the logger is uncertain, a Println will have to
   265  			// suffice.
   266  			fmt.Println("Error when closing the logger:", err)
   267  		}
   268  	})
   269  
   270  	// Add the storage manager to the host, and set up the stop call that will
   271  	// close the storage manager.
   272  	h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager"))
   273  	if err != nil {
   274  		h.log.Println("Could not open the storage manager:", err)
   275  		return nil, err
   276  	}
   277  	h.tg.AfterStop(func() {
   278  		err = h.StorageManager.Close()
   279  		if err != nil {
   280  			h.log.Println("Could not close storage manager:", err)
   281  		}
   282  	})
   283  
   284  	// Load the prior persistence structures, and configure the host to save
   285  	// before shutting down.
   286  	err = h.load()
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	h.tg.AfterStop(func() {
   291  		err = h.saveSync()
   292  		if err != nil {
   293  			h.log.Println("Could not save host upon shutdown:", err)
   294  		}
   295  	})
   296  
   297  	// Initialize the networking. We need to hold the lock while doing so since
   298  	// the previous load subscribed the host to the consenus set.
   299  	h.mu.Lock()
   300  	err = h.initNetworking(listenerAddress)
   301  	h.mu.Unlock()
   302  	if err != nil {
   303  		h.log.Println("Could not initialize host networking:", err)
   304  		return nil, err
   305  	}
   306  	return h, nil
   307  }
   308  
   309  // New returns an initialized Host.
   310  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) {
   311  	return newHost(modules.ProdDependencies, cs, tpool, wallet, address, persistDir)
   312  }
   313  
   314  // Close shuts down the host.
   315  func (h *Host) Close() error {
   316  	return h.tg.Stop()
   317  }
   318  
   319  // ExternalSettings returns the hosts external settings. These values cannot be
   320  // set by the user (host is configured through InternalSettings), and are the
   321  // values that get displayed to other hosts on the network.
   322  func (h *Host) ExternalSettings() modules.HostExternalSettings {
   323  	h.mu.Lock()
   324  	defer h.mu.Unlock()
   325  	err := h.tg.Add()
   326  	if err != nil {
   327  		build.Critical("Call to ExternalSettings after close")
   328  	}
   329  	defer h.tg.Done()
   330  	return h.externalSettings()
   331  }
   332  
   333  // WorkingStatus returns the working state of the host, where working is
   334  // defined as having received more than workingStatusThreshold settings calls
   335  // over the period of workingStatusFrequency.
   336  func (h *Host) WorkingStatus() modules.HostWorkingStatus {
   337  	h.mu.RLock()
   338  	defer h.mu.RUnlock()
   339  	return h.workingStatus
   340  }
   341  
   342  // ConnectabilityStatus returns the connectability state of the host, whether
   343  // the host can connect to itself on its configured netaddress.
   344  func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus {
   345  	h.mu.RLock()
   346  	defer h.mu.RUnlock()
   347  	return h.connectabilityStatus
   348  }
   349  
   350  // FinancialMetrics returns information about the financial commitments,
   351  // rewards, and activities of the host.
   352  func (h *Host) FinancialMetrics() modules.HostFinancialMetrics {
   353  	h.mu.RLock()
   354  	defer h.mu.RUnlock()
   355  	err := h.tg.Add()
   356  	if err != nil {
   357  		build.Critical("Call to FinancialMetrics after close")
   358  	}
   359  	defer h.tg.Done()
   360  	return h.financialMetrics
   361  }
   362  
   363  // PublicKey returns the public key of the host that is used to facilitate
   364  // relationships between the host and renter.
   365  func (h *Host) PublicKey() types.SiaPublicKey {
   366  	h.mu.RLock()
   367  	defer h.mu.RUnlock()
   368  	return h.publicKey
   369  }
   370  
   371  // SetInternalSettings updates the host's internal HostInternalSettings object.
   372  func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error {
   373  	h.mu.Lock()
   374  	defer h.mu.Unlock()
   375  	err := h.tg.Add()
   376  	if err != nil {
   377  		return err
   378  	}
   379  	defer h.tg.Done()
   380  
   381  	// The host should not be accepting file contracts if it does not have an
   382  	// unlock hash.
   383  	if settings.AcceptingContracts {
   384  		err := h.checkUnlockHash()
   385  		if err != nil {
   386  			return errors.New("internal settings not updated, no unlock hash: " + err.Error())
   387  		}
   388  	}
   389  
   390  	if settings.NetAddress != "" {
   391  		err := settings.NetAddress.IsValid()
   392  		if err != nil {
   393  			return errors.New("internal settings not updated, invalid NetAddress: " + err.Error())
   394  		}
   395  	}
   396  
   397  	// Check if the net address for the host has changed. If it has, and it's
   398  	// not equal to the auto address, then the host is going to need to make
   399  	// another blockchain announcement.
   400  	if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress {
   401  		h.announced = false
   402  	}
   403  
   404  	h.settings = settings
   405  	h.revisionNumber++
   406  
   407  	err = h.saveSync()
   408  	if err != nil {
   409  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   410  	}
   411  	return nil
   412  }
   413  
   414  // InternalSettings returns the settings of a host.
   415  func (h *Host) InternalSettings() modules.HostInternalSettings {
   416  	h.mu.RLock()
   417  	defer h.mu.RUnlock()
   418  	err := h.tg.Add()
   419  	if err != nil {
   420  		return modules.HostInternalSettings{}
   421  	}
   422  	defer h.tg.Done()
   423  	return h.settings
   424  }