github.com/NebulousLabs/Sia@v1.3.7/modules/renter/contractor/downloader.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/NebulousLabs/Sia/crypto"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    10  	"github.com/NebulousLabs/Sia/types"
    11  )
    12  
    13  var errInvalidDownloader = errors.New("downloader has been invalidated because its contract is being renewed")
    14  
    15  // An Downloader retrieves sectors from with a host. It requests one sector at
    16  // a time, and revises the file contract to transfer money to the host
    17  // proportional to the data retrieved.
    18  type Downloader interface {
    19  	// Sector retrieves the sector with the specified Merkle root, and revises
    20  	// the underlying contract to pay the host proportionally to the data
    21  	// retrieve.
    22  	Sector(root crypto.Hash) ([]byte, error)
    23  
    24  	// Close terminates the connection to the host.
    25  	Close() error
    26  }
    27  
    28  // A hostDownloader retrieves sectors by calling the download RPC on a host.
    29  // It implements the Downloader interface. hostDownloaders are safe for use by
    30  // multiple goroutines.
    31  type hostDownloader struct {
    32  	clients      int // safe to Close when 0
    33  	contractID   types.FileContractID
    34  	contractor   *Contractor
    35  	downloader   *proto.Downloader
    36  	hostSettings modules.HostExternalSettings
    37  	invalid      bool   // true if invalidate has been called
    38  	speed        uint64 // Bytes per second.
    39  	mu           sync.Mutex
    40  }
    41  
    42  // invalidate sets the invalid flag and closes the underlying
    43  // proto.Downloader. Once invalidate returns, the hostDownloader is guaranteed
    44  // to not further revise its contract. This is used during contract renewal to
    45  // prevent a Downloader from revising a contract mid-renewal.
    46  func (hd *hostDownloader) invalidate() {
    47  	hd.mu.Lock()
    48  	defer hd.mu.Unlock()
    49  	if !hd.invalid {
    50  		hd.downloader.Close()
    51  		hd.invalid = true
    52  	}
    53  	hd.contractor.mu.Lock()
    54  	delete(hd.contractor.downloaders, hd.contractID)
    55  	delete(hd.contractor.revising, hd.contractID)
    56  	hd.contractor.mu.Unlock()
    57  }
    58  
    59  // Close cleanly terminates the download loop with the host and closes the
    60  // connection.
    61  func (hd *hostDownloader) Close() error {
    62  	hd.mu.Lock()
    63  	defer hd.mu.Unlock()
    64  	hd.clients--
    65  	// Close is a no-op if invalidate has been called, or if there are other
    66  	// clients still using the hostDownloader.
    67  	if hd.invalid || hd.clients > 0 {
    68  		return nil
    69  	}
    70  	hd.invalid = true
    71  	hd.contractor.mu.Lock()
    72  	delete(hd.contractor.downloaders, hd.contractID)
    73  	delete(hd.contractor.revising, hd.contractID)
    74  	hd.contractor.mu.Unlock()
    75  	return hd.downloader.Close()
    76  }
    77  
    78  // HostSettings returns the settings of the host that the downloader connects
    79  // to.
    80  func (hd *hostDownloader) HostSettings() modules.HostExternalSettings {
    81  	hd.mu.Lock()
    82  	defer hd.mu.Unlock()
    83  	return hd.hostSettings
    84  }
    85  
    86  // Sector retrieves the sector with the specified Merkle root, and revises
    87  // the underlying contract to pay the host proportionally to the data
    88  // retrieve.
    89  func (hd *hostDownloader) Sector(root crypto.Hash) ([]byte, error) {
    90  	hd.mu.Lock()
    91  	defer hd.mu.Unlock()
    92  	if hd.invalid {
    93  		return nil, errInvalidDownloader
    94  	}
    95  
    96  	// Download the sector.
    97  	_, sector, err := hd.downloader.Sector(root)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  	return sector, nil
   102  }
   103  
   104  // Downloader returns a Downloader object that can be used to download sectors
   105  // from a host.
   106  func (c *Contractor) Downloader(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Downloader, err error) {
   107  	c.mu.RLock()
   108  	id, gotID := c.pubKeysToContractID[string(pk.Key)]
   109  	cachedDownloader, haveDownloader := c.downloaders[id]
   110  	height := c.blockHeight
   111  	renewing := c.renewing[id]
   112  	c.mu.RUnlock()
   113  	if !gotID {
   114  		return nil, errors.New("failed to get filecontract id from key")
   115  	}
   116  	if renewing {
   117  		return nil, errors.New("currently renewing that contract")
   118  	} else if haveDownloader {
   119  		// increment number of clients and return
   120  		cachedDownloader.mu.Lock()
   121  		cachedDownloader.clients++
   122  		cachedDownloader.mu.Unlock()
   123  		return cachedDownloader, 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  	// Acquire the revising lock for the contract, which excludes other threads
   141  	// from interacting with the contract.
   142  	//
   143  	// TODO: Because we have another layer of contract safety via the
   144  	// contractset, do we need the revising lock anymore?
   145  	c.mu.Lock()
   146  	alreadyRevising := c.revising[contract.ID]
   147  	if alreadyRevising {
   148  		c.mu.Unlock()
   149  		return nil, errors.New("already revising that contract")
   150  	}
   151  	c.revising[contract.ID] = true
   152  	c.mu.Unlock()
   153  	// release lock early if function returns an error
   154  	defer func() {
   155  		if err != nil {
   156  			c.mu.Lock()
   157  			delete(c.revising, contract.ID)
   158  			c.mu.Unlock()
   159  		}
   160  	}()
   161  
   162  	// create downloader
   163  	d, err := c.staticContracts.NewDownloader(host, contract.ID, c.hdb, cancel)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	// cache downloader
   169  	hd := &hostDownloader{
   170  		clients:    1,
   171  		contractor: c,
   172  		downloader: d,
   173  		contractID: id,
   174  	}
   175  	c.mu.Lock()
   176  	c.downloaders[contract.ID] = hd
   177  	c.mu.Unlock()
   178  
   179  	return hd, nil
   180  }