github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/proto/editor.go (about) 1 package proto 2 3 import ( 4 "errors" 5 "net" 6 "time" 7 8 "github.com/NebulousLabs/Sia/crypto" 9 "github.com/NebulousLabs/Sia/encoding" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/types" 12 ) 13 14 var ( 15 // sectorHeight is the height of a Merkle tree that covers a single 16 // sector. It is log2(modules.SectorSize / crypto.SegmentSize) 17 sectorHeight = func() uint64 { 18 height := uint64(0) 19 for 1<<height < (modules.SectorSize / crypto.SegmentSize) { 20 height++ 21 } 22 return height 23 }() 24 ) 25 26 // cachedMerkleRoot calculates the root of a set of existing Merkle roots. 27 func cachedMerkleRoot(roots []crypto.Hash) crypto.Hash { 28 tree := crypto.NewCachedTree(sectorHeight) // NOTE: height is not strictly necessary here 29 for _, h := range roots { 30 tree.Push(h) 31 } 32 return tree.Root() 33 } 34 35 // A Editor modifies a Contract by calling the revise RPC on a host. It 36 // Editors are NOT thread-safe; calls to Upload must happen in serial. 37 type Editor struct { 38 conn net.Conn 39 host modules.HostDBEntry 40 41 height types.BlockHeight 42 contract modules.RenterContract // updated after each revision 43 44 // metrics 45 StorageSpending types.Currency 46 UploadSpending types.Currency 47 } 48 49 // Close cleanly terminates the revision loop with the host and closes the 50 // connection. 51 func (he *Editor) Close() error { 52 // don't care about these errors 53 _, _ = verifySettings(he.conn, he.host) 54 _ = modules.WriteNegotiationStop(he.conn) 55 return he.conn.Close() 56 } 57 58 // runRevisionIteration submits actions and their accompanying revision to the 59 // host for approval. If negotiation is successful, it updates the underlying 60 // Contract. 61 func (he *Editor) runRevisionIteration(actions []modules.RevisionAction, rev types.FileContractRevision, newRoots []crypto.Hash) error { 62 // initiate revision 63 if err := startRevision(he.conn, he.host); err != nil { 64 return err 65 } 66 67 // send actions 68 if err := encoding.WriteObject(he.conn, actions); err != nil { 69 return err 70 } 71 72 // send revision to host and exchange signatures 73 signedTxn, err := negotiateRevision(he.conn, rev, he.contract.SecretKey) 74 if err != nil { 75 return err 76 } 77 78 // update host contract 79 he.contract.LastRevision = rev 80 he.contract.LastRevisionTxn = signedTxn 81 he.contract.MerkleRoots = newRoots 82 83 return nil 84 } 85 86 // Upload negotiates a revision that adds a sector to a file contract. 87 func (he *Editor) Upload(data []byte) (modules.RenterContract, crypto.Hash, error) { 88 // allot 10 minutes for this exchange; sufficient to transfer 4 MB over 50 kbps 89 extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime) 90 defer extendDeadline(he.conn, time.Hour) // reset deadline 91 92 // calculate price 93 // TODO: height is never updated, so we'll wind up overpaying on long-running uploads 94 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(he.contract.FileContract.WindowEnd-he.height)) 95 sectorStoragePrice := he.host.StoragePrice.Mul(blockBytes) 96 sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(modules.SectorSize) 97 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 98 if he.contract.LastRevision.NewValidProofOutputs[0].Value.Cmp(sectorPrice) < 0 { 99 return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient funds to support upload") 100 } 101 sectorCollateral := he.host.Collateral.Mul(blockBytes) 102 if he.contract.LastRevision.NewMissedProofOutputs[1].Value.Cmp(sectorCollateral) < 0 { 103 return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient collateral to support upload") 104 } 105 106 // calculate the new Merkle root 107 sectorRoot := crypto.MerkleRoot(data) 108 newRoots := append(he.contract.MerkleRoots, sectorRoot) 109 merkleRoot := cachedMerkleRoot(newRoots) 110 111 // create the action and revision 112 actions := []modules.RevisionAction{{ 113 Type: modules.ActionInsert, 114 SectorIndex: uint64(len(he.contract.MerkleRoots)), 115 Data: data, 116 }} 117 rev := newUploadRevision(he.contract.LastRevision, merkleRoot, sectorPrice, sectorCollateral) 118 119 // run the revision iteration 120 if err := he.runRevisionIteration(actions, rev, newRoots); err != nil { 121 return modules.RenterContract{}, crypto.Hash{}, err 122 } 123 124 // update metrics 125 he.StorageSpending = he.StorageSpending.Add(sectorStoragePrice) 126 he.UploadSpending = he.UploadSpending.Add(sectorBandwidthPrice) 127 128 return he.contract, sectorRoot, nil 129 } 130 131 // Delete negotiates a revision that removes a sector from a file contract. 132 func (he *Editor) Delete(root crypto.Hash) (modules.RenterContract, error) { 133 // allot 2 minutes for this exchange 134 extendDeadline(he.conn, 120*time.Second) 135 defer extendDeadline(he.conn, time.Hour) // reset deadline 136 137 // calculate the new Merkle root 138 newRoots := make([]crypto.Hash, 0, len(he.contract.MerkleRoots)) 139 index := -1 140 for i, h := range he.contract.MerkleRoots { 141 if h == root { 142 index = i 143 } else { 144 newRoots = append(newRoots, h) 145 } 146 } 147 if index == -1 { 148 return modules.RenterContract{}, errors.New("no record of that sector root") 149 } 150 merkleRoot := cachedMerkleRoot(newRoots) 151 152 // create the action and accompanying revision 153 actions := []modules.RevisionAction{{ 154 Type: modules.ActionDelete, 155 SectorIndex: uint64(index), 156 }} 157 rev := newDeleteRevision(he.contract.LastRevision, merkleRoot) 158 159 // run the revision iteration 160 if err := he.runRevisionIteration(actions, rev, newRoots); err != nil { 161 return modules.RenterContract{}, err 162 } 163 return he.contract, nil 164 } 165 166 // Modify negotiates a revision that edits a sector in a file contract. 167 func (he *Editor) Modify(oldRoot, newRoot crypto.Hash, offset uint64, newData []byte) (modules.RenterContract, error) { 168 // allot 10 minutes for this exchange; sufficient to transfer 4 MB over 50 kbps 169 extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime) 170 defer extendDeadline(he.conn, time.Hour) // reset deadline 171 172 // calculate price 173 sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(uint64(len(newData))) 174 if he.contract.LastRevision.NewValidProofOutputs[0].Value.Cmp(sectorBandwidthPrice) < 0 { 175 return modules.RenterContract{}, errors.New("contract has insufficient funds to support modification") 176 } 177 178 // calculate the new Merkle root 179 newRoots := make([]crypto.Hash, len(he.contract.MerkleRoots)) 180 index := -1 181 for i, h := range he.contract.MerkleRoots { 182 if h == oldRoot { 183 index = i 184 newRoots[i] = newRoot 185 } else { 186 newRoots[i] = h 187 } 188 } 189 if index == -1 { 190 return modules.RenterContract{}, errors.New("no record of that sector root") 191 } 192 merkleRoot := cachedMerkleRoot(newRoots) 193 194 // create the action and revision 195 actions := []modules.RevisionAction{{ 196 Type: modules.ActionModify, 197 SectorIndex: uint64(index), 198 Offset: offset, 199 Data: newData, 200 }} 201 rev := newModifyRevision(he.contract.LastRevision, merkleRoot, sectorBandwidthPrice) 202 203 // run the revision iteration 204 if err := he.runRevisionIteration(actions, rev, newRoots); err != nil { 205 return modules.RenterContract{}, err 206 } 207 208 // update metrics 209 he.UploadSpending = he.UploadSpending.Add(sectorBandwidthPrice) 210 211 return he.contract, nil 212 } 213 214 // NewEditor initiates the contract revision process with a host, and returns 215 // an Editor. 216 func NewEditor(host modules.HostDBEntry, contract modules.RenterContract, currentHeight types.BlockHeight) (*Editor, error) { 217 // check that contract has enough value to support an upload 218 if len(contract.LastRevision.NewValidProofOutputs) != 2 { 219 return nil, errors.New("invalid contract") 220 } 221 if !host.StoragePrice.IsZero() { 222 bytes, errOverflow := contract.LastRevision.NewValidProofOutputs[0].Value.Div(host.StoragePrice).Uint64() 223 if errOverflow == nil && bytes < modules.SectorSize { 224 return nil, errors.New("contract has insufficient capacity") 225 } 226 } 227 228 // initiate revision loop 229 conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second) 230 if err != nil { 231 return nil, err 232 } 233 // allot 2 minutes for RPC request + revision exchange 234 extendDeadline(conn, modules.NegotiateRecentRevisionTime) 235 defer extendDeadline(conn, time.Hour) 236 if err := encoding.WriteObject(conn, modules.RPCReviseContract); err != nil { 237 return nil, errors.New("couldn't initiate RPC: " + err.Error()) 238 } 239 if err := verifyRecentRevision(conn, contract); err != nil { 240 return nil, errors.New("revision exchange failed: " + err.Error()) 241 } 242 243 // the host is now ready to accept revisions 244 return &Editor{ 245 host: host, 246 height: currentHeight, 247 contract: contract, 248 conn: conn, 249 }, nil 250 }