gitlab.com/SiaPrime/SiaPrime@v1.4.1/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  	"gitlab.com/SiaPrime/SiaPrime/build"
    74  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    75  	"gitlab.com/SiaPrime/SiaPrime/modules"
    76  	"gitlab.com/SiaPrime/SiaPrime/modules/host/contractmanager"
    77  	"gitlab.com/SiaPrime/SiaPrime/persist"
    78  	siasync "gitlab.com/SiaPrime/SiaPrime/sync"
    79  	"gitlab.com/SiaPrime/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  	// Ensure the host is consistent by pruning any stale storage obligations.
   304  	if err := h.PruneStaleStorageObligations(); err != nil {
   305  		h.log.Println("Could not prune stale storage obligations:", err)
   306  		return nil, err
   307  	}
   308  
   309  	// Initialize the networking. We need to hold the lock while doing so since
   310  	// the previous load subscribed the host to the consensus set.
   311  	h.mu.Lock()
   312  	err = h.initNetworking(listenerAddress)
   313  	h.mu.Unlock()
   314  	if err != nil {
   315  		h.log.Println("Could not initialize host networking:", err)
   316  		return nil, err
   317  	}
   318  	return h, nil
   319  }
   320  
   321  // New returns an initialized Host.
   322  func New(cs modules.ConsensusSet, g modules.Gateway, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) {
   323  	return newHost(modules.ProdDependencies, cs, g, tpool, wallet, address, persistDir)
   324  }
   325  
   326  // Close shuts down the host.
   327  func (h *Host) Close() error {
   328  	return h.tg.Stop()
   329  }
   330  
   331  // ExternalSettings returns the hosts external settings. These values cannot be
   332  // set by the user (host is configured through InternalSettings), and are the
   333  // values that get displayed to other hosts on the network.
   334  func (h *Host) ExternalSettings() modules.HostExternalSettings {
   335  	h.mu.Lock()
   336  	defer h.mu.Unlock()
   337  	err := h.tg.Add()
   338  	if err != nil {
   339  		build.Critical("Call to ExternalSettings after close")
   340  	}
   341  	defer h.tg.Done()
   342  	return h.externalSettings()
   343  }
   344  
   345  // WorkingStatus returns the working state of the host, where working is
   346  // defined as having received more than workingStatusThreshold settings calls
   347  // over the period of workingStatusFrequency.
   348  func (h *Host) WorkingStatus() modules.HostWorkingStatus {
   349  	h.mu.RLock()
   350  	defer h.mu.RUnlock()
   351  	return h.workingStatus
   352  }
   353  
   354  // ConnectabilityStatus returns the connectability state of the host, whether
   355  // the host can connect to itself on its configured netaddress.
   356  func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus {
   357  	h.mu.RLock()
   358  	defer h.mu.RUnlock()
   359  	return h.connectabilityStatus
   360  }
   361  
   362  // FinancialMetrics returns information about the financial commitments,
   363  // rewards, and activities of the host.
   364  func (h *Host) FinancialMetrics() modules.HostFinancialMetrics {
   365  	h.mu.RLock()
   366  	defer h.mu.RUnlock()
   367  	err := h.tg.Add()
   368  	if err != nil {
   369  		build.Critical("Call to FinancialMetrics after close")
   370  	}
   371  	defer h.tg.Done()
   372  	return h.financialMetrics
   373  }
   374  
   375  // PublicKey returns the public key of the host that is used to facilitate
   376  // relationships between the host and renter.
   377  func (h *Host) PublicKey() types.SiaPublicKey {
   378  	h.mu.RLock()
   379  	defer h.mu.RUnlock()
   380  	return h.publicKey
   381  }
   382  
   383  // SetInternalSettings updates the host's internal HostInternalSettings object.
   384  func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error {
   385  	h.mu.Lock()
   386  	defer h.mu.Unlock()
   387  	err := h.tg.Add()
   388  	if err != nil {
   389  		return err
   390  	}
   391  	defer h.tg.Done()
   392  
   393  	// The host should not be accepting file contracts if it does not have an
   394  	// unlock hash.
   395  	if settings.AcceptingContracts {
   396  		err := h.checkUnlockHash()
   397  		if err != nil {
   398  			return errors.New("internal settings not updated, no unlock hash: " + err.Error())
   399  		}
   400  	}
   401  
   402  	if settings.NetAddress != "" {
   403  		err := settings.NetAddress.IsValid()
   404  		if err != nil {
   405  			return errors.New("internal settings not updated, invalid NetAddress: " + err.Error())
   406  		}
   407  	}
   408  
   409  	// Check if the net address for the host has changed. If it has, and it's
   410  	// not equal to the auto address, then the host is going to need to make
   411  	// another blockchain announcement.
   412  	if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress {
   413  		h.announced = false
   414  	}
   415  
   416  	h.settings = settings
   417  	h.revisionNumber++
   418  
   419  	err = h.saveSync()
   420  	if err != nil {
   421  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   422  	}
   423  	return nil
   424  }
   425  
   426  // InternalSettings returns the settings of a host.
   427  func (h *Host) InternalSettings() modules.HostInternalSettings {
   428  	h.mu.RLock()
   429  	defer h.mu.RUnlock()
   430  	err := h.tg.Add()
   431  	if err != nil {
   432  		return modules.HostInternalSettings{}
   433  	}
   434  	defer h.tg.Done()
   435  	return h.settings
   436  }