gitlab.com/jokerrs1/Sia@v1.3.2/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 := h.wallet.AllAddresses()
   184  	hasAddr := false
   185  	for _, addr := range addrs {
   186  		if h.unlockHash == addr {
   187  			hasAddr = true
   188  			break
   189  		}
   190  	}
   191  	if !hasAddr || h.unlockHash == (types.UnlockHash{}) {
   192  		uc, err := h.wallet.NextAddress()
   193  		if err != nil {
   194  			return err
   195  		}
   196  
   197  		// Set the unlock hash and save the host. Saving is important, because
   198  		// the host will be using this unlock hash to establish identity, and
   199  		// losing it will mean silently losing part of the host identity.
   200  		h.unlockHash = uc.UnlockHash()
   201  		err = h.saveSync()
   202  		if err != nil {
   203  			return err
   204  		}
   205  	}
   206  	return nil
   207  }
   208  
   209  // newHost returns an initialized Host, taking a set of dependencies as input.
   210  // By making the dependencies an argument of the 'new' call, the host can be
   211  // mocked such that the dependencies can return unexpected errors or unique
   212  // behaviors during testing, enabling easier testing of the failure modes of
   213  // the Host.
   214  func newHost(dependencies modules.Dependencies, cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, listenerAddress string, persistDir string) (*Host, error) {
   215  	// Check that all the dependencies were provided.
   216  	if cs == nil {
   217  		return nil, errNilCS
   218  	}
   219  	if tpool == nil {
   220  		return nil, errNilTpool
   221  	}
   222  	if wallet == nil {
   223  		return nil, errNilWallet
   224  	}
   225  
   226  	// Create the host object.
   227  	h := &Host{
   228  		cs:           cs,
   229  		tpool:        tpool,
   230  		wallet:       wallet,
   231  		dependencies: dependencies,
   232  
   233  		lockedStorageObligations: make(map[types.FileContractID]*siasync.TryMutex),
   234  
   235  		persistDir: persistDir,
   236  	}
   237  
   238  	// Call stop in the event of a partial startup.
   239  	var err error
   240  	defer func() {
   241  		if err != nil {
   242  			err = composeErrors(h.tg.Stop(), err)
   243  		}
   244  	}()
   245  
   246  	// Create the perist directory if it does not yet exist.
   247  	err = dependencies.MkdirAll(h.persistDir, 0700)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	// Initialize the logger, and set up the stop call that will close the
   253  	// logger.
   254  	h.log, err = dependencies.NewLogger(filepath.Join(h.persistDir, logFile))
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	h.tg.AfterStop(func() {
   259  		err = h.log.Close()
   260  		if err != nil {
   261  			// State of the logger is uncertain, a Println will have to
   262  			// suffice.
   263  			fmt.Println("Error when closing the logger:", err)
   264  		}
   265  	})
   266  
   267  	// Add the storage manager to the host, and set up the stop call that will
   268  	// close the storage manager.
   269  	h.StorageManager, err = contractmanager.New(filepath.Join(persistDir, "contractmanager"))
   270  	if err != nil {
   271  		h.log.Println("Could not open the storage manager:", err)
   272  		return nil, err
   273  	}
   274  	h.tg.AfterStop(func() {
   275  		err = h.StorageManager.Close()
   276  		if err != nil {
   277  			h.log.Println("Could not close storage manager:", err)
   278  		}
   279  	})
   280  
   281  	// Load the prior persistence structures, and configure the host to save
   282  	// before shutting down.
   283  	err = h.load()
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	h.tg.AfterStop(func() {
   288  		err = h.saveSync()
   289  		if err != nil {
   290  			h.log.Println("Could not save host upon shutdown:", err)
   291  		}
   292  	})
   293  
   294  	// Initialize the networking.
   295  	err = h.initNetworking(listenerAddress)
   296  	if err != nil {
   297  		h.log.Println("Could not initialize host networking:", err)
   298  		return nil, err
   299  	}
   300  	return h, nil
   301  }
   302  
   303  // New returns an initialized Host.
   304  func New(cs modules.ConsensusSet, tpool modules.TransactionPool, wallet modules.Wallet, address string, persistDir string) (*Host, error) {
   305  	return newHost(&modules.ProductionDependencies{}, cs, tpool, wallet, address, persistDir)
   306  }
   307  
   308  // Close shuts down the host.
   309  func (h *Host) Close() error {
   310  	return h.tg.Stop()
   311  }
   312  
   313  // ExternalSettings returns the hosts external settings. These values cannot be
   314  // set by the user (host is configured through InternalSettings), and are the
   315  // values that get displayed to other hosts on the network.
   316  func (h *Host) ExternalSettings() modules.HostExternalSettings {
   317  	h.mu.Lock()
   318  	defer h.mu.Unlock()
   319  	err := h.tg.Add()
   320  	if err != nil {
   321  		build.Critical("Call to ExternalSettings after close")
   322  	}
   323  	defer h.tg.Done()
   324  	return h.externalSettings()
   325  }
   326  
   327  // WorkingStatus returns the working state of the host, where working is
   328  // defined as having received more than workingStatusThreshold settings calls
   329  // over the period of workingStatusFrequency.
   330  func (h *Host) WorkingStatus() modules.HostWorkingStatus {
   331  	h.mu.RLock()
   332  	defer h.mu.RUnlock()
   333  	return h.workingStatus
   334  }
   335  
   336  // ConnectabilityStatus returns the connectability state of the host, whether
   337  // the host can connect to itself on its configured netaddress.
   338  func (h *Host) ConnectabilityStatus() modules.HostConnectabilityStatus {
   339  	h.mu.RLock()
   340  	defer h.mu.RUnlock()
   341  	return h.connectabilityStatus
   342  }
   343  
   344  // FinancialMetrics returns information about the financial commitments,
   345  // rewards, and activities of the host.
   346  func (h *Host) FinancialMetrics() modules.HostFinancialMetrics {
   347  	h.mu.RLock()
   348  	defer h.mu.RUnlock()
   349  	err := h.tg.Add()
   350  	if err != nil {
   351  		build.Critical("Call to FinancialMetrics after close")
   352  	}
   353  	defer h.tg.Done()
   354  	return h.financialMetrics
   355  }
   356  
   357  // PublicKey returns the public key of the host that is used to facilitate
   358  // relationships between the host and renter.
   359  func (h *Host) PublicKey() types.SiaPublicKey {
   360  	h.mu.RLock()
   361  	defer h.mu.RUnlock()
   362  	return h.publicKey
   363  }
   364  
   365  // SetInternalSettings updates the host's internal HostInternalSettings object.
   366  func (h *Host) SetInternalSettings(settings modules.HostInternalSettings) error {
   367  	h.mu.Lock()
   368  	defer h.mu.Unlock()
   369  	err := h.tg.Add()
   370  	if err != nil {
   371  		return err
   372  	}
   373  	defer h.tg.Done()
   374  
   375  	// The host should not be accepting file contracts if it does not have an
   376  	// unlock hash.
   377  	if settings.AcceptingContracts {
   378  		err := h.checkUnlockHash()
   379  		if err != nil {
   380  			return errors.New("internal settings not updated, no unlock hash: " + err.Error())
   381  		}
   382  	}
   383  
   384  	if settings.NetAddress != "" {
   385  		err := settings.NetAddress.IsValid()
   386  		if err != nil {
   387  			return errors.New("internal settings not updated, invalid NetAddress: " + err.Error())
   388  		}
   389  	}
   390  
   391  	// Check if the net address for the host has changed. If it has, and it's
   392  	// not equal to the auto address, then the host is going to need to make
   393  	// another blockchain announcement.
   394  	if h.settings.NetAddress != settings.NetAddress && settings.NetAddress != h.autoAddress {
   395  		h.announced = false
   396  	}
   397  
   398  	h.settings = settings
   399  	h.revisionNumber++
   400  
   401  	err = h.saveSync()
   402  	if err != nil {
   403  		return errors.New("internal settings updated, but failed saving to disk: " + err.Error())
   404  	}
   405  	return nil
   406  }
   407  
   408  // InternalSettings returns the settings of a host.
   409  func (h *Host) InternalSettings() modules.HostInternalSettings {
   410  	h.mu.RLock()
   411  	defer h.mu.RUnlock()
   412  	err := h.tg.Add()
   413  	if err != nil {
   414  		return modules.HostInternalSettings{}
   415  	}
   416  	defer h.tg.Done()
   417  	return h.settings
   418  }