gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/downloader.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "sync" 6 7 "gitlab.com/SiaPrime/SiaPrime/build" 8 "gitlab.com/SiaPrime/SiaPrime/crypto" 9 "gitlab.com/SiaPrime/SiaPrime/modules" 10 "gitlab.com/SiaPrime/SiaPrime/modules/renter/proto" 11 "gitlab.com/SiaPrime/SiaPrime/types" 12 ) 13 14 var errInvalidDownloader = errors.New("downloader has been invalidated because its contract is being renewed") 15 16 // An Downloader retrieves sectors from with a host. It requests one sector at 17 // a time, and revises the file contract to transfer money to the host 18 // proportional to the data retrieved. 19 type Downloader interface { 20 // Download requests the specified sector data. 21 Download(root crypto.Hash, offset, length uint32) ([]byte, error) 22 23 // Close terminates the connection to the host. 24 Close() error 25 } 26 27 // A hostDownloader retrieves sectors by calling the download RPC on a host. 28 // It implements the Downloader interface. hostDownloaders are safe for use by 29 // multiple goroutines. 30 type hostDownloader struct { 31 clients int // safe to Close when 0 32 contractID types.FileContractID 33 contractor *Contractor 34 downloader *proto.Downloader 35 hostSettings modules.HostExternalSettings 36 invalid bool // true if invalidate has been called 37 speed uint64 // Bytes per second. 38 mu sync.Mutex 39 } 40 41 // invalidate sets the invalid flag and closes the underlying 42 // proto.Downloader. Once invalidate returns, the hostDownloader is guaranteed 43 // to not further revise its contract. This is used during contract renewal to 44 // prevent a Downloader from revising a contract mid-renewal. 45 func (hd *hostDownloader) invalidate() { 46 hd.mu.Lock() 47 defer hd.mu.Unlock() 48 if !hd.invalid { 49 hd.downloader.Close() 50 hd.invalid = true 51 } 52 hd.contractor.mu.Lock() 53 delete(hd.contractor.downloaders, hd.contractID) 54 hd.contractor.mu.Unlock() 55 } 56 57 // Close cleanly terminates the download loop with the host and closes the 58 // connection. 59 func (hd *hostDownloader) Close() error { 60 hd.mu.Lock() 61 defer hd.mu.Unlock() 62 hd.clients-- 63 // Close is a no-op if invalidate has been called, or if there are other 64 // clients still using the hostDownloader. 65 if hd.invalid || hd.clients > 0 { 66 return nil 67 } 68 hd.invalid = true 69 hd.contractor.mu.Lock() 70 delete(hd.contractor.downloaders, hd.contractID) 71 hd.contractor.mu.Unlock() 72 return hd.downloader.Close() 73 } 74 75 // HostSettings returns the settings of the host that the downloader connects 76 // to. 77 func (hd *hostDownloader) HostSettings() modules.HostExternalSettings { 78 hd.mu.Lock() 79 defer hd.mu.Unlock() 80 return hd.hostSettings 81 } 82 83 // Download retrieves the requested sector data and revises the underlying 84 // contract to pay the host proportionally to the data retrieved. 85 func (hd *hostDownloader) Download(root crypto.Hash, offset, length uint32) ([]byte, error) { 86 hd.mu.Lock() 87 defer hd.mu.Unlock() 88 if hd.invalid { 89 return nil, errInvalidDownloader 90 } 91 _, data, err := hd.downloader.Download(root, offset, length) 92 if err != nil { 93 return nil, err 94 } 95 return data, nil 96 } 97 98 // Downloader returns a Downloader object that can be used to download sectors 99 // from a host. 100 func (c *Contractor) Downloader(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Downloader, err error) { 101 c.mu.RLock() 102 id, gotID := c.pubKeysToContractID[pk.String()] 103 cachedDownloader, haveDownloader := c.downloaders[id] 104 cachedSession, haveSession := c.sessions[id] 105 height := c.blockHeight 106 renewing := c.renewing[id] 107 c.mu.RUnlock() 108 if !gotID { 109 return nil, errors.New("failed to get filecontract id from key") 110 } 111 if renewing { 112 return nil, errors.New("currently renewing that contract") 113 } else if haveDownloader { 114 // increment number of clients and return 115 cachedDownloader.mu.Lock() 116 cachedDownloader.clients++ 117 cachedDownloader.mu.Unlock() 118 return cachedDownloader, nil 119 } else if haveSession { 120 cachedSession.mu.Lock() 121 cachedSession.clients++ 122 cachedSession.mu.Unlock() 123 return cachedSession, nil 124 } 125 126 // Fetch the contract and host. 127 contract, haveContract := c.staticContracts.View(id) 128 if !haveContract { 129 return nil, errors.New("no record of that contract") 130 } 131 host, haveHost := c.hdb.Host(contract.HostPublicKey) 132 if height > contract.EndHeight { 133 return nil, errors.New("contract has already ended") 134 } else if !haveHost { 135 return nil, errors.New("no record of that host") 136 } else if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 { 137 return nil, errTooExpensive 138 } 139 140 // If host is >= 1.4.0, use the new renter-host protocol. 141 if build.VersionCmp(host.Version, "1.4.0") >= 0 { 142 return c.Session(pk, cancel) 143 } 144 145 // create downloader 146 d, err := c.staticContracts.NewDownloader(host, contract.ID, height, c.hdb, cancel) 147 if err != nil { 148 return nil, err 149 } 150 151 // cache downloader 152 hd := &hostDownloader{ 153 clients: 1, 154 contractor: c, 155 downloader: d, 156 contractID: id, 157 } 158 c.mu.Lock() 159 c.downloaders[contract.ID] = hd 160 c.mu.Unlock() 161 162 return hd, nil 163 }