github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/contractor/contractor.go (about)

     1  package contractor
     2  
     3  // TODO: We are in the middle of migrating the contractor to a new concurrency
     4  // model. The contractor should never call out to another package while under a
     5  // lock (except for the proto package). This is because the renter is going to
     6  // start calling contractor methods while holding the renter lock, so we need to
     7  // be absolutely confident that no contractor thread will attempt to grab a
     8  // renter lock.
     9  
    10  import (
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"path/filepath"
    15  	"sync"
    16  
    17  	"github.com/Synthesix/Sia/modules"
    18  	"github.com/Synthesix/Sia/modules/renter/proto"
    19  	"github.com/Synthesix/Sia/persist"
    20  	siasync "github.com/Synthesix/Sia/sync"
    21  	"github.com/Synthesix/Sia/types"
    22  )
    23  
    24  var (
    25  	errNilCS     = errors.New("cannot create contractor with nil consensus set")
    26  	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
    27  	errNilWallet = errors.New("cannot create contractor with nil wallet")
    28  
    29  	// COMPATv1.0.4-lts
    30  	// metricsContractID identifies a special contract that contains aggregate
    31  	// financial metrics from older contractors
    32  	metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'}
    33  )
    34  
    35  // A Contractor negotiates, revises, renews, and provides access to file
    36  // contracts.
    37  type Contractor struct {
    38  	// dependencies
    39  	cs      consensusSet
    40  	deps    modules.Dependencies
    41  	hdb     hostDB
    42  	log     *persist.Logger
    43  	mu      sync.RWMutex
    44  	persist persister
    45  	tg      siasync.ThreadGroup
    46  	tpool   transactionPool
    47  	wallet  wallet
    48  
    49  	// Only one thread should be performing contract maintenance at a time.
    50  	interruptMaintenance chan struct{}
    51  	maintenanceLock      siasync.TryMutex
    52  
    53  	allowance     modules.Allowance
    54  	blockHeight   types.BlockHeight
    55  	currentPeriod types.BlockHeight
    56  	lastChange    modules.ConsensusChangeID
    57  
    58  	downloaders map[types.FileContractID]*hostDownloader
    59  	editors     map[types.FileContractID]*hostEditor
    60  	renewing    map[types.FileContractID]bool // prevent revising during renewal
    61  	revising    map[types.FileContractID]bool // prevent overlapping revisions
    62  
    63  	// The contract utility values are not persisted in any way, instead get
    64  	// set based on the values in the hostdb at startup. During startup, the
    65  	// 'managedMarkContractsUtility' needs to be called so that the utility is
    66  	// set correctly.
    67  	contracts         *proto.ContractSet
    68  	contractUtilities map[types.FileContractID]modules.ContractUtility
    69  	oldContracts      map[types.FileContractID]modules.RenterContract
    70  	renewedIDs        map[types.FileContractID]types.FileContractID
    71  }
    72  
    73  // resolveID returns the ID of the most recent renewal of id.
    74  func (c *Contractor) resolveID(id types.FileContractID) types.FileContractID {
    75  	newID, exists := c.renewedIDs[id]
    76  	for exists {
    77  		id = newID
    78  		newID, exists = c.renewedIDs[id]
    79  	}
    80  	return id
    81  }
    82  
    83  // Allowance returns the current allowance.
    84  func (c *Contractor) Allowance() modules.Allowance {
    85  	c.mu.RLock()
    86  	defer c.mu.RUnlock()
    87  	return c.allowance
    88  }
    89  
    90  // PeriodSpending returns the amount spent on contracts during the current
    91  // billing period.
    92  func (c *Contractor) PeriodSpending() modules.ContractorSpending {
    93  	c.mu.RLock()
    94  	defer c.mu.RUnlock()
    95  
    96  	var spending modules.ContractorSpending
    97  	for _, contract := range c.contracts.ViewAll() {
    98  		spending.ContractSpending = spending.ContractSpending.Add(contract.TotalCost)
    99  		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
   100  		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
   101  		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
   102  		// TODO: fix PreviousContracts
   103  		// for _, pre := range contract.PreviousContracts {
   104  		// 	spending.ContractSpending = spending.ContractSpending.Add(pre.TotalCost)
   105  		// 	spending.DownloadSpending = spending.DownloadSpending.Add(pre.DownloadSpending)
   106  		// 	spending.UploadSpending = spending.UploadSpending.Add(pre.UploadSpending)
   107  		// 	spending.StorageSpending = spending.StorageSpending.Add(pre.StorageSpending)
   108  		// }
   109  	}
   110  	allSpending := spending.ContractSpending.Add(spending.DownloadSpending).Add(spending.UploadSpending).Add(spending.StorageSpending)
   111  
   112  	// If the allowance is smaller than the spending, the unspent funds are 0
   113  	if !(c.allowance.Funds.Cmp(allSpending) < 0) {
   114  		spending.Unspent = c.allowance.Funds.Sub(allSpending)
   115  	}
   116  	return spending
   117  }
   118  
   119  // ContractByID returns the contract with the id specified, if it exists. The
   120  // contract will be resolved if possible to the most recent child contract.
   121  func (c *Contractor) ContractByID(id types.FileContractID) (modules.RenterContract, bool) {
   122  	c.mu.RLock()
   123  	defer c.mu.RUnlock()
   124  	return c.contracts.View(c.resolveID(id))
   125  }
   126  
   127  // Contracts returns the contracts formed by the contractor in the current
   128  // allowance period. Only contracts formed with currently online hosts are
   129  // returned.
   130  func (c *Contractor) Contracts() []modules.RenterContract {
   131  	c.mu.RLock()
   132  	defer c.mu.RUnlock()
   133  	return c.contracts.ViewAll()
   134  }
   135  
   136  // ContractUtility returns the utility fields for the given contract.
   137  func (c *Contractor) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) {
   138  	c.mu.RLock()
   139  	utility, exists := c.contractUtilities[c.resolveID(id)]
   140  	c.mu.RUnlock()
   141  	return utility, exists
   142  }
   143  
   144  // CurrentPeriod returns the height at which the current allowance period
   145  // began.
   146  func (c *Contractor) CurrentPeriod() types.BlockHeight {
   147  	c.mu.RLock()
   148  	defer c.mu.RUnlock()
   149  	return c.currentPeriod
   150  }
   151  
   152  // ResolveID returns the ID of the most recent renewal of id.
   153  func (c *Contractor) ResolveID(id types.FileContractID) types.FileContractID {
   154  	c.mu.RLock()
   155  	newID := c.resolveID(id)
   156  	c.mu.RUnlock()
   157  	return newID
   158  }
   159  
   160  // SetRateLimits sets the bandwidth limits for connections created by the
   161  // contractSet.
   162  func (c *Contractor) SetRateLimits(readBPS, writeBPS int64, packetSize uint64) {
   163  	c.contracts.SetRateLimits(readBPS, writeBPS, packetSize)
   164  }
   165  
   166  // Close closes the Contractor.
   167  func (c *Contractor) Close() error {
   168  	return c.tg.Stop()
   169  }
   170  
   171  // New returns a new Contractor.
   172  func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
   173  	// Check for nil inputs.
   174  	if cs == nil {
   175  		return nil, errNilCS
   176  	}
   177  	if wallet == nil {
   178  		return nil, errNilWallet
   179  	}
   180  	if tpool == nil {
   181  		return nil, errNilTpool
   182  	}
   183  
   184  	// Create the persist directory if it does not yet exist.
   185  	if err := os.MkdirAll(persistDir, 0700); err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	// Convert the old persist file(s), if necessary. This must occur before
   190  	// loading the contract set.
   191  	if err := convertPersist(persistDir); err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// Create the contract set.
   196  	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), modules.ProdDependencies)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	// Create the logger.
   201  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	// Create Contractor using production dependencies.
   207  	return NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies)
   208  }
   209  
   210  // NewCustomContractor creates a Contractor using the provided dependencies.
   211  func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) {
   212  	// Create the Contractor object.
   213  	c := &Contractor{
   214  		cs:      cs,
   215  		deps:    deps,
   216  		hdb:     hdb,
   217  		log:     l,
   218  		persist: p,
   219  		tpool:   tp,
   220  		wallet:  w,
   221  
   222  		interruptMaintenance: make(chan struct{}),
   223  
   224  		contracts:         contractSet,
   225  		downloaders:       make(map[types.FileContractID]*hostDownloader),
   226  		editors:           make(map[types.FileContractID]*hostEditor),
   227  		contractUtilities: make(map[types.FileContractID]modules.ContractUtility),
   228  		oldContracts:      make(map[types.FileContractID]modules.RenterContract),
   229  		renewedIDs:        make(map[types.FileContractID]types.FileContractID),
   230  		renewing:          make(map[types.FileContractID]bool),
   231  		revising:          make(map[types.FileContractID]bool),
   232  	}
   233  
   234  	// Close the contract set and logger upon shutdown.
   235  	c.tg.AfterStop(func() {
   236  		if err := c.contracts.Close(); err != nil {
   237  			c.log.Println("Failed to close contract set:", err)
   238  		}
   239  		if err := c.log.Close(); err != nil {
   240  			fmt.Println("Failed to close the contractor logger:", err)
   241  		}
   242  	})
   243  
   244  	// Load the prior persistence structures.
   245  	err := c.load()
   246  	if err != nil && !os.IsNotExist(err) {
   247  		return nil, err
   248  	}
   249  
   250  	// Mark contract utility.
   251  	c.managedMarkContractsUtility()
   252  
   253  	// Subscribe to the consensus set.
   254  	err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   255  	if err == modules.ErrInvalidConsensusChangeID {
   256  		// Reset the contractor consensus variables and try rescanning.
   257  		c.blockHeight = 0
   258  		c.lastChange = modules.ConsensusChangeBeginning
   259  		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   260  	}
   261  	if err != nil {
   262  		return nil, errors.New("contractor subscription failed: " + err.Error())
   263  	}
   264  	// Unsubscribe from the consensus set upon shutdown.
   265  	c.tg.OnStop(func() {
   266  		cs.Unsubscribe(c)
   267  	})
   268  
   269  	// We may have upgraded persist or resubscribed. Save now so that we don't
   270  	// lose our work.
   271  	c.mu.Lock()
   272  	err = c.save()
   273  	c.mu.Unlock()
   274  	if err != nil {
   275  		return nil, err
   276  	}
   277  
   278  	return c, nil
   279  }