github.com/nebulouslabs/sia@v1.3.7/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(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Editor, err error) { 119 c.mu.RLock() 120 id, gotID := c.pubKeysToContractID[string(pk.Key)] 121 cachedEditor, haveEditor := c.editors[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 } 139 140 // Check that the contract and host are both available, and run some brief 141 // sanity checks to see that the host is not swindling us. 142 contract, haveContract := c.staticContracts.View(id) 143 if !haveContract { 144 return nil, errors.New("no record of that contract") 145 } 146 host, haveHost := c.hdb.Host(contract.HostPublicKey) 147 if height > contract.EndHeight { 148 return nil, errors.New("contract has already ended") 149 } else if !haveHost { 150 return nil, errors.New("no record of that host") 151 } else if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 152 return nil, errTooExpensive 153 } else if host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 { 154 return nil, errTooExpensive 155 } 156 157 // Acquire the revising lock. 158 c.mu.Lock() 159 alreadyRevising := c.revising[contract.ID] 160 if alreadyRevising { 161 c.mu.Unlock() 162 return nil, errors.New("already revising that contract") 163 } 164 c.revising[contract.ID] = true 165 c.mu.Unlock() 166 // Release the revising lock early in the event of an error. 167 defer func() { 168 if err != nil { 169 c.mu.Lock() 170 delete(c.revising, contract.ID) 171 c.mu.Unlock() 172 } 173 }() 174 175 // Create the editor. 176 e, err := c.staticContracts.NewEditor(host, contract.ID, height, c.hdb, cancel) 177 if err != nil { 178 return nil, err 179 } 180 181 // cache editor 182 he := &hostEditor{ 183 clients: 1, 184 contractor: c, 185 editor: e, 186 endHeight: contract.EndHeight, 187 id: id, 188 netAddress: host.NetAddress, 189 } 190 c.mu.Lock() 191 c.editors[contract.ID] = he 192 c.mu.Unlock() 193 194 return he, nil 195 }