// NewDownloader initiates the download request loop with a host, and returns a // Downloader. func NewDownloader(host modules.HostDBEntry, contract modules.RenterContract) (*Downloader, error) { // check that contract has enough value to support a download if len(contract.LastRevision.NewValidProofOutputs) != 2 { return nil, errors.New("invalid contract") } sectorPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) if contract.RenterFunds().Cmp(sectorPrice) < 0 { return nil, errors.New("contract has insufficient funds to support download") } // initiate download loop conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second) if err != nil { return nil, err } // allot 2 minutes for RPC request + revision exchange extendDeadline(conn, modules.NegotiateRecentRevisionTime) defer extendDeadline(conn, time.Hour) if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil { conn.Close() return nil, errors.New("couldn't initiate RPC: " + err.Error()) } if err := verifyRecentRevision(conn, contract); err != nil { conn.Close() // TODO: close gracefully if host has entered revision loop return nil, err } // the host is now ready to accept revisions return &Downloader{ contract: contract, host: host, conn: conn, }, nil }
// Editor initiates the contract revision process with a host, and returns // an Editor. func (c *Contractor) Editor(contract modules.RenterContract) (Editor, error) { c.mu.RLock() height := c.blockHeight c.mu.RUnlock() if height > contract.EndHeight() { return nil, errors.New("contract has already ended") } host, ok := c.hdb.Host(contract.NetAddress) if !ok { return nil, errors.New("no record of that host") } if host.StoragePrice.Cmp(maxStoragePrice) > 0 { return nil, errTooExpensive } // cap host.Collateral on new hosts if build.VersionCmp(host.Version, "0.6.0") > 0 { if host.Collateral.Cmp(maxUploadCollateral) > 0 { host.Collateral = maxUploadCollateral } } // create editor e, err := proto.NewEditor(host, contract, height) if proto.IsRevisionMismatch(err) { // try again with the cached revision c.mu.RLock() cached, ok := c.cachedRevisions[contract.ID] c.mu.RUnlock() if !ok { // nothing we can do; return original error return nil, err } c.log.Printf("host %v has different revision for %v; retrying with cached revision", contract.NetAddress, contract.ID) contract.LastRevision = cached.revision contract.MerkleRoots = cached.merkleRoots e, err = proto.NewEditor(host, contract, height) } if err != nil { return nil, err } // supply a SaveFn that saves the revision to the contractor's persist // (the existing revision will be overwritten when SaveFn is called) e.SaveFn = c.saveRevision(contract.ID) return &hostEditor{ editor: e, contract: contract, contractor: c, }, nil }
// Downloader initiates the download request loop with a host, and returns a // Downloader. func (c *Contractor) Downloader(contract modules.RenterContract) (Downloader, error) { c.mu.RLock() height := c.blockHeight c.mu.RUnlock() if height > contract.EndHeight() { return nil, errors.New("contract has already ended") } host, ok := c.hdb.Host(contract.NetAddress) if !ok { return nil, errors.New("no record of that host") } if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 { return nil, errTooExpensive } // create downloader d, err := proto.NewDownloader(host, contract) if proto.IsRevisionMismatch(err) { // try again with the cached revision c.mu.RLock() cached, ok := c.cachedRevisions[contract.ID] c.mu.RUnlock() if !ok { // nothing we can do; return original error return nil, err } c.log.Printf("host %v has different revision for %v; retrying with cached revision", contract.NetAddress, contract.ID) contract.LastRevision = cached.revision d, err = proto.NewDownloader(host, contract) } if err != nil { return nil, err } // supply a SaveFn that saves the revision to the contractor's persist // (the existing revision will be overwritten when SaveFn is called) d.SaveFn = c.saveRevision(contract.ID) return &hostDownloader{ downloader: d, contractor: c, }, nil }