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