gitlab.com/jokerrs1/Sia@v1.3.2/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 import ( 19 "errors" 20 "reflect" 21 "strings" 22 "sync" 23 24 "github.com/NebulousLabs/Sia/build" 25 "github.com/NebulousLabs/Sia/modules" 26 "github.com/NebulousLabs/Sia/modules/renter/contractor" 27 "github.com/NebulousLabs/Sia/modules/renter/hostdb" 28 "github.com/NebulousLabs/Sia/persist" 29 siasync "github.com/NebulousLabs/Sia/sync" 30 "github.com/NebulousLabs/Sia/types" 31 "github.com/NebulousLabs/ratelimit" 32 33 "github.com/NebulousLabs/threadgroup" 34 ) 35 36 var ( 37 errNilContractor = errors.New("cannot create renter with nil contractor") 38 errNilCS = errors.New("cannot create renter with nil consensus set") 39 errNilGateway = errors.New("cannot create hostdb with nil gateway") 40 errNilHdb = errors.New("cannot create renter with nil hostdb") 41 errNilTpool = errors.New("cannot create renter with nil transaction pool") 42 ) 43 44 var ( 45 // priceEstimationScope is the number of hosts that get queried by the 46 // renter when providing price estimates. Especially for the 'Standard' 47 // variable, there should be congruence with the number of contracts being 48 // used in the renter allowance. 49 priceEstimationScope = build.Select(build.Var{ 50 Standard: int(50), 51 Dev: int(12), 52 Testing: int(4), 53 }).(int) 54 ) 55 56 // A hostDB is a database of hosts that the renter can use for figuring out who 57 // to upload to, and download from. 58 type hostDB interface { 59 // ActiveHosts returns the list of hosts that are actively being selected 60 // from. 61 ActiveHosts() []modules.HostDBEntry 62 63 // AllHosts returns the full list of hosts known to the hostdb, sorted in 64 // order of preference. 65 AllHosts() []modules.HostDBEntry 66 67 // AverageContractPrice returns the average contract price of a host. 68 AverageContractPrice() types.Currency 69 70 // Close closes the hostdb. 71 Close() error 72 73 // Host returns the HostDBEntry for a given host. 74 Host(types.SiaPublicKey) (modules.HostDBEntry, bool) 75 76 // RandomHosts returns a set of random hosts, weighted by their estimated 77 // usefulness / attractiveness to the renter. RandomHosts will not return 78 // any offline or inactive hosts. 79 RandomHosts(int, []types.SiaPublicKey) []modules.HostDBEntry 80 81 // ScoreBreakdown returns a detailed explanation of the various properties 82 // of the host. 83 ScoreBreakdown(modules.HostDBEntry) modules.HostScoreBreakdown 84 85 // EstimateHostScore returns the estimated score breakdown of a host with the 86 // provided settings. 87 EstimateHostScore(modules.HostDBEntry) modules.HostScoreBreakdown 88 } 89 90 // A hostContractor negotiates, revises, renews, and provides access to file 91 // contracts. 92 type hostContractor interface { 93 // SetAllowance sets the amount of money the contractor is allowed to 94 // spend on contracts over a given time period, divided among the number 95 // of hosts specified. Note that contractor can start forming contracts as 96 // soon as SetAllowance is called; that is, it may block. 97 SetAllowance(modules.Allowance) error 98 99 // Allowance returns the current allowance 100 Allowance() modules.Allowance 101 102 // Close closes the hostContractor. 103 Close() error 104 105 // Contracts returns the contracts formed by the contractor. 106 Contracts() []modules.RenterContract 107 108 // ContractByID returns the contract associated with the file contract id. 109 ContractByID(types.FileContractID) (modules.RenterContract, bool) 110 111 // ContractUtility returns the utility field for a given contract, along 112 // with a bool indicating if it exists. 113 ContractUtility(types.FileContractID) (modules.ContractUtility, bool) 114 115 // CurrentPeriod returns the height at which the current allowance period 116 // began. 117 CurrentPeriod() types.BlockHeight 118 119 // PeriodSpending returns the amount spent on contracts during the current 120 // billing period. 121 PeriodSpending() modules.ContractorSpending 122 123 // Editor creates an Editor from the specified contract ID, allowing the 124 // insertion, deletion, and modification of sectors. 125 Editor(types.FileContractID, <-chan struct{}) (contractor.Editor, error) 126 127 // IsOffline reports whether the specified host is considered offline. 128 IsOffline(types.FileContractID) bool 129 130 // Downloader creates a Downloader from the specified contract ID, 131 // allowing the retrieval of sectors. 132 Downloader(types.FileContractID, <-chan struct{}) (contractor.Downloader, error) 133 134 // ResolveID returns the most recent renewal of the specified ID. 135 ResolveID(types.FileContractID) types.FileContractID 136 } 137 138 // A trackedFile contains metadata about files being tracked by the Renter. 139 // Tracked files are actively repaired by the Renter. By default, files 140 // uploaded by the user are tracked, and files that are added (via loading a 141 // .sia file) are not. 142 type trackedFile struct { 143 // location of original file on disk 144 RepairPath string 145 } 146 147 // A Renter is responsible for tracking all of the files that a user has 148 // uploaded to Sia, as well as the locations and health of these files. 149 // 150 // TODO: Separate the workerPool to have its own mutex. The workerPool doesn't 151 // interfere with any of the other fields in the renter, should be fine for it 152 // to have a separate mutex, that way operations on the worker pool don't block 153 // operations on other parts of the struct. If we're going to do it that way, 154 // might make sense to split the worker pool off into it's own struct entirely 155 // the same way that we split of the memoryManager entirely. 156 type Renter struct { 157 // File management. 158 // 159 // tracking contains a list of files that the user intends to maintain. By 160 // default, files loaded through sharing are not maintained by the user. 161 files map[string]*file 162 tracking map[string]trackedFile // Map from nickname to metadata. 163 164 // Download management. The heap has a separate mutex because it is always 165 // accessed in isolation. 166 downloadHeapMu sync.Mutex // Used to protect the downloadHeap. 167 downloadHeap *downloadChunkHeap // A heap of priority-sorted chunks to download. 168 newDownloads chan struct{} // Used to notify download loop that new downloads are available. 169 170 // Download history. The history list has its own mutex because it is always 171 // accessed in isolation. 172 // 173 // TODO: Currently the download history doesn't include repair-initiated 174 // downloads, and instead only contains user-initiated downlods. 175 downloadHistory []*download 176 downloadHistoryMu sync.Mutex 177 178 // Upload management. 179 uploadHeap uploadHeap 180 181 // List of workers that can be used for uploading and/or downloading. 182 memoryManager *memoryManager 183 workerPool map[types.FileContractID]*worker 184 185 // Cache the last price estimation result. 186 lastEstimation modules.RenterPriceEstimation 187 188 // Utilities. 189 cs modules.ConsensusSet 190 g modules.Gateway 191 hostContractor hostContractor 192 hostDB hostDB 193 log *persist.Logger 194 persistDir string 195 mu *siasync.RWMutex 196 tg threadgroup.ThreadGroup 197 tpool modules.TransactionPool 198 } 199 200 // Close closes the Renter and its dependencies 201 func (r *Renter) Close() error { 202 r.tg.Stop() 203 r.hostDB.Close() 204 return r.hostContractor.Close() 205 } 206 207 // PriceEstimation estimates the cost in siacoins of performing various storage 208 // and data operations. 209 // 210 // TODO: Make this function line up with the actual settings in the renter. 211 // Perhaps even make it so it uses the renter's actual contracts if it has any. 212 func (r *Renter) PriceEstimation() modules.RenterPriceEstimation { 213 id := r.mu.RLock() 214 lastEstimation := r.lastEstimation 215 r.mu.RUnlock(id) 216 if !reflect.DeepEqual(lastEstimation, modules.RenterPriceEstimation{}) { 217 return lastEstimation 218 } 219 220 // Grab hosts to perform the estimation. 221 hosts := r.hostDB.RandomHosts(priceEstimationScope, nil) 222 223 // Check if there are zero hosts, which means no estimation can be made. 224 if len(hosts) == 0 { 225 return modules.RenterPriceEstimation{} 226 } 227 228 // Add up the costs for each host. 229 var totalContractCost types.Currency 230 var totalDownloadCost types.Currency 231 var totalStorageCost types.Currency 232 var totalUploadCost types.Currency 233 for _, host := range hosts { 234 totalContractCost = totalContractCost.Add(host.ContractPrice) 235 totalDownloadCost = totalDownloadCost.Add(host.DownloadBandwidthPrice) 236 totalStorageCost = totalStorageCost.Add(host.StoragePrice) 237 totalUploadCost = totalUploadCost.Add(host.UploadBandwidthPrice) 238 } 239 240 // Convert values to being human-scale. 241 totalDownloadCost = totalDownloadCost.Mul(modules.BytesPerTerabyte) 242 totalStorageCost = totalStorageCost.Mul(modules.BlockBytesPerMonthTerabyte) 243 totalUploadCost = totalUploadCost.Mul(modules.BytesPerTerabyte) 244 245 // Factor in redundancy. 246 totalStorageCost = totalStorageCost.Mul64(3) // TODO: follow file settings? 247 totalUploadCost = totalUploadCost.Mul64(3) // TODO: follow file settings? 248 249 // Perform averages. 250 totalContractCost = totalContractCost.Div64(uint64(len(hosts))) 251 totalDownloadCost = totalDownloadCost.Div64(uint64(len(hosts))) 252 totalStorageCost = totalStorageCost.Div64(uint64(len(hosts))) 253 totalUploadCost = totalUploadCost.Div64(uint64(len(hosts))) 254 255 // Take the average of the host set to estimate the overall cost of the 256 // contract forming. 257 totalContractCost = totalContractCost.Mul64(uint64(priceEstimationScope)) 258 259 // Add the cost of paying the transaction fees for the first contract. 260 _, feePerByte := r.tpool.FeeEstimation() 261 totalContractCost = totalContractCost.Add(feePerByte.Mul64(1000).Mul64(uint64(priceEstimationScope))) 262 263 est := modules.RenterPriceEstimation{ 264 FormContracts: totalContractCost, 265 DownloadTerabyte: totalDownloadCost, 266 StorageTerabyteMonth: totalStorageCost, 267 UploadTerabyte: totalUploadCost, 268 } 269 270 id = r.mu.Lock() 271 r.lastEstimation = est 272 r.mu.Unlock(id) 273 274 return est 275 } 276 277 // SetSettings will update the settings for the renter. 278 func (r *Renter) SetSettings(s modules.RenterSettings) error { 279 // Set allowance. 280 err := r.hostContractor.SetAllowance(s.Allowance) 281 if err != nil { 282 return err 283 } 284 // Set bandwidth limits. 285 if s.MaxDownloadSpeed < 0 || s.MaxUploadSpeed < 0 { 286 return errors.New("download/upload rate limit can't be below 0") 287 } 288 // Set ratelimit 289 if s.MaxDownloadSpeed == 0 && s.MaxUploadSpeed == 0 { 290 ratelimit.SetLimits(0, 0, 0) 291 } else { 292 ratelimit.SetLimits(s.MaxDownloadSpeed, s.MaxUploadSpeed, 4*4096) 293 } 294 295 r.managedUpdateWorkerPool() 296 return nil 297 } 298 299 // ActiveHosts returns an array of hostDB's active hosts 300 func (r *Renter) ActiveHosts() []modules.HostDBEntry { return r.hostDB.ActiveHosts() } 301 302 // AllHosts returns an array of all hosts 303 func (r *Renter) AllHosts() []modules.HostDBEntry { return r.hostDB.AllHosts() } 304 305 // Host returns the host associated with the given public key 306 func (r *Renter) Host(spk types.SiaPublicKey) (modules.HostDBEntry, bool) { return r.hostDB.Host(spk) } 307 308 // ScoreBreakdown returns the score breakdown 309 func (r *Renter) ScoreBreakdown(e modules.HostDBEntry) modules.HostScoreBreakdown { 310 return r.hostDB.ScoreBreakdown(e) 311 } 312 313 // EstimateHostScore returns the estimated host score 314 func (r *Renter) EstimateHostScore(e modules.HostDBEntry) modules.HostScoreBreakdown { 315 return r.hostDB.EstimateHostScore(e) 316 } 317 318 // Contracts returns an array of host contractor's contracts 319 func (r *Renter) Contracts() []modules.RenterContract { return r.hostContractor.Contracts() } 320 321 // CurrentPeriod returns the host contractor's current period 322 func (r *Renter) CurrentPeriod() types.BlockHeight { return r.hostContractor.CurrentPeriod() } 323 324 // ContractUtility returns the utility field for a given contract, along 325 // with a bool indicating if it exists. 326 func (r *Renter) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) { 327 return r.hostContractor.ContractUtility(id) 328 } 329 330 // PeriodSpending returns the host contractor's period spending 331 func (r *Renter) PeriodSpending() modules.ContractorSpending { return r.hostContractor.PeriodSpending() } 332 333 // Settings returns the host contractor's allowance 334 func (r *Renter) Settings() modules.RenterSettings { 335 return modules.RenterSettings{ 336 Allowance: r.hostContractor.Allowance(), 337 } 338 } 339 340 // ProcessConsensusChange returns the process consensus change 341 func (r *Renter) ProcessConsensusChange(cc modules.ConsensusChange) { 342 id := r.mu.Lock() 343 r.lastEstimation = modules.RenterPriceEstimation{} 344 r.mu.Unlock(id) 345 } 346 347 // validateSiapath checks that a Siapath is a legal filename. 348 // ../ is disallowed to prevent directory traversal, and paths must not begin 349 // with / or be empty. 350 func validateSiapath(siapath string) error { 351 if siapath == "" { 352 return ErrEmptyFilename 353 } 354 if siapath == ".." { 355 return errors.New("siapath cannot be ..") 356 } 357 if siapath == "." { 358 return errors.New("siapath cannot be .") 359 } 360 // check prefix 361 if strings.HasPrefix(siapath, "/") { 362 return errors.New("siapath cannot begin with /") 363 } 364 if strings.HasPrefix(siapath, "../") { 365 return errors.New("siapath cannot begin with ../") 366 } 367 if strings.HasPrefix(siapath, "./") { 368 return errors.New("siapath connot begin with ./") 369 } 370 for _, pathElem := range strings.Split(siapath, "/") { 371 if pathElem == "." || pathElem == ".." { 372 return errors.New("siapath cannot contain . or .. elements") 373 } 374 } 375 return nil 376 } 377 378 // Enforce that Renter satisfies the modules.Renter interface. 379 var _ modules.Renter = (*Renter)(nil) 380 381 // newRenter initializes a renter and returns it. 382 func newRenter(g modules.Gateway, cs modules.ConsensusSet, tpool modules.TransactionPool, hdb hostDB, hc hostContractor, persistDir string) (*Renter, error) { 383 if g == nil { 384 return nil, errNilGateway 385 } 386 if cs == nil { 387 return nil, errNilCS 388 } 389 if tpool == nil { 390 return nil, errNilTpool 391 } 392 if hc == nil { 393 return nil, errNilContractor 394 } 395 if hdb == nil && build.Release != "testing" { 396 return nil, errNilHdb 397 } 398 399 r := &Renter{ 400 files: make(map[string]*file), 401 tracking: make(map[string]trackedFile), 402 403 // Making newDownloads a buffered channel means that most of the time, a 404 // new download will trigger an unnecessary extra iteration of the 405 // download heap loop, searching for a chunk that's not there. This is 406 // preferable to the alternative, where in rare cases the download heap 407 // will miss work altogether. 408 newDownloads: make(chan struct{}, 1), 409 downloadHeap: new(downloadChunkHeap), 410 411 uploadHeap: uploadHeap{ 412 activeChunks: make(map[uploadChunkID]struct{}), 413 newUploads: make(chan struct{}, 1), 414 }, 415 416 workerPool: make(map[types.FileContractID]*worker), 417 418 cs: cs, 419 g: g, 420 hostDB: hdb, 421 hostContractor: hc, 422 persistDir: persistDir, 423 mu: siasync.New(modules.SafeMutexDelay, 1), 424 tpool: tpool, 425 } 426 r.memoryManager = newMemoryManager(defaultMemory, r.tg.StopChan()) 427 428 // Load all saved data. 429 if err := r.initPersist(); err != nil { 430 return nil, err 431 } 432 433 // Subscribe to the consensus set. 434 err := cs.ConsensusSetSubscribe(r, modules.ConsensusChangeRecent, r.tg.StopChan()) 435 if err != nil { 436 return nil, err 437 } 438 439 // Spin up the workers for the work pool. 440 r.managedUpdateWorkerPool() 441 go r.threadedDownloadLoop() 442 go r.threadedUploadLoop() 443 444 // Kill workers on shutdown. 445 r.tg.OnStop(func() error { 446 id := r.mu.RLock() 447 for _, worker := range r.workerPool { 448 close(worker.killChan) 449 } 450 r.mu.RUnlock(id) 451 return nil 452 }) 453 454 return r, nil 455 } 456 457 // New returns an initialized renter. 458 func New(g modules.Gateway, cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, persistDir string) (*Renter, error) { 459 hdb, err := hostdb.New(g, cs, persistDir) 460 if err != nil { 461 return nil, err 462 } 463 hc, err := contractor.New(cs, wallet, tpool, hdb, persistDir) 464 if err != nil { 465 return nil, err 466 } 467 468 return newRenter(g, cs, tpool, hdb, hc, persistDir) 469 }