gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/downloader.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"gitlab.com/SiaPrime/SiaPrime/build"
     8  	"gitlab.com/SiaPrime/SiaPrime/crypto"
     9  	"gitlab.com/SiaPrime/SiaPrime/modules"
    10  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/proto"
    11  	"gitlab.com/SiaPrime/SiaPrime/types"
    12  )
    13  
    14  var errInvalidDownloader = errors.New("downloader has been invalidated because its contract is being renewed")
    15  
    16  // An Downloader retrieves sectors from with a host. It requests one sector at
    17  // a time, and revises the file contract to transfer money to the host
    18  // proportional to the data retrieved.
    19  type Downloader interface {
    20  	// Download requests the specified sector data.
    21  	Download(root crypto.Hash, offset, length uint32) ([]byte, error)
    22  
    23  	// Close terminates the connection to the host.
    24  	Close() error
    25  }
    26  
    27  // A hostDownloader retrieves sectors by calling the download RPC on a host.
    28  // It implements the Downloader interface. hostDownloaders are safe for use by
    29  // multiple goroutines.
    30  type hostDownloader struct {
    31  	clients      int // safe to Close when 0
    32  	contractID   types.FileContractID
    33  	contractor   *Contractor
    34  	downloader   *proto.Downloader
    35  	hostSettings modules.HostExternalSettings
    36  	invalid      bool   // true if invalidate has been called
    37  	speed        uint64 // Bytes per second.
    38  	mu           sync.Mutex
    39  }
    40  
    41  // invalidate sets the invalid flag and closes the underlying
    42  // proto.Downloader. Once invalidate returns, the hostDownloader is guaranteed
    43  // to not further revise its contract. This is used during contract renewal to
    44  // prevent a Downloader from revising a contract mid-renewal.
    45  func (hd *hostDownloader) invalidate() {
    46  	hd.mu.Lock()
    47  	defer hd.mu.Unlock()
    48  	if !hd.invalid {
    49  		hd.downloader.Close()
    50  		hd.invalid = true
    51  	}
    52  	hd.contractor.mu.Lock()
    53  	delete(hd.contractor.downloaders, hd.contractID)
    54  	hd.contractor.mu.Unlock()
    55  }
    56  
    57  // Close cleanly terminates the download loop with the host and closes the
    58  // connection.
    59  func (hd *hostDownloader) Close() error {
    60  	hd.mu.Lock()
    61  	defer hd.mu.Unlock()
    62  	hd.clients--
    63  	// Close is a no-op if invalidate has been called, or if there are other
    64  	// clients still using the hostDownloader.
    65  	if hd.invalid || hd.clients > 0 {
    66  		return nil
    67  	}
    68  	hd.invalid = true
    69  	hd.contractor.mu.Lock()
    70  	delete(hd.contractor.downloaders, hd.contractID)
    71  	hd.contractor.mu.Unlock()
    72  	return hd.downloader.Close()
    73  }
    74  
    75  // HostSettings returns the settings of the host that the downloader connects
    76  // to.
    77  func (hd *hostDownloader) HostSettings() modules.HostExternalSettings {
    78  	hd.mu.Lock()
    79  	defer hd.mu.Unlock()
    80  	return hd.hostSettings
    81  }
    82  
    83  // Download retrieves the requested sector data and revises the underlying
    84  // contract to pay the host proportionally to the data retrieved.
    85  func (hd *hostDownloader) Download(root crypto.Hash, offset, length uint32) ([]byte, error) {
    86  	hd.mu.Lock()
    87  	defer hd.mu.Unlock()
    88  	if hd.invalid {
    89  		return nil, errInvalidDownloader
    90  	}
    91  	_, data, err := hd.downloader.Download(root, offset, length)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	return data, nil
    96  }
    97  
    98  // Downloader returns a Downloader object that can be used to download sectors
    99  // from a host.
   100  func (c *Contractor) Downloader(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Downloader, err error) {
   101  	c.mu.RLock()
   102  	id, gotID := c.pubKeysToContractID[pk.String()]
   103  	cachedDownloader, haveDownloader := c.downloaders[id]
   104  	cachedSession, haveSession := c.sessions[id]
   105  	height := c.blockHeight
   106  	renewing := c.renewing[id]
   107  	c.mu.RUnlock()
   108  	if !gotID {
   109  		return nil, errors.New("failed to get filecontract id from key")
   110  	}
   111  	if renewing {
   112  		return nil, errors.New("currently renewing that contract")
   113  	} else if haveDownloader {
   114  		// increment number of clients and return
   115  		cachedDownloader.mu.Lock()
   116  		cachedDownloader.clients++
   117  		cachedDownloader.mu.Unlock()
   118  		return cachedDownloader, nil
   119  	} else if haveSession {
   120  		cachedSession.mu.Lock()
   121  		cachedSession.clients++
   122  		cachedSession.mu.Unlock()
   123  		return cachedSession, nil
   124  	}
   125  
   126  	// Fetch the contract and host.
   127  	contract, haveContract := c.staticContracts.View(id)
   128  	if !haveContract {
   129  		return nil, errors.New("no record of that contract")
   130  	}
   131  	host, haveHost := c.hdb.Host(contract.HostPublicKey)
   132  	if height > contract.EndHeight {
   133  		return nil, errors.New("contract has already ended")
   134  	} else if !haveHost {
   135  		return nil, errors.New("no record of that host")
   136  	} else if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 {
   137  		return nil, errTooExpensive
   138  	}
   139  
   140  	// If host is >= 1.4.0, use the new renter-host protocol.
   141  	if build.VersionCmp(host.Version, "1.4.0") >= 0 {
   142  		return c.Session(pk, cancel)
   143  	}
   144  
   145  	// create downloader
   146  	d, err := c.staticContracts.NewDownloader(host, contract.ID, height, c.hdb, cancel)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	// cache downloader
   152  	hd := &hostDownloader{
   153  		clients:    1,
   154  		contractor: c,
   155  		downloader: d,
   156  		contractID: id,
   157  	}
   158  	c.mu.Lock()
   159  	c.downloaders[contract.ID] = hd
   160  	c.mu.Unlock()
   161  
   162  	return hd, nil
   163  }