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