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