github.com/NebulousLabs/Sia@v1.3.7/modules/renter/contractor/downloader.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "sync" 6 7 "github.com/NebulousLabs/Sia/crypto" 8 "github.com/NebulousLabs/Sia/modules" 9 "github.com/NebulousLabs/Sia/modules/renter/proto" 10 "github.com/NebulousLabs/Sia/types" 11 ) 12 13 var errInvalidDownloader = errors.New("downloader has been invalidated because its contract is being renewed") 14 15 // An Downloader retrieves sectors from with a host. It requests one sector at 16 // a time, and revises the file contract to transfer money to the host 17 // proportional to the data retrieved. 18 type Downloader interface { 19 // Sector retrieves the sector with the specified Merkle root, and revises 20 // the underlying contract to pay the host proportionally to the data 21 // retrieve. 22 Sector(root crypto.Hash) ([]byte, error) 23 24 // Close terminates the connection to the host. 25 Close() error 26 } 27 28 // A hostDownloader retrieves sectors by calling the download RPC on a host. 29 // It implements the Downloader interface. hostDownloaders are safe for use by 30 // multiple goroutines. 31 type hostDownloader struct { 32 clients int // safe to Close when 0 33 contractID types.FileContractID 34 contractor *Contractor 35 downloader *proto.Downloader 36 hostSettings modules.HostExternalSettings 37 invalid bool // true if invalidate has been called 38 speed uint64 // Bytes per second. 39 mu sync.Mutex 40 } 41 42 // invalidate sets the invalid flag and closes the underlying 43 // proto.Downloader. Once invalidate returns, the hostDownloader is guaranteed 44 // to not further revise its contract. This is used during contract renewal to 45 // prevent a Downloader from revising a contract mid-renewal. 46 func (hd *hostDownloader) invalidate() { 47 hd.mu.Lock() 48 defer hd.mu.Unlock() 49 if !hd.invalid { 50 hd.downloader.Close() 51 hd.invalid = true 52 } 53 hd.contractor.mu.Lock() 54 delete(hd.contractor.downloaders, hd.contractID) 55 delete(hd.contractor.revising, hd.contractID) 56 hd.contractor.mu.Unlock() 57 } 58 59 // Close cleanly terminates the download loop with the host and closes the 60 // connection. 61 func (hd *hostDownloader) Close() error { 62 hd.mu.Lock() 63 defer hd.mu.Unlock() 64 hd.clients-- 65 // Close is a no-op if invalidate has been called, or if there are other 66 // clients still using the hostDownloader. 67 if hd.invalid || hd.clients > 0 { 68 return nil 69 } 70 hd.invalid = true 71 hd.contractor.mu.Lock() 72 delete(hd.contractor.downloaders, hd.contractID) 73 delete(hd.contractor.revising, hd.contractID) 74 hd.contractor.mu.Unlock() 75 return hd.downloader.Close() 76 } 77 78 // HostSettings returns the settings of the host that the downloader connects 79 // to. 80 func (hd *hostDownloader) HostSettings() modules.HostExternalSettings { 81 hd.mu.Lock() 82 defer hd.mu.Unlock() 83 return hd.hostSettings 84 } 85 86 // Sector retrieves the sector with the specified Merkle root, and revises 87 // the underlying contract to pay the host proportionally to the data 88 // retrieve. 89 func (hd *hostDownloader) Sector(root crypto.Hash) ([]byte, error) { 90 hd.mu.Lock() 91 defer hd.mu.Unlock() 92 if hd.invalid { 93 return nil, errInvalidDownloader 94 } 95 96 // Download the sector. 97 _, sector, err := hd.downloader.Sector(root) 98 if err != nil { 99 return nil, err 100 } 101 return sector, nil 102 } 103 104 // Downloader returns a Downloader object that can be used to download sectors 105 // from a host. 106 func (c *Contractor) Downloader(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Downloader, err error) { 107 c.mu.RLock() 108 id, gotID := c.pubKeysToContractID[string(pk.Key)] 109 cachedDownloader, haveDownloader := c.downloaders[id] 110 height := c.blockHeight 111 renewing := c.renewing[id] 112 c.mu.RUnlock() 113 if !gotID { 114 return nil, errors.New("failed to get filecontract id from key") 115 } 116 if renewing { 117 return nil, errors.New("currently renewing that contract") 118 } else if haveDownloader { 119 // increment number of clients and return 120 cachedDownloader.mu.Lock() 121 cachedDownloader.clients++ 122 cachedDownloader.mu.Unlock() 123 return cachedDownloader, 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 // Acquire the revising lock for the contract, which excludes other threads 141 // from interacting with the contract. 142 // 143 // TODO: Because we have another layer of contract safety via the 144 // contractset, do we need the revising lock anymore? 145 c.mu.Lock() 146 alreadyRevising := c.revising[contract.ID] 147 if alreadyRevising { 148 c.mu.Unlock() 149 return nil, errors.New("already revising that contract") 150 } 151 c.revising[contract.ID] = true 152 c.mu.Unlock() 153 // release lock early if function returns an error 154 defer func() { 155 if err != nil { 156 c.mu.Lock() 157 delete(c.revising, contract.ID) 158 c.mu.Unlock() 159 } 160 }() 161 162 // create downloader 163 d, err := c.staticContracts.NewDownloader(host, contract.ID, c.hdb, cancel) 164 if err != nil { 165 return nil, err 166 } 167 168 // cache downloader 169 hd := &hostDownloader{ 170 clients: 1, 171 contractor: c, 172 downloader: d, 173 contractID: id, 174 } 175 c.mu.Lock() 176 c.downloaders[contract.ID] = hd 177 c.mu.Unlock() 178 179 return hd, nil 180 }