gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/downloader.go (about)

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