gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/proto/downloader.go (about)

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/NebulousLabs/Sia/crypto"
    10  	"github.com/NebulousLabs/Sia/encoding"
    11  	"github.com/NebulousLabs/Sia/modules"
    12  	"github.com/NebulousLabs/Sia/types"
    13  )
    14  
    15  // A Downloader retrieves sectors by calling the download RPC on a host.
    16  // Downloaders are NOT thread- safe; calls to Sector must be serialized.
    17  type Downloader struct {
    18  	contractID  types.FileContractID
    19  	contractSet *ContractSet
    20  	host        modules.HostDBEntry
    21  	conn        net.Conn
    22  	closeChan   chan struct{}
    23  	once        sync.Once
    24  	hdb         hostDB
    25  }
    26  
    27  // Sector retrieves the sector with the specified Merkle root, and revises
    28  // the underlying contract to pay the host proportionally to the data
    29  // retrieve.
    30  func (hd *Downloader) Sector(root crypto.Hash) (_ modules.RenterContract, _ []byte, err error) {
    31  	// Reset deadline when finished.
    32  	defer extendDeadline(hd.conn, time.Hour) // TODO: Constant.
    33  
    34  	// Acquire the contract.
    35  	// TODO: why not just lock the SafeContract directly?
    36  	sc, haveContract := hd.contractSet.Acquire(hd.contractID)
    37  	if !haveContract {
    38  		return modules.RenterContract{}, nil, errors.New("contract not present in contract set")
    39  	}
    40  	defer hd.contractSet.Return(sc)
    41  	contract := sc.header // for convenience
    42  
    43  	// calculate price
    44  	sectorPrice := hd.host.DownloadBandwidthPrice.Mul64(modules.SectorSize)
    45  	if contract.RenterFunds().Cmp(sectorPrice) < 0 {
    46  		return modules.RenterContract{}, nil, errors.New("contract has insufficient funds to support download")
    47  	}
    48  	// To mitigate small errors (e.g. differing block heights), fudge the
    49  	// price and collateral by 0.2%.
    50  	sectorPrice = sectorPrice.MulFloat(1 + hostPriceLeeway)
    51  
    52  	// create the download revision
    53  	rev := newDownloadRevision(contract.LastRevision(), sectorPrice)
    54  
    55  	// initiate download by confirming host settings
    56  	extendDeadline(hd.conn, modules.NegotiateSettingsTime)
    57  	if err := startDownload(hd.conn, hd.host); err != nil {
    58  		return modules.RenterContract{}, nil, err
    59  	}
    60  
    61  	// record the change we are about to make to the contract. If we lose power
    62  	// mid-revision, this allows us to restore either the pre-revision or
    63  	// post-revision contract.
    64  	walTxn, err := sc.recordDownloadIntent(rev, sectorPrice)
    65  	if err != nil {
    66  		return modules.RenterContract{}, nil, err
    67  	}
    68  
    69  	// send download action
    70  	extendDeadline(hd.conn, 2*time.Minute) // TODO: Constant.
    71  	err = encoding.WriteObject(hd.conn, []modules.DownloadAction{{
    72  		MerkleRoot: root,
    73  		Offset:     0,
    74  		Length:     modules.SectorSize,
    75  	}})
    76  	if err != nil {
    77  		return modules.RenterContract{}, nil, err
    78  	}
    79  
    80  	// Increase Successful/Failed interactions accordingly
    81  	defer func() {
    82  		if err != nil {
    83  			hd.hdb.IncrementFailedInteractions(contract.HostPublicKey())
    84  		} else if err == nil {
    85  			hd.hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
    86  		}
    87  	}()
    88  
    89  	// send the revision to the host for approval
    90  	extendDeadline(hd.conn, 2*time.Minute) // TODO: Constant.
    91  	signedTxn, err := negotiateRevision(hd.conn, rev, contract.SecretKey)
    92  	if err == modules.ErrStopResponse {
    93  		// if host gracefully closed, close our connection as well; this will
    94  		// cause the next download to fail. However, we must delay closing
    95  		// until we've finished downloading the sector.
    96  		defer hd.conn.Close()
    97  	} else if err != nil {
    98  		return modules.RenterContract{}, nil, err
    99  	}
   100  
   101  	// read sector data, completing one iteration of the download loop
   102  	extendDeadline(hd.conn, modules.NegotiateDownloadTime)
   103  	var sectors [][]byte
   104  	if err := encoding.ReadObject(hd.conn, &sectors, modules.SectorSize+16); err != nil {
   105  		return modules.RenterContract{}, nil, err
   106  	} else if len(sectors) != 1 {
   107  		return modules.RenterContract{}, nil, errors.New("host did not send enough sectors")
   108  	}
   109  	sector := sectors[0]
   110  	if uint64(len(sector)) != modules.SectorSize {
   111  		return modules.RenterContract{}, nil, errors.New("host did not send enough sector data")
   112  	} else if crypto.MerkleRoot(sector) != root {
   113  		return modules.RenterContract{}, nil, errors.New("host sent bad sector data")
   114  	}
   115  
   116  	// update contract and metrics
   117  	if err := sc.commitDownload(walTxn, signedTxn, sectorPrice); err != nil {
   118  		return modules.RenterContract{}, nil, err
   119  	}
   120  
   121  	return sc.Metadata(), sector, nil
   122  }
   123  
   124  // shutdown terminates the revision loop and signals the goroutine spawned in
   125  // NewDownloader to return.
   126  func (hd *Downloader) shutdown() {
   127  	extendDeadline(hd.conn, modules.NegotiateSettingsTime)
   128  	// don't care about these errors
   129  	_, _ = verifySettings(hd.conn, hd.host)
   130  	_ = modules.WriteNegotiationStop(hd.conn)
   131  	close(hd.closeChan)
   132  }
   133  
   134  // Close cleanly terminates the download loop with the host and closes the
   135  // connection.
   136  func (hd *Downloader) Close() error {
   137  	// using once ensures that Close is idempotent
   138  	hd.once.Do(hd.shutdown)
   139  	return hd.conn.Close()
   140  }
   141  
   142  // NewDownloader initiates the download request loop with a host, and returns a
   143  // Downloader.
   144  func (cs *ContractSet) NewDownloader(host modules.HostDBEntry, id types.FileContractID, hdb hostDB, cancel <-chan struct{}) (_ *Downloader, err error) {
   145  	sc, ok := cs.Acquire(id)
   146  	if !ok {
   147  		return nil, errors.New("invalid contract")
   148  	}
   149  	defer cs.Return(sc)
   150  	contract := sc.header
   151  
   152  	// check that contract has enough value to support a download
   153  	sectorPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize)
   154  	if contract.RenterFunds().Cmp(sectorPrice) < 0 {
   155  		return nil, errors.New("contract has insufficient funds to support download")
   156  	}
   157  
   158  	// Increase Successful/Failed interactions accordingly
   159  	defer func() {
   160  		// A revision mismatch might not be the host's fault.
   161  		if err != nil && !IsRevisionMismatch(err) {
   162  			hdb.IncrementFailedInteractions(contract.HostPublicKey())
   163  		} else if err == nil {
   164  			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   165  		}
   166  	}()
   167  
   168  	conn, closeChan, err := initiateRevisionLoop(host, contract, modules.RPCDownload, cancel)
   169  	if IsRevisionMismatch(err) && len(sc.unappliedTxns) > 0 {
   170  		// we have desynced from the host. If we have unapplied updates from the
   171  		// WAL, try applying them.
   172  		conn, closeChan, err = initiateRevisionLoop(host, sc.unappliedHeader(), modules.RPCDownload, cancel)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		// applying the updates was successful; commit them to disk
   177  		if err := sc.commitTxns(); err != nil {
   178  			return nil, err
   179  		}
   180  	} else if err != nil {
   181  		return nil, err
   182  	}
   183  	// if we succeeded, we can safely discard the unappliedTxns
   184  	for _, txn := range sc.unappliedTxns {
   185  		txn.SignalUpdatesApplied()
   186  	}
   187  	sc.unappliedTxns = nil
   188  
   189  	// the host is now ready to accept revisions
   190  	return &Downloader{
   191  		contractID:  id,
   192  		contractSet: cs,
   193  		host:        host,
   194  		conn:        conn,
   195  		closeChan:   closeChan,
   196  		hdb:         hdb,
   197  	}, nil
   198  }