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