github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/contractor/contractor.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  
     9  	"github.com/NebulousLabs/Sia/modules"
    10  	"github.com/NebulousLabs/Sia/persist"
    11  	"github.com/NebulousLabs/Sia/types"
    12  )
    13  
    14  var (
    15  	errNilCS     = errors.New("cannot create contractor with nil consensus set")
    16  	errNilWallet = errors.New("cannot create contractor with nil wallet")
    17  	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
    18  )
    19  
    20  // A Contractor negotiates, revises, renews, and provides access to file
    21  // contracts.
    22  type Contractor struct {
    23  	// dependencies
    24  	hdb     hostDB
    25  	log     *persist.Logger
    26  	persist persister
    27  	tpool   transactionPool
    28  	wallet  wallet
    29  
    30  	allowance   modules.Allowance
    31  	blockHeight types.BlockHeight
    32  	contracts   map[types.FileContractID]modules.RenterContract
    33  	lastChange  modules.ConsensusChangeID
    34  	renewHeight types.BlockHeight // height at which to renew contracts
    35  
    36  	// metrics
    37  	downloadSpending types.Currency
    38  	storageSpending  types.Currency
    39  	uploadSpending   types.Currency
    40  
    41  	mu sync.RWMutex
    42  }
    43  
    44  // Allowance returns the current allowance.
    45  func (c *Contractor) Allowance() modules.Allowance {
    46  	c.mu.RLock()
    47  	defer c.mu.RUnlock()
    48  	return c.allowance
    49  }
    50  
    51  // FinancialMetrics returns the financial metrics of the Contractor.
    52  func (c *Contractor) FinancialMetrics() modules.RenterFinancialMetrics {
    53  	c.mu.RLock()
    54  	defer c.mu.RUnlock()
    55  	// calculate contract spending
    56  	var contractSpending types.Currency
    57  	for _, contract := range c.contracts {
    58  		contractSpending = contractSpending.Add(contract.FileContract.Payout)
    59  	}
    60  	return modules.RenterFinancialMetrics{
    61  		ContractSpending: contractSpending,
    62  		DownloadSpending: c.downloadSpending,
    63  		StorageSpending:  c.storageSpending,
    64  		UploadSpending:   c.uploadSpending,
    65  	}
    66  }
    67  
    68  // SetAllowance sets the amount of money the Contractor is allowed to spend on
    69  // contracts over a given time period, divided among the number of hosts
    70  // specified. Note that Contractor can start forming contracts as soon as
    71  // SetAllowance is called; that is, it may block.
    72  func (c *Contractor) SetAllowance(a modules.Allowance) error {
    73  	// sanity checks
    74  	if a.Hosts == 0 {
    75  		return errors.New("hosts must be non-zero")
    76  	} else if a.Period == 0 {
    77  		return errors.New("period must be non-zero")
    78  	} else if a.RenewWindow == 0 {
    79  		return errors.New("renew window must be non-zero")
    80  	} else if a.RenewWindow >= a.Period {
    81  		return errors.New("renew window must be less than period")
    82  	}
    83  
    84  	err := c.formContracts(a)
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	// Set the allowance.
    90  	c.mu.Lock()
    91  	c.allowance = a
    92  	err = c.saveSync()
    93  	c.mu.Unlock()
    94  
    95  	return err
    96  
    97  	/*
    98  		// If this is the first time the allowance has been set, form contracts
    99  		// immediately.
   100  		if old.Hosts == 0 {
   101  			return c.formContracts(a)
   102  		}
   103  
   104  		// Otherwise, if the new allowance is "significantly different" (to be
   105  		// defined more precisely later), form intermediary contracts.
   106  		if a.Funds.Cmp(old.Funds) > 0 {
   107  			// TODO: implement
   108  			// c.formContracts(diff(a, old))
   109  		}
   110  
   111  		return nil
   112  	*/
   113  }
   114  
   115  // Contracts returns the contracts formed by the contractor.
   116  func (c *Contractor) Contracts() (cs []modules.RenterContract) {
   117  	c.mu.RLock()
   118  	defer c.mu.RUnlock()
   119  	for _, c := range c.contracts {
   120  		cs = append(cs, c)
   121  	}
   122  	return
   123  }
   124  
   125  // New returns a new Contractor.
   126  func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
   127  	// Check for nil inputs.
   128  	if cs == nil {
   129  		return nil, errNilCS
   130  	}
   131  	if wallet == nil {
   132  		return nil, errNilWallet
   133  	}
   134  	if tpool == nil {
   135  		return nil, errNilTpool
   136  	}
   137  
   138  	// Create the persist directory if it does not yet exist.
   139  	err := os.MkdirAll(persistDir, 0700)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	// Create the logger.
   144  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	// Create Contractor using production dependencies.
   150  	return newContractor(cs, &walletBridge{w: wallet}, tpool, hdb, newPersist(persistDir), logger)
   151  }
   152  
   153  // newContractor creates a Contractor using the provided dependencies.
   154  func newContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, p persister, l *persist.Logger) (*Contractor, error) {
   155  	// Create the Contractor object.
   156  	c := &Contractor{
   157  		hdb:     hdb,
   158  		log:     l,
   159  		persist: p,
   160  		tpool:   tp,
   161  		wallet:  w,
   162  
   163  		contracts: make(map[types.FileContractID]modules.RenterContract),
   164  	}
   165  
   166  	// Load the prior persistence structures.
   167  	err := c.load()
   168  	if err != nil && !os.IsNotExist(err) {
   169  		return nil, err
   170  	}
   171  
   172  	err = cs.ConsensusSetSubscribe(c, c.lastChange)
   173  	if err == modules.ErrInvalidConsensusChangeID {
   174  		c.lastChange = modules.ConsensusChangeBeginning
   175  		// ??? fix things ???
   176  		// subscribe again using the new ID
   177  		err = cs.ConsensusSetSubscribe(c, c.lastChange)
   178  	}
   179  	if err != nil {
   180  		return nil, errors.New("contractor subscription failed: " + err.Error())
   181  	}
   182  
   183  	return c, nil
   184  }