gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/proto/editor.go (about) 1 package proto 2 3 import ( 4 "errors" 5 "net" 6 "sync" 7 "time" 8 9 "github.com/NebulousLabs/Sia/build" 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/encoding" 12 "github.com/NebulousLabs/Sia/modules" 13 "github.com/NebulousLabs/Sia/types" 14 "github.com/NebulousLabs/ratelimit" 15 ) 16 17 // cachedMerkleRoot calculates the root of a set of existing Merkle roots. 18 func cachedMerkleRoot(roots []crypto.Hash) crypto.Hash { 19 tree := crypto.NewCachedTree(sectorHeight) // NOTE: height is not strictly necessary here 20 for _, h := range roots { 21 tree.Push(h) 22 } 23 return tree.Root() 24 } 25 26 // A Editor modifies a Contract by calling the revise RPC on a host. It 27 // Editors are NOT thread-safe; calls to Upload must happen in serial. 28 type Editor struct { 29 contractID types.FileContractID 30 contractSet *ContractSet 31 conn net.Conn 32 closeChan chan struct{} 33 once sync.Once 34 host modules.HostDBEntry 35 hdb hostDB 36 37 height types.BlockHeight 38 } 39 40 // shutdown terminates the revision loop and signals the goroutine spawned in 41 // NewEditor to return. 42 func (he *Editor) shutdown() { 43 extendDeadline(he.conn, modules.NegotiateSettingsTime) 44 // don't care about these errors 45 _, _ = verifySettings(he.conn, he.host) 46 _ = modules.WriteNegotiationStop(he.conn) 47 close(he.closeChan) 48 } 49 50 // Close cleanly terminates the revision loop with the host and closes the 51 // connection. 52 func (he *Editor) Close() error { 53 // using once ensures that Close is idempotent 54 he.once.Do(he.shutdown) 55 return he.conn.Close() 56 } 57 58 // Upload negotiates a revision that adds a sector to a file contract. 59 func (he *Editor) Upload(data []byte) (_ modules.RenterContract, _ crypto.Hash, err error) { 60 // Acquire the contract. 61 sc, haveContract := he.contractSet.Acquire(he.contractID) 62 if !haveContract { 63 return modules.RenterContract{}, crypto.Hash{}, errors.New("contract not present in contract set") 64 } 65 defer he.contractSet.Return(sc) 66 contract := sc.header // for convenience 67 68 // calculate price 69 // TODO: height is never updated, so we'll wind up overpaying on long-running uploads 70 blockBytes := types.NewCurrency64(modules.SectorSize * uint64(contract.LastRevision().NewWindowEnd-he.height)) 71 sectorStoragePrice := he.host.StoragePrice.Mul(blockBytes) 72 sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(modules.SectorSize) 73 sectorCollateral := he.host.Collateral.Mul(blockBytes) 74 75 // to mitigate small errors (e.g. differing block heights), fudge the 76 // price and collateral by 0.2%. This is only applied to hosts above 77 // v1.0.1; older hosts use stricter math. 78 if build.VersionCmp(he.host.Version, "1.0.1") > 0 { 79 sectorStoragePrice = sectorStoragePrice.MulFloat(1 + hostPriceLeeway) 80 sectorBandwidthPrice = sectorBandwidthPrice.MulFloat(1 + hostPriceLeeway) 81 sectorCollateral = sectorCollateral.MulFloat(1 - hostPriceLeeway) 82 } 83 84 sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice) 85 if contract.RenterFunds().Cmp(sectorPrice) < 0 { 86 return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient funds to support upload") 87 } 88 if contract.LastRevision().NewMissedProofOutputs[1].Value.Cmp(sectorCollateral) < 0 { 89 return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient collateral to support upload") 90 } 91 92 // calculate the new Merkle root 93 sectorRoot := crypto.MerkleRoot(data) 94 newRoots := append(sc.merkleRoots, sectorRoot) 95 merkleRoot := cachedMerkleRoot(newRoots) 96 97 // create the action and revision 98 actions := []modules.RevisionAction{{ 99 Type: modules.ActionInsert, 100 SectorIndex: uint64(len(sc.merkleRoots)), 101 Data: data, 102 }} 103 rev := newUploadRevision(contract.LastRevision(), merkleRoot, sectorPrice, sectorCollateral) 104 105 // run the revision iteration 106 defer func() { 107 // Increase Successful/Failed interactions accordingly 108 if err != nil { 109 he.hdb.IncrementFailedInteractions(he.host.PublicKey) 110 } else { 111 he.hdb.IncrementSuccessfulInteractions(he.host.PublicKey) 112 } 113 114 // reset deadline 115 extendDeadline(he.conn, time.Hour) 116 }() 117 118 // initiate revision 119 extendDeadline(he.conn, modules.NegotiateSettingsTime) 120 if err := startRevision(he.conn, he.host); err != nil { 121 return modules.RenterContract{}, crypto.Hash{}, err 122 } 123 124 // record the change we are about to make to the contract. If we lose power 125 // mid-revision, this allows us to restore either the pre-revision or 126 // post-revision contract. 127 walTxn, err := sc.recordUploadIntent(rev, sectorRoot, sectorStoragePrice, sectorBandwidthPrice) 128 if err != nil { 129 return modules.RenterContract{}, crypto.Hash{}, err 130 } 131 132 // send actions 133 extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime) 134 if err := encoding.WriteObject(he.conn, actions); err != nil { 135 return modules.RenterContract{}, crypto.Hash{}, err 136 } 137 138 // send revision to host and exchange signatures 139 extendDeadline(he.conn, 2*time.Minute) 140 signedTxn, err := negotiateRevision(he.conn, rev, contract.SecretKey) 141 if err == modules.ErrStopResponse { 142 // if host gracefully closed, close our connection as well; this will 143 // cause the next operation to fail 144 he.conn.Close() 145 } else if err != nil { 146 return modules.RenterContract{}, crypto.Hash{}, err 147 } 148 149 // update contract 150 err = sc.commitUpload(walTxn, signedTxn, sectorRoot, sectorStoragePrice, sectorBandwidthPrice) 151 if err != nil { 152 return modules.RenterContract{}, crypto.Hash{}, err 153 } 154 155 return sc.Metadata(), sectorRoot, nil 156 } 157 158 // NewEditor initiates the contract revision process with a host, and returns 159 // an Editor. 160 func (cs *ContractSet) NewEditor(host modules.HostDBEntry, id types.FileContractID, currentHeight types.BlockHeight, hdb hostDB, cancel <-chan struct{}) (_ *Editor, err error) { 161 sc, ok := cs.Acquire(id) 162 if !ok { 163 return nil, errors.New("invalid contract") 164 } 165 defer cs.Return(sc) 166 contract := sc.header 167 168 // Increase Successful/Failed interactions accordingly 169 defer func() { 170 // a revision mismatch is not necessarily the host's fault 171 if err != nil && !IsRevisionMismatch(err) { 172 hdb.IncrementFailedInteractions(contract.HostPublicKey()) 173 } else if err == nil { 174 hdb.IncrementSuccessfulInteractions(contract.HostPublicKey()) 175 } 176 }() 177 178 conn, closeChan, err := initiateRevisionLoop(host, contract, modules.RPCReviseContract, cancel) 179 if IsRevisionMismatch(err) && len(sc.unappliedTxns) > 0 { 180 // we have desynced from the host. If we have unapplied updates from the 181 // WAL, try applying them. 182 conn, closeChan, err = initiateRevisionLoop(host, sc.unappliedHeader(), modules.RPCReviseContract, cancel) 183 if err != nil { 184 return nil, err 185 } 186 // applying the updates was successful; commit them to disk 187 if err := sc.commitTxns(); err != nil { 188 return nil, err 189 } 190 } else if err != nil { 191 return nil, err 192 } 193 // if we succeeded, we can safely discard the unappliedTxns 194 for _, txn := range sc.unappliedTxns { 195 txn.SignalUpdatesApplied() 196 } 197 sc.unappliedTxns = nil 198 199 // the host is now ready to accept revisions 200 return &Editor{ 201 host: host, 202 hdb: hdb, 203 height: currentHeight, 204 contractID: id, 205 contractSet: cs, 206 conn: conn, 207 closeChan: closeChan, 208 }, nil 209 } 210 211 // initiateRevisionLoop initiates either the editor or downloader loop with 212 // host, depending on which rpc was passed. 213 func initiateRevisionLoop(host modules.HostDBEntry, contract contractHeader, rpc types.Specifier, cancel <-chan struct{}) (net.Conn, chan struct{}, error) { 214 c, err := (&net.Dialer{ 215 Cancel: cancel, 216 Timeout: 45 * time.Second, // TODO: Constant 217 }).Dial("tcp", string(host.NetAddress)) 218 if err != nil { 219 return nil, nil, err 220 } 221 conn := ratelimit.NewRLConn(c, cancel) 222 223 closeChan := make(chan struct{}) 224 go func() { 225 select { 226 case <-cancel: 227 conn.Close() 228 case <-closeChan: 229 } 230 }() 231 232 // allot 2 minutes for RPC request + revision exchange 233 extendDeadline(conn, modules.NegotiateRecentRevisionTime) 234 defer extendDeadline(conn, time.Hour) 235 if err := encoding.WriteObject(conn, rpc); err != nil { 236 conn.Close() 237 close(closeChan) 238 return nil, closeChan, errors.New("couldn't initiate RPC: " + err.Error()) 239 } 240 if err := verifyRecentRevision(conn, contract, host.Version); err != nil { 241 conn.Close() // TODO: close gracefully if host has entered revision loop 242 close(closeChan) 243 return nil, closeChan, err 244 } 245 return conn, closeChan, nil 246 }