gitlab.com/jokerrs1/Sia@v1.3.2/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(id types.FileContractID, cancel <-chan struct{}) (_ Downloader, err error) {
   107  	id = c.ResolveID(id)
   108  	c.mu.RLock()
   109  	cachedDownloader, haveDownloader := c.downloaders[id]
   110  	height := c.blockHeight
   111  	renewing := c.renewing[id]
   112  	c.mu.RUnlock()
   113  	if renewing {
   114  		return nil, errors.New("currently renewing that contract")
   115  	} else if haveDownloader {
   116  		// increment number of clients and return
   117  		cachedDownloader.mu.Lock()
   118  		cachedDownloader.clients++
   119  		cachedDownloader.mu.Unlock()
   120  		return cachedDownloader, nil
   121  	}
   122  
   123  	// Fetch the contract and host.
   124  	contract, haveContract := c.contracts.View(id)
   125  	if !haveContract {
   126  		return nil, errors.New("no record of that contract")
   127  	}
   128  	host, haveHost := c.hdb.Host(contract.HostPublicKey)
   129  	if height > contract.EndHeight {
   130  		return nil, errors.New("contract has already ended")
   131  	} else if !haveHost {
   132  		return nil, errors.New("no record of that host")
   133  	} else if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 {
   134  		return nil, errTooExpensive
   135  	}
   136  
   137  	// Acquire the revising lock for the contract, which excludes other threads
   138  	// from interacting with the contract.
   139  	//
   140  	// TODO: Because we have another layer of contract safety via the
   141  	// contractset, do we need the revising lock anymore?
   142  	c.mu.Lock()
   143  	alreadyRevising := c.revising[contract.ID]
   144  	if alreadyRevising {
   145  		c.mu.Unlock()
   146  		return nil, errors.New("already revising that contract")
   147  	}
   148  	c.revising[contract.ID] = true
   149  	c.mu.Unlock()
   150  	// release lock early if function returns an error
   151  	defer func() {
   152  		if err != nil {
   153  			c.mu.Lock()
   154  			delete(c.revising, contract.ID)
   155  			c.mu.Unlock()
   156  		}
   157  	}()
   158  
   159  	// create downloader
   160  	d, err := c.contracts.NewDownloader(host, contract.ID, c.hdb, cancel)
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	// cache downloader
   166  	hd := &hostDownloader{
   167  		clients:      1,
   168  		contractID:   contract.ID,
   169  		contractor:   c,
   170  		downloader:   d,
   171  		hostSettings: host.HostExternalSettings,
   172  	}
   173  	c.mu.Lock()
   174  	c.downloaders[contract.ID] = hd
   175  	c.mu.Unlock()
   176  
   177  	return hd, nil
   178  }