github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/proto/downloader.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 // A Downloader retrieves sectors by calling the download RPC on a host. 15 // Downloaders are NOT thread- safe; calls to Sector must be serialized. 16 type Downloader struct { 17 host modules.HostDBEntry 18 contract modules.RenterContract // updated after each revision 19 conn net.Conn 20 21 // metrics 22 DownloadSpending types.Currency 23 } 24 25 // Sector retrieves the sector with the specified Merkle root, and revises 26 // the underlying contract to pay the host proportionally to the data 27 // retrieve. 28 func (hd *Downloader) Sector(root crypto.Hash) (modules.RenterContract, []byte, error) { 29 extendDeadline(hd.conn, modules.NegotiateDownloadTime) 30 defer extendDeadline(hd.conn, time.Hour) // reset deadline when finished 31 32 // calculate price 33 sectorPrice := hd.host.DownloadBandwidthPrice.Mul64(modules.SectorSize) 34 if hd.contract.LastRevision.NewValidProofOutputs[0].Value.Cmp(sectorPrice) < 0 { 35 return modules.RenterContract{}, nil, errors.New("contract has insufficient funds to support download") 36 } 37 38 // initiate download by confirming host settings 39 if err := startDownload(hd.conn, hd.host); err != nil { 40 return modules.RenterContract{}, nil, err 41 } 42 43 // send download action 44 err := encoding.WriteObject(hd.conn, []modules.DownloadAction{{ 45 MerkleRoot: root, 46 Offset: 0, 47 Length: modules.SectorSize, 48 }}) 49 if err != nil { 50 return modules.RenterContract{}, nil, err 51 } 52 53 // create and send revision to host for approval 54 rev := newDownloadRevision(hd.contract.LastRevision, sectorPrice) 55 signedTxn, err := negotiateRevision(hd.conn, rev, hd.contract.SecretKey) 56 if err != nil { 57 return modules.RenterContract{}, nil, err 58 } 59 60 // read sector data, completing one iteration of the download loop 61 var sectors [][]byte 62 if err := encoding.ReadObject(hd.conn, §ors, modules.SectorSize+16); err != nil { 63 return modules.RenterContract{}, nil, err 64 } else if len(sectors) != 1 { 65 return modules.RenterContract{}, nil, errors.New("host did not send enough sectors") 66 } 67 sector := sectors[0] 68 if uint64(len(sector)) != modules.SectorSize { 69 return modules.RenterContract{}, nil, errors.New("host did not send enough sector data") 70 } else if crypto.MerkleRoot(sector) != root { 71 return modules.RenterContract{}, nil, errors.New("host sent bad sector data") 72 } 73 74 // update contract and metrics 75 hd.contract.LastRevision = rev 76 hd.contract.LastRevisionTxn = signedTxn 77 hd.DownloadSpending = hd.DownloadSpending.Add(sectorPrice) 78 79 return hd.contract, sector, nil 80 } 81 82 // Close cleanly terminates the download loop with the host and closes the 83 // connection. 84 func (hd *Downloader) Close() error { 85 extendDeadline(hd.conn, modules.NegotiateSettingsTime) 86 // don't care about these errors 87 _, _ = verifySettings(hd.conn, hd.host) 88 _ = modules.WriteNegotiationStop(hd.conn) 89 return hd.conn.Close() 90 } 91 92 // NewDownloader initiates the download request loop with a host, and returns a 93 // Downloader. 94 func NewDownloader(host modules.HostDBEntry, contract modules.RenterContract) (*Downloader, error) { 95 // check that contract has enough value to support a download 96 if len(contract.LastRevision.NewValidProofOutputs) != 2 { 97 return nil, errors.New("invalid contract") 98 } 99 if !host.DownloadBandwidthPrice.IsZero() { 100 bytes, errOverflow := contract.LastRevision.NewValidProofOutputs[0].Value.Div(host.DownloadBandwidthPrice).Uint64() 101 if errOverflow == nil && bytes < modules.SectorSize { 102 return nil, errors.New("contract has insufficient funds to support download") 103 } 104 } 105 106 // initiate download loop 107 conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second) 108 if err != nil { 109 return nil, err 110 } 111 // allot 2 minutes for RPC request + revision exchange 112 extendDeadline(conn, modules.NegotiateRecentRevisionTime) 113 defer extendDeadline(conn, time.Hour) 114 if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil { 115 return nil, errors.New("couldn't initiate RPC: " + err.Error()) 116 } 117 if err := verifyRecentRevision(conn, contract); err != nil { 118 return nil, errors.New("revision exchange failed: " + err.Error()) 119 } 120 121 // the host is now ready to accept revisions 122 return &Downloader{ 123 contract: contract, 124 host: host, 125 conn: conn, 126 }, nil 127 }