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 }