gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/session.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "sync" 6 7 "gitlab.com/SiaPrime/SiaPrime/crypto" 8 "gitlab.com/SiaPrime/SiaPrime/modules" 9 "gitlab.com/SiaPrime/SiaPrime/modules/renter/proto" 10 "gitlab.com/SiaPrime/SiaPrime/types" 11 ) 12 13 var errInvalidSession = errors.New("session has been invalidated because its contract is being renewed") 14 15 // A Session modifies a Contract by communicating with a host. It uses the 16 // renter-host protocol to send modification requests to the host. Among other 17 // things, Sessions are the means by which the renter transfers file data to 18 // and from hosts. 19 type Session interface { 20 // Address returns the address of the host. 21 Address() modules.NetAddress 22 23 // Close terminates the connection to the host. 24 Close() error 25 26 // ContractID returns the FileContractID of the contract. 27 ContractID() types.FileContractID 28 29 // Download requests the specified sector data. 30 Download(root crypto.Hash, offset, length uint32) ([]byte, error) 31 32 // DownloadIndex requests data from the sector with the specified index 33 // within the contract. 34 DownloadIndex(index uint64, offset, length uint32) ([]byte, error) 35 36 // EndHeight returns the height at which the contract ends. 37 EndHeight() types.BlockHeight 38 39 // Upload revises the underlying contract to store the new data. It 40 // returns the Merkle root of the data. 41 Upload(data []byte) (crypto.Hash, error) 42 43 // Replace replaces the sector at the specified index with data. The old 44 // sector is swapped to the end of the contract data, and is deleted if the 45 // trim flag is set. 46 Replace(data []byte, sectorIndex uint64, trim bool) (crypto.Hash, error) 47 } 48 49 // A hostSession modifies a Contract via the renter-host RPC loop. It 50 // implements the Session interface. hostSessions are safe for use by multiple 51 // goroutines. 52 type hostSession struct { 53 clients int // safe to Close when 0 54 contractor *Contractor 55 session *proto.Session 56 endHeight types.BlockHeight 57 id types.FileContractID 58 invalid bool // true if invalidate has been called 59 netAddress modules.NetAddress 60 61 mu sync.Mutex 62 } 63 64 // invalidate sets the invalid flag and closes the underlying proto.Session. 65 // Once invalidate returns, the hostSession is guaranteed to not further revise 66 // its contract. This is used during contract renewal to prevent an Session 67 // from revising a contract mid-renewal. 68 func (hs *hostSession) invalidate() { 69 hs.mu.Lock() 70 defer hs.mu.Unlock() 71 if hs.invalid { 72 return 73 } 74 hs.session.Close() 75 hs.contractor.mu.Lock() 76 delete(hs.contractor.sessions, hs.id) 77 hs.contractor.mu.Unlock() 78 hs.invalid = true 79 } 80 81 // Address returns the NetAddress of the host. 82 func (hs *hostSession) Address() modules.NetAddress { return hs.netAddress } 83 84 // Close cleanly terminates the revision loop with the host and closes the 85 // connection. 86 func (hs *hostSession) Close() error { 87 hs.mu.Lock() 88 defer hs.mu.Unlock() 89 hs.clients-- 90 // Close is a no-op if invalidate has been called, or if there are other 91 // clients still using the hostSession. 92 if hs.invalid || hs.clients > 0 { 93 return nil 94 } 95 hs.invalid = true 96 hs.contractor.mu.Lock() 97 delete(hs.contractor.sessions, hs.id) 98 hs.contractor.mu.Unlock() 99 return hs.session.Close() 100 } 101 102 // ContractID returns the ID of the contract being revised. 103 func (hs *hostSession) ContractID() types.FileContractID { return hs.id } 104 105 // Download retrieves the sector with the specified Merkle root, and revises 106 // the underlying contract to pay the host proportionally to the data 107 // retrieved. 108 func (hs *hostSession) Download(root crypto.Hash, offset, length uint32) ([]byte, error) { 109 hs.mu.Lock() 110 defer hs.mu.Unlock() 111 if hs.invalid { 112 return nil, errInvalidSession 113 } 114 115 // Download the data. 116 _, data, err := hs.session.ReadSection(root, offset, length) 117 if err != nil { 118 return nil, err 119 } 120 return data, nil 121 } 122 123 // DownloadIndex retrieves the sector with the specified index. 124 func (hs *hostSession) DownloadIndex(index uint64, offset, length uint32) ([]byte, error) { 125 hs.mu.Lock() 126 defer hs.mu.Unlock() 127 if hs.invalid { 128 return nil, errInvalidSession 129 } 130 131 // Retrieve the Merkle root for the index. 132 _, roots, err := hs.session.SectorRoots(modules.LoopSectorRootsRequest{ 133 RootOffset: index, 134 NumRoots: 1, 135 }) 136 if err != nil { 137 return nil, err 138 } 139 140 // Download the data. 141 _, data, err := hs.session.ReadSection(roots[0], offset, length) 142 if err != nil { 143 return nil, err 144 } 145 return data, nil 146 } 147 148 // EndHeight returns the height at which the host is no longer obligated to 149 // store the file. 150 func (hs *hostSession) EndHeight() types.BlockHeight { return hs.endHeight } 151 152 // Upload negotiates a revision that adds a sector to a file contract. 153 func (hs *hostSession) Upload(data []byte) (crypto.Hash, error) { 154 hs.mu.Lock() 155 defer hs.mu.Unlock() 156 if hs.invalid { 157 return crypto.Hash{}, errInvalidSession 158 } 159 160 // Perform the upload. 161 _, sectorRoot, err := hs.session.Append(data) 162 if err != nil { 163 return crypto.Hash{}, err 164 } 165 return sectorRoot, nil 166 } 167 168 // Replace replaces the sector at the specified index with data. 169 func (hs *hostSession) Replace(data []byte, sectorIndex uint64, trim bool) (crypto.Hash, error) { 170 hs.mu.Lock() 171 defer hs.mu.Unlock() 172 if hs.invalid { 173 return crypto.Hash{}, errInvalidSession 174 } 175 176 _, sectorRoot, err := hs.session.Replace(data, sectorIndex, trim) 177 if err != nil { 178 return crypto.Hash{}, err 179 } 180 return sectorRoot, nil 181 } 182 183 // Session returns a Session object that can be used to upload, modify, and 184 // delete sectors on a host. 185 func (c *Contractor) Session(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Session, err error) { 186 c.mu.RLock() 187 id, gotID := c.pubKeysToContractID[pk.String()] 188 cachedSession, haveSession := c.sessions[id] 189 height := c.blockHeight 190 renewing := c.renewing[id] 191 c.mu.RUnlock() 192 if !gotID { 193 return nil, errors.New("failed to get filecontract id from key") 194 } 195 if renewing { 196 // Cannot use the session if the contract is being renewed. 197 return nil, errors.New("currently renewing that contract") 198 } else if haveSession { 199 // This session already exists. Mark that there are now two routines 200 // using the session, and then return the session that already exists. 201 cachedSession.mu.Lock() 202 cachedSession.clients++ 203 cachedSession.mu.Unlock() 204 return cachedSession, nil 205 } 206 207 // Check that the contract and host are both available, and run some brief 208 // sanity checks to see that the host is not swindling us. 209 contract, haveContract := c.staticContracts.View(id) 210 if !haveContract { 211 return nil, errors.New("no record of that contract") 212 } 213 host, haveHost := c.hdb.Host(contract.HostPublicKey) 214 if height > contract.EndHeight { 215 return nil, errors.New("contract has already ended") 216 } else if !haveHost { 217 return nil, errors.New("no record of that host") 218 } else if host.Filtered { 219 return nil, errors.New("host is blacklisted") 220 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 221 return nil, errTooExpensive 222 } else if host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 { 223 return nil, errTooExpensive 224 } 225 226 // Create the session. 227 s, err := c.staticContracts.NewSession(host, id, height, c.hdb, cancel) 228 if err != nil { 229 return nil, err 230 } 231 232 // cache session 233 hs := &hostSession{ 234 clients: 1, 235 contractor: c, 236 session: s, 237 endHeight: contract.EndHeight, 238 id: id, 239 netAddress: host.NetAddress, 240 } 241 c.mu.Lock() 242 c.sessions[contract.ID] = hs 243 c.mu.Unlock() 244 245 return hs, nil 246 }