github.com/NebulousLabs/Sia@v1.3.7/modules/renter/renter.go (about)

     1  // Package renter is responsible for uploading and downloading files on the sia
     2  // network.
     3  package renter
     4  
     5  // NOTE: Some of the concurrency patterns in the renter are fairly complex. A
     6  // lot of this has been cleaned up, though some shadows of previous demons still
     7  // remain. Be careful when working with anything that involves concurrency.
     8  
     9  // TODO: Allow the 'baseMemory' to be set by the user.
    10  
    11  // TODO: The repair loop currently receives new upload jobs through a channel.
    12  // The download loop has a better model, a heap that can be pushed to and popped
    13  // from concurrently without needing complex channel communication. Migrating
    14  // the renter to this model should clean up some of the places where uploading
    15  // bottlenecks, and reduce the amount of channel-ninjitsu required to make the
    16  // uploading function.
    17  
    18  // TODO: Allow user to configure the packet size when ratelimiting the renter.
    19  // Currently the default is set to 16kb. That's going to require updating the
    20  // API and extending the settings object, and then tweaking the
    21  // setBandwidthLimits function.
    22  
    23  import (
    24  	"errors"
    25  	"reflect"
    26  	"strings"
    27  	"sync"
    28  
    29  	"github.com/NebulousLabs/Sia/build"
    30  	"github.com/NebulousLabs/Sia/modules"
    31  	"github.com/NebulousLabs/Sia/modules/renter/contractor"
    32  	"github.com/NebulousLabs/Sia/modules/renter/hostdb"
    33  	"github.com/NebulousLabs/Sia/persist"
    34  	siasync "github.com/NebulousLabs/Sia/sync"
    35  	"github.com/NebulousLabs/Sia/types"
    36  
    37  	"github.com/NebulousLabs/threadgroup"
    38  )
    39  
    40  var (
    41  	errNilContractor = errors.New("cannot create renter with nil contractor")
    42  	errNilCS         = errors.New("cannot create renter with nil consensus set")
    43  	errNilGateway    = errors.New("cannot create hostdb with nil gateway")
    44  	errNilHdb        = errors.New("cannot create renter with nil hostdb")
    45  	errNilTpool      = errors.New("cannot create renter with nil transaction pool")
    46  )
    47  
    48  var (
    49  	// priceEstimationScope is the number of hosts that get queried by the
    50  	// renter when providing price estimates. Especially for the 'Standard'
    51  	// variable, there should be congruence with the number of contracts being
    52  	// used in the renter allowance.
    53  	priceEstimationScope = build.Select(build.Var{
    54  		Standard: int(50),
    55  		Dev:      int(12),
    56  		Testing:  int(4),
    57  	}).(int)
    58  )
    59  
    60  // A hostDB is a database of hosts that the renter can use for figuring out who
    61  // to upload to, and download from.
    62  type hostDB interface {
    63  	// ActiveHosts returns the list of hosts that are actively being selected
    64  	// from.
    65  	ActiveHosts() []modules.HostDBEntry
    66  
    67  	// AllHosts returns the full list of hosts known to the hostdb, sorted in
    68  	// order of preference.
    69  	AllHosts() []modules.HostDBEntry
    70  
    71  	// AverageContractPrice returns the average contract price of a host.
    72  	AverageContractPrice() types.Currency
    73  
    74  	// Close closes the hostdb.
    75  	Close() error
    76  
    77  	// Host returns the HostDBEntry for a given host.
    78  	Host(types.SiaPublicKey) (modules.HostDBEntry, bool)
    79  
    80  	// initialScanComplete returns a boolean indicating if the initial scan of the
    81  	// hostdb is completed.
    82  	InitialScanComplete() (bool, error)
    83  
    84  	// RandomHosts returns a set of random hosts, weighted by their estimated
    85  	// usefulness / attractiveness to the renter. RandomHosts will not return
    86  	// any offline or inactive hosts.
    87  	RandomHosts(int, []types.SiaPublicKey) ([]modules.HostDBEntry, error)
    88  
    89  	// ScoreBreakdown returns a detailed explanation of the various properties
    90  	// of the host.
    91  	ScoreBreakdown(modules.HostDBEntry) modules.HostScoreBreakdown
    92  
    93  	// EstimateHostScore returns the estimated score breakdown of a host with the
    94  	// provided settings.
    95  	EstimateHostScore(modules.HostDBEntry) modules.HostScoreBreakdown
    96  }
    97  
    98  // A hostContractor negotiates, revises, renews, and provides access to file
    99  // contracts.
   100  type hostContractor interface {
   101  	// SetAllowance sets the amount of money the contractor is allowed to
   102  	// spend on contracts over a given time period, divided among the number
   103  	// of hosts specified. Note that contractor can start forming contracts as
   104  	// soon as SetAllowance is called; that is, it may block.
   105  	SetAllowance(modules.Allowance) error
   106  
   107  	// Allowance returns the current allowance
   108  	Allowance() modules.Allowance
   109  
   110  	// Close closes the hostContractor.
   111  	Close() error
   112  
   113  	// Contracts returns the staticContracts of the renter's hostContractor.
   114  	Contracts() []modules.RenterContract
   115  
   116  	// OldContracts returns the oldContracts of the renter's hostContractor.
   117  	OldContracts() []modules.RenterContract
   118  
   119  	// ContractByPublicKey returns the contract associated with the host key.
   120  	ContractByPublicKey(types.SiaPublicKey) (modules.RenterContract, bool)
   121  
   122  	// ContractUtility returns the utility field for a given contract, along
   123  	// with a bool indicating if it exists.
   124  	ContractUtility(types.SiaPublicKey) (modules.ContractUtility, bool)
   125  
   126  	// CurrentPeriod returns the height at which the current allowance period
   127  	// began.
   128  	CurrentPeriod() types.BlockHeight
   129  
   130  	// PeriodSpending returns the amount spent on contracts during the current
   131  	// billing period.
   132  	PeriodSpending() modules.ContractorSpending
   133  
   134  	// Editor creates an Editor from the specified contract ID, allowing the
   135  	// insertion, deletion, and modification of sectors.
   136  	Editor(types.SiaPublicKey, <-chan struct{}) (contractor.Editor, error)
   137  
   138  	// IsOffline reports whether the specified host is considered offline.
   139  	IsOffline(types.SiaPublicKey) bool
   140  
   141  	// Downloader creates a Downloader from the specified contract ID,
   142  	// allowing the retrieval of sectors.
   143  	Downloader(types.SiaPublicKey, <-chan struct{}) (contractor.Downloader, error)
   144  
   145  	// ResolveIDToPubKey returns the public key of a host given a contract id.
   146  	ResolveIDToPubKey(types.FileContractID) types.SiaPublicKey
   147  
   148  	// RateLimits Gets the bandwidth limits for connections created by the
   149  	// contractor and its submodules.
   150  	RateLimits() (readBPS int64, writeBPS int64, packetSize uint64)
   151  
   152  	// SetRateLimits sets the bandwidth limits for connections created by the
   153  	// contractor and its submodules.
   154  	SetRateLimits(int64, int64, uint64)
   155  }
   156  
   157  // A trackedFile contains metadata about files being tracked by the Renter.
   158  // Tracked files are actively repaired by the Renter. By default, files
   159  // uploaded by the user are tracked, and files that are added (via loading a
   160  // .sia file) are not.
   161  type trackedFile struct {
   162  	// location of original file on disk
   163  	RepairPath string
   164  }
   165  
   166  // A Renter is responsible for tracking all of the files that a user has
   167  // uploaded to Sia, as well as the locations and health of these files.
   168  //
   169  // TODO: Separate the workerPool to have its own mutex. The workerPool doesn't
   170  // interfere with any of the other fields in the renter, should be fine for it
   171  // to have a separate mutex, that way operations on the worker pool don't block
   172  // operations on other parts of the struct. If we're going to do it that way,
   173  // might make sense to split the worker pool off into it's own struct entirely
   174  // the same way that we split of the memoryManager entirely.
   175  type Renter struct {
   176  	// File management.
   177  	//
   178  	// tracking contains a list of files that the user intends to maintain. By
   179  	// default, files loaded through sharing are not maintained by the user.
   180  	files map[string]*file
   181  
   182  	// Download management. The heap has a separate mutex because it is always
   183  	// accessed in isolation.
   184  	downloadHeapMu sync.Mutex         // Used to protect the downloadHeap.
   185  	downloadHeap   *downloadChunkHeap // A heap of priority-sorted chunks to download.
   186  	newDownloads   chan struct{}      // Used to notify download loop that new downloads are available.
   187  
   188  	// Download history. The history list has its own mutex because it is always
   189  	// accessed in isolation.
   190  	//
   191  	// TODO: Currently the download history doesn't include repair-initiated
   192  	// downloads, and instead only contains user-initiated downloads.
   193  	downloadHistory   []*download
   194  	downloadHistoryMu sync.Mutex
   195  
   196  	// Upload management.
   197  	uploadHeap uploadHeap
   198  
   199  	// List of workers that can be used for uploading and/or downloading.
   200  	memoryManager *memoryManager
   201  	workerPool    map[types.FileContractID]*worker
   202  
   203  	// Cache the last price estimation result.
   204  	lastEstimation modules.RenterPriceEstimation
   205  
   206  	// Utilities.
   207  	staticStreamCache *streamCache
   208  	cs                modules.ConsensusSet
   209  	deps              modules.Dependencies
   210  	g                 modules.Gateway
   211  	hostContractor    hostContractor
   212  	hostDB            hostDB
   213  	log               *persist.Logger
   214  	persist           persistence
   215  	persistDir        string
   216  	mu                *siasync.RWMutex
   217  	tg                threadgroup.ThreadGroup
   218  	tpool             modules.TransactionPool
   219  }
   220  
   221  // Close closes the Renter and its dependencies
   222  func (r *Renter) Close() error {
   223  	r.tg.Stop()
   224  	r.hostDB.Close()
   225  	return r.hostContractor.Close()
   226  }
   227  
   228  // PriceEstimation estimates the cost in siacoins of performing various storage
   229  // and data operations.
   230  //
   231  // TODO: Make this function line up with the actual settings in the renter.
   232  // Perhaps even make it so it uses the renter's actual contracts if it has any.
   233  func (r *Renter) PriceEstimation() modules.RenterPriceEstimation {
   234  	id := r.mu.RLock()
   235  	lastEstimation := r.lastEstimation
   236  	r.mu.RUnlock(id)
   237  	if !reflect.DeepEqual(lastEstimation, modules.RenterPriceEstimation{}) {
   238  		return lastEstimation
   239  	}
   240  
   241  	// Grab hosts to perform the estimation.
   242  	hosts, err := r.hostDB.RandomHosts(priceEstimationScope, nil)
   243  	if err != nil {
   244  		return modules.RenterPriceEstimation{}
   245  	}
   246  
   247  	// Check if there are zero hosts, which means no estimation can be made.
   248  	if len(hosts) == 0 {
   249  		return modules.RenterPriceEstimation{}
   250  	}
   251  
   252  	// Add up the costs for each host.
   253  	var totalContractCost types.Currency
   254  	var totalDownloadCost types.Currency
   255  	var totalStorageCost types.Currency
   256  	var totalUploadCost types.Currency
   257  	for _, host := range hosts {
   258  		totalContractCost = totalContractCost.Add(host.ContractPrice)
   259  		totalDownloadCost = totalDownloadCost.Add(host.DownloadBandwidthPrice)
   260  		totalStorageCost = totalStorageCost.Add(host.StoragePrice)
   261  		totalUploadCost = totalUploadCost.Add(host.UploadBandwidthPrice)
   262  	}
   263  
   264  	// Convert values to being human-scale.
   265  	totalDownloadCost = totalDownloadCost.Mul(modules.BytesPerTerabyte)
   266  	totalStorageCost = totalStorageCost.Mul(modules.BlockBytesPerMonthTerabyte)
   267  	totalUploadCost = totalUploadCost.Mul(modules.BytesPerTerabyte)
   268  
   269  	// Factor in redundancy.
   270  	totalStorageCost = totalStorageCost.Mul64(3) // TODO: follow file settings?
   271  	totalUploadCost = totalUploadCost.Mul64(3)   // TODO: follow file settings?
   272  
   273  	// Perform averages.
   274  	totalContractCost = totalContractCost.Div64(uint64(len(hosts)))
   275  	totalDownloadCost = totalDownloadCost.Div64(uint64(len(hosts)))
   276  	totalStorageCost = totalStorageCost.Div64(uint64(len(hosts)))
   277  	totalUploadCost = totalUploadCost.Div64(uint64(len(hosts)))
   278  
   279  	// Take the average of the host set to estimate the overall cost of the
   280  	// contract forming.
   281  	totalContractCost = totalContractCost.Mul64(uint64(priceEstimationScope))
   282  
   283  	// Add the cost of paying the transaction fees for the first contract.
   284  	_, feePerByte := r.tpool.FeeEstimation()
   285  	totalContractCost = totalContractCost.Add(feePerByte.Mul64(1000).Mul64(uint64(priceEstimationScope)))
   286  
   287  	est := modules.RenterPriceEstimation{
   288  		FormContracts:        totalContractCost,
   289  		DownloadTerabyte:     totalDownloadCost,
   290  		StorageTerabyteMonth: totalStorageCost,
   291  		UploadTerabyte:       totalUploadCost,
   292  	}
   293  
   294  	id = r.mu.Lock()
   295  	r.lastEstimation = est
   296  	r.mu.Unlock(id)
   297  
   298  	return est
   299  }
   300  
   301  // setBandwidthLimits will change the bandwidth limits of the renter based on
   302  // the persist values for the bandwidth.
   303  func (r *Renter) setBandwidthLimits(downloadSpeed int64, uploadSpeed int64) error {
   304  	// Input validation.
   305  	if downloadSpeed < 0 || uploadSpeed < 0 {
   306  		return errors.New("download/upload rate limit can't be below 0")
   307  	}
   308  
   309  	// Check for sentinel "no limits" value.
   310  	if downloadSpeed == 0 && uploadSpeed == 0 {
   311  		r.hostContractor.SetRateLimits(0, 0, 0)
   312  	} else {
   313  		// Set the rate limits according to the provided values.
   314  		r.hostContractor.SetRateLimits(downloadSpeed, uploadSpeed, 4*4096)
   315  	}
   316  	return nil
   317  }
   318  
   319  // SetSettings will update the settings for the renter.
   320  //
   321  // NOTE: This function can't be atomic. Typically we try to have user requests
   322  // be atomic, so that either everything changes or nothing changes, but since
   323  // these changes happen progressively, it's possible for some of the settings
   324  // (like the allowance) to succeed, but then if the bandwidth limits for example
   325  // are bad, then the allowance will update but the bandwidth will not update.
   326  func (r *Renter) SetSettings(s modules.RenterSettings) error {
   327  	// Early input validation.
   328  	if s.MaxDownloadSpeed < 0 || s.MaxUploadSpeed < 0 {
   329  		return errors.New("bandwidth limits cannot be negative")
   330  	}
   331  	if s.StreamCacheSize <= 0 {
   332  		return errors.New("stream cache size needs to be 1 or larger")
   333  	}
   334  
   335  	// Set allowance.
   336  	err := r.hostContractor.SetAllowance(s.Allowance)
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	// Set the bandwidth limits.
   342  	err = r.setBandwidthLimits(s.MaxDownloadSpeed, s.MaxUploadSpeed)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	r.persist.MaxDownloadSpeed = s.MaxDownloadSpeed
   347  	r.persist.MaxUploadSpeed = s.MaxUploadSpeed
   348  
   349  	// Set StreamingCacheSize
   350  	err = r.staticStreamCache.SetStreamingCacheSize(s.StreamCacheSize)
   351  	if err != nil {
   352  		return err
   353  	}
   354  	r.persist.StreamCacheSize = s.StreamCacheSize
   355  
   356  	// Save the changes.
   357  	err = r.saveSync()
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	// Update the worker pool so that the changes are immediately apparent to
   363  	// users.
   364  	r.managedUpdateWorkerPool()
   365  	return nil
   366  }
   367  
   368  // ActiveHosts returns an array of hostDB's active hosts
   369  func (r *Renter) ActiveHosts() []modules.HostDBEntry { return r.hostDB.ActiveHosts() }
   370  
   371  // AllHosts returns an array of all hosts
   372  func (r *Renter) AllHosts() []modules.HostDBEntry { return r.hostDB.AllHosts() }
   373  
   374  // Host returns the host associated with the given public key
   375  func (r *Renter) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) { return r.hostDB.Host(spk) }
   376  
   377  // InitialScanComplete returns a boolean indicating if the initial scan of the
   378  // hostdb is completed.
   379  func (r *Renter) InitialScanComplete() (bool, error) { return r.hostDB.InitialScanComplete() }
   380  
   381  // ScoreBreakdown returns the score breakdown
   382  func (r *Renter) ScoreBreakdown(e modules.HostDBEntry) modules.HostScoreBreakdown {
   383  	return r.hostDB.ScoreBreakdown(e)
   384  }
   385  
   386  // EstimateHostScore returns the estimated host score
   387  func (r *Renter) EstimateHostScore(e modules.HostDBEntry) modules.HostScoreBreakdown {
   388  	return r.hostDB.EstimateHostScore(e)
   389  }
   390  
   391  // Contracts returns an array of host contractor's staticContracts
   392  func (r *Renter) Contracts() []modules.RenterContract { return r.hostContractor.Contracts() }
   393  
   394  // OldContracts returns an array of host contractor's oldContracts
   395  func (r *Renter) OldContracts() []modules.RenterContract {
   396  	return r.hostContractor.OldContracts()
   397  }
   398  
   399  // CurrentPeriod returns the host contractor's current period
   400  func (r *Renter) CurrentPeriod() types.BlockHeight { return r.hostContractor.CurrentPeriod() }
   401  
   402  // ContractUtility returns the utility field for a given contract, along
   403  // with a bool indicating if it exists.
   404  func (r *Renter) ContractUtility(pk types.SiaPublicKey) (modules.ContractUtility, bool) {
   405  	return r.hostContractor.ContractUtility(pk)
   406  }
   407  
   408  // PeriodSpending returns the host contractor's period spending
   409  func (r *Renter) PeriodSpending() modules.ContractorSpending { return r.hostContractor.PeriodSpending() }
   410  
   411  // Settings returns the host contractor's allowance
   412  func (r *Renter) Settings() modules.RenterSettings {
   413  	download, upload, _ := r.hostContractor.RateLimits()
   414  	return modules.RenterSettings{
   415  		Allowance:        r.hostContractor.Allowance(),
   416  		MaxDownloadSpeed: download,
   417  		MaxUploadSpeed:   upload,
   418  		StreamCacheSize:  r.staticStreamCache.cacheSize,
   419  	}
   420  }
   421  
   422  // ProcessConsensusChange returns the process consensus change
   423  func (r *Renter) ProcessConsensusChange(cc modules.ConsensusChange) {
   424  	id := r.mu.Lock()
   425  	r.lastEstimation = modules.RenterPriceEstimation{}
   426  	r.mu.Unlock(id)
   427  }
   428  
   429  // validateSiapath checks that a Siapath is a legal filename.
   430  // ../ is disallowed to prevent directory traversal, and paths must not begin
   431  // with / or be empty.
   432  func validateSiapath(siapath string) error {
   433  	if siapath == "" {
   434  		return ErrEmptyFilename
   435  	}
   436  	if siapath == ".." {
   437  		return errors.New("siapath cannot be '..'")
   438  	}
   439  	if siapath == "." {
   440  		return errors.New("siapath cannot be '.'")
   441  	}
   442  	// check prefix
   443  	if strings.HasPrefix(siapath, "/") {
   444  		return errors.New("siapath cannot begin with /")
   445  	}
   446  	if strings.HasPrefix(siapath, "../") {
   447  		return errors.New("siapath cannot begin with ../")
   448  	}
   449  	if strings.HasPrefix(siapath, "./") {
   450  		return errors.New("siapath connot begin with ./")
   451  	}
   452  	for _, pathElem := range strings.Split(siapath, "/") {
   453  		if pathElem == "." || pathElem == ".." {
   454  			return errors.New("siapath cannot contain . or .. elements")
   455  		}
   456  	}
   457  	return nil
   458  }
   459  
   460  // Enforce that Renter satisfies the modules.Renter interface.
   461  var _ modules.Renter = (*Renter)(nil)
   462  
   463  // NewCustomRenter initializes a renter and returns it.
   464  func NewCustomRenter(g modules.Gateway, cs modules.ConsensusSet, tpool modules.TransactionPool, hdb hostDB, hc hostContractor, persistDir string, deps modules.Dependencies) (*Renter, error) {
   465  	if g == nil {
   466  		return nil, errNilGateway
   467  	}
   468  	if cs == nil {
   469  		return nil, errNilCS
   470  	}
   471  	if tpool == nil {
   472  		return nil, errNilTpool
   473  	}
   474  	if hc == nil {
   475  		return nil, errNilContractor
   476  	}
   477  	if hdb == nil && build.Release != "testing" {
   478  		return nil, errNilHdb
   479  	}
   480  
   481  	r := &Renter{
   482  		files: make(map[string]*file),
   483  
   484  		// Making newDownloads a buffered channel means that most of the time, a
   485  		// new download will trigger an unnecessary extra iteration of the
   486  		// download heap loop, searching for a chunk that's not there. This is
   487  		// preferable to the alternative, where in rare cases the download heap
   488  		// will miss work altogether.
   489  		newDownloads: make(chan struct{}, 1),
   490  		downloadHeap: new(downloadChunkHeap),
   491  
   492  		uploadHeap: uploadHeap{
   493  			activeChunks: make(map[uploadChunkID]struct{}),
   494  			newUploads:   make(chan struct{}, 1),
   495  		},
   496  
   497  		workerPool: make(map[types.FileContractID]*worker),
   498  
   499  		cs:             cs,
   500  		deps:           deps,
   501  		g:              g,
   502  		hostDB:         hdb,
   503  		hostContractor: hc,
   504  		persistDir:     persistDir,
   505  		mu:             siasync.New(modules.SafeMutexDelay, 1),
   506  		tpool:          tpool,
   507  	}
   508  	r.memoryManager = newMemoryManager(defaultMemory, r.tg.StopChan())
   509  
   510  	// Load all saved data.
   511  	if err := r.initPersist(); err != nil {
   512  		return nil, err
   513  	}
   514  
   515  	// Set the bandwidth limits, since the contractor doesn't persist them.
   516  	//
   517  	// TODO: Reconsider the way that the bandwidth limits are allocated to the
   518  	// renter module, because really it seems they only impact the contractor.
   519  	// The renter itself doesn't actually do any uploading or downloading.
   520  	err := r.setBandwidthLimits(r.persist.MaxDownloadSpeed, r.persist.MaxUploadSpeed)
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  
   525  	// Initialize the streaming cache.
   526  	r.staticStreamCache = newStreamCache(r.persist.StreamCacheSize)
   527  
   528  	// Subscribe to the consensus set.
   529  	err = cs.ConsensusSetSubscribe(r, modules.ConsensusChangeRecent, r.tg.StopChan())
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	// Spin up the workers for the work pool.
   535  	r.managedUpdateWorkerPool()
   536  	go r.threadedDownloadLoop()
   537  	go r.threadedUploadLoop()
   538  
   539  	// Kill workers on shutdown.
   540  	r.tg.OnStop(func() error {
   541  		id := r.mu.RLock()
   542  		for _, worker := range r.workerPool {
   543  			close(worker.killChan)
   544  		}
   545  		r.mu.RUnlock(id)
   546  		return nil
   547  	})
   548  
   549  	return r, nil
   550  }
   551  
   552  // New returns an initialized renter.
   553  func New(g modules.Gateway, cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, persistDir string) (*Renter, error) {
   554  	hdb, err := hostdb.New(g, cs, persistDir)
   555  	if err != nil {
   556  		return nil, err
   557  	}
   558  	hc, err := contractor.New(cs, wallet, tpool, hdb, persistDir)
   559  	if err != nil {
   560  		return nil, err
   561  	}
   562  
   563  	return NewCustomRenter(g, cs, tpool, hdb, hc, persistDir, modules.ProdDependencies)
   564  }