github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/contractor/editor.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "sync" 6 7 "SiaPrime/crypto" 8 "SiaPrime/modules" 9 "SiaPrime/modules/renter/proto" 10 "SiaPrime/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 he.contractor.mu.Unlock() 68 } 69 70 // Address returns the NetAddress of the host. 71 func (he *hostEditor) Address() modules.NetAddress { return he.netAddress } 72 73 // ContractID returns the id of the contract being revised. 74 func (he *hostEditor) ContractID() types.FileContractID { return he.id } 75 76 // EndHeight returns the height at which the host is no longer obligated to 77 // store the file. 78 func (he *hostEditor) EndHeight() types.BlockHeight { return he.endHeight } 79 80 // Close cleanly terminates the revision loop with the host and closes the 81 // connection. 82 func (he *hostEditor) Close() error { 83 he.mu.Lock() 84 defer he.mu.Unlock() 85 he.clients-- 86 // Close is a no-op if invalidate has been called, or if there are other 87 // clients still using the hostEditor. 88 if he.invalid || he.clients > 0 { 89 return nil 90 } 91 he.invalid = true 92 he.contractor.mu.Lock() 93 delete(he.contractor.editors, he.id) 94 he.contractor.mu.Unlock() 95 return he.editor.Close() 96 } 97 98 // Upload negotiates a revision that adds a sector to a file contract. 99 func (he *hostEditor) Upload(data []byte) (_ crypto.Hash, err error) { 100 he.mu.Lock() 101 defer he.mu.Unlock() 102 if he.invalid { 103 return crypto.Hash{}, errInvalidEditor 104 } 105 106 // Perform the upload. 107 _, sectorRoot, err := he.editor.Upload(data) 108 if err != nil { 109 return crypto.Hash{}, err 110 } 111 return sectorRoot, nil 112 } 113 114 // Editor returns a Editor object that can be used to upload, modify, and 115 // delete sectors on a host. 116 func (c *Contractor) Editor(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Editor, err error) { 117 c.mu.RLock() 118 id, gotID := c.pubKeysToContractID[string(pk.Key)] 119 cachedEditor, haveEditor := c.editors[id] 120 height := c.blockHeight 121 renewing := c.renewing[id] 122 c.mu.RUnlock() 123 if !gotID { 124 return nil, errors.New("failed to get filecontract id from key") 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.staticContracts.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 // Create the editor. 156 e, err := c.staticContracts.NewEditor(host, contract.ID, height, c.hdb, cancel) 157 if err != nil { 158 return nil, err 159 } 160 161 // cache editor 162 he := &hostEditor{ 163 clients: 1, 164 contractor: c, 165 editor: e, 166 endHeight: contract.EndHeight, 167 id: id, 168 netAddress: host.NetAddress, 169 } 170 c.mu.Lock() 171 c.editors[contract.ID] = he 172 c.mu.Unlock() 173 174 return he, nil 175 }