gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/editor.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 errInvalidEditor = errors.New("editor has been invalidated because its contract is being renewed") 16 17 // An Editor modifies a Contract by communicating with a host. It uses the 18 // contract revision protocol to send modification requests to the host. 19 // Editors are the means by which the renter uploads data to hosts. 20 type Editor interface { 21 // Upload revises the underlying contract to store the new data. It 22 // returns the Merkle root of the data. 23 Upload(data []byte) (root crypto.Hash, err error) 24 25 // Address returns the address of the host. 26 Address() modules.NetAddress 27 28 // ContractID returns the FileContractID of the contract. 29 ContractID() types.FileContractID 30 31 // EndHeight returns the height at which the contract ends. 32 EndHeight() types.BlockHeight 33 34 // Close terminates the connection to the host. 35 Close() error 36 37 // HostSettings returns the host settings that are currently active for the 38 // underlying session. 39 HostSettings() modules.HostExternalSettings 40 } 41 42 // A hostEditor modifies a Contract by calling the revise RPC on a host. It 43 // implements the Editor interface. hostEditors are safe for use by 44 // multiple goroutines. 45 type hostEditor struct { 46 clients int // safe to Close when 0 47 invalid bool // true if invalidate has been called 48 49 // Static Fields 50 staticContractor *Contractor 51 staticEditor *proto.Editor 52 staticEndHeight types.BlockHeight 53 staticID types.FileContractID 54 staticNetAddress modules.NetAddress 55 56 mu sync.Mutex 57 } 58 59 // callInvalidate sets the invalid flag and closes the underlying proto.Editor. 60 // Once callInvalidate returns, the hostEditor is guaranteed to not further 61 // revise its contract. This is used during contract renewal to prevent an 62 // Editor from revising a contract mid-renewal. 63 func (he *hostEditor) callInvalidate() { 64 he.mu.Lock() 65 defer he.mu.Unlock() 66 if !he.invalid { 67 he.staticEditor.Close() 68 he.invalid = true 69 } 70 he.staticContractor.mu.Lock() 71 delete(he.staticContractor.editors, he.staticID) 72 he.staticContractor.mu.Unlock() 73 } 74 75 // Address returns the NetAddress of the host. 76 func (he *hostEditor) Address() modules.NetAddress { return he.staticNetAddress } 77 78 // ContractID returns the id of the contract being revised. 79 func (he *hostEditor) ContractID() types.FileContractID { return he.staticID } 80 81 // EndHeight returns the height at which the host is no longer obligated to 82 // store the file. 83 func (he *hostEditor) EndHeight() types.BlockHeight { return he.staticEndHeight } 84 85 // Close cleanly terminates the revision loop with the host and closes the 86 // connection. 87 func (he *hostEditor) Close() error { 88 he.mu.Lock() 89 defer he.mu.Unlock() 90 he.clients-- 91 // Close is a no-op if invalidate has been called, or if there are other 92 // clients still using the hostEditor. 93 if he.invalid || he.clients > 0 { 94 return nil 95 } 96 he.invalid = true 97 he.staticContractor.mu.Lock() 98 delete(he.staticContractor.editors, he.staticID) 99 he.staticContractor.mu.Unlock() 100 return he.staticEditor.Close() 101 } 102 103 // HostSettings returns the host settings that are currently active for the 104 // underlying session. 105 func (he *hostEditor) HostSettings() modules.HostExternalSettings { 106 return he.staticEditor.HostSettings() 107 } 108 109 // Upload negotiates a revision that adds a sector to a file contract. 110 func (he *hostEditor) Upload(data []byte) (_ crypto.Hash, err error) { 111 he.mu.Lock() 112 defer he.mu.Unlock() 113 if he.invalid { 114 return crypto.Hash{}, errInvalidEditor 115 } 116 117 // Perform the upload. 118 _, sectorRoot, err := he.staticEditor.Upload(data) 119 if err != nil { 120 return crypto.Hash{}, err 121 } 122 return sectorRoot, nil 123 } 124 125 // Editor returns a Editor object that can be used to upload, modify, and 126 // delete sectors on a host. 127 func (c *Contractor) Editor(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Editor, err error) { 128 c.mu.RLock() 129 id, gotID := c.pubKeysToContractID[pk.String()] 130 cachedEditor, haveEditor := c.editors[id] 131 cachedSession, haveSession := c.sessions[id] 132 height := c.blockHeight 133 renewing := c.renewing[id] 134 c.mu.RUnlock() 135 if !gotID { 136 return nil, errors.New("failed to get filecontract id from key") 137 } 138 if renewing { 139 // Cannot use the editor if the contract is being renewed. 140 return nil, ErrContractRenewing 141 } else if haveEditor { 142 // This editor already exists. Mark that there are now two routines 143 // using the editor, and then return the editor that already exists. 144 cachedEditor.mu.Lock() 145 cachedEditor.clients++ 146 cachedEditor.mu.Unlock() 147 return cachedEditor, nil 148 } else if haveSession { 149 // This session already exists. 150 cachedSession.mu.Lock() 151 cachedSession.clients++ 152 cachedSession.mu.Unlock() 153 return cachedSession, nil 154 } 155 156 // Check that the contract and host are both available, and run some brief 157 // sanity checks to see that the host is not swindling us. 158 contract, haveContract := c.staticContracts.View(id) 159 if !haveContract { 160 return nil, errors.New("contract was not found in the renter's contract set") 161 } 162 host, haveHost, err := c.staticHDB.Host(contract.HostPublicKey) 163 if err != nil { 164 return nil, errors.AddContext(err, "error getting host from hostdb:") 165 } else if height > contract.EndHeight { 166 return nil, errContractEnded 167 } else if !haveHost { 168 return nil, errHostNotFound 169 } else if host.Filtered { 170 return nil, errHostBlocked 171 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 172 return nil, errTooExpensive 173 } else if host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 { 174 return nil, errTooExpensive 175 } 176 177 // If host is >= 1.4.0, use the new renter-host protocol. 178 if build.VersionCmp(host.Version, "1.4.0") >= 0 { 179 return c.Session(pk, cancel) 180 } 181 182 // Create the editor. 183 e, err := c.staticContracts.NewEditor(host, contract.ID, height, c.staticHDB, cancel) 184 if err != nil { 185 return nil, err 186 } 187 188 // cache editor 189 he := &hostEditor{ 190 clients: 1, 191 staticContractor: c, 192 staticEditor: e, 193 staticEndHeight: contract.EndHeight, 194 staticID: id, 195 staticNetAddress: host.NetAddress, 196 } 197 c.mu.Lock() 198 c.editors[contract.ID] = he 199 c.mu.Unlock() 200 201 return he, nil 202 }