github.com/NebulousLabs/Sia@v1.3.7/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/NebulousLabs/Sia/modules"
    18  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    19  	"github.com/NebulousLabs/Sia/persist"
    20  	siasync "github.com/NebulousLabs/Sia/sync"
    21  	"github.com/NebulousLabs/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  	hdb        hostDB
    41  	log        *persist.Logger
    42  	mu         sync.RWMutex
    43  	persist    persister
    44  	staticDeps modules.Dependencies
    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  	numFailedRenews     map[types.FileContractID]types.BlockHeight
    61  	pubKeysToContractID map[string]types.FileContractID
    62  	contractIDToPubKey  map[types.FileContractID]types.SiaPublicKey
    63  	renewing            map[types.FileContractID]bool // prevent revising during renewal
    64  	revising            map[types.FileContractID]bool // prevent overlapping revisions
    65  
    66  	staticContracts *proto.ContractSet
    67  	oldContracts    map[types.FileContractID]modules.RenterContract
    68  }
    69  
    70  // Allowance returns the current allowance.
    71  func (c *Contractor) Allowance() modules.Allowance {
    72  	c.mu.RLock()
    73  	defer c.mu.RUnlock()
    74  	return c.allowance
    75  }
    76  
    77  // PeriodSpending returns the amount spent on contracts during the current
    78  // billing period.
    79  func (c *Contractor) PeriodSpending() modules.ContractorSpending {
    80  	c.mu.RLock()
    81  	defer c.mu.RUnlock()
    82  
    83  	var spending modules.ContractorSpending
    84  	for _, contract := range c.staticContracts.ViewAll() {
    85  		// Calculate ContractFees
    86  		spending.ContractFees = spending.ContractFees.Add(contract.ContractFee)
    87  		spending.ContractFees = spending.ContractFees.Add(contract.TxnFee)
    88  		spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee)
    89  		// Calculate TotalAllocated
    90  		spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
    91  		spending.ContractSpendingDeprecated = spending.TotalAllocated
    92  		// Calculate Spending
    93  		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
    94  		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
    95  		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
    96  	}
    97  
    98  	// Calculate needed spending to be reported from old contracts
    99  	for _, contract := range c.oldContracts {
   100  		host, exist := c.hdb.Host(contract.HostPublicKey)
   101  		if contract.StartHeight >= c.currentPeriod {
   102  			// Calculate spending from contracts that were renewed during the current period
   103  			// Calculate ContractFees
   104  			spending.ContractFees = spending.ContractFees.Add(contract.ContractFee)
   105  			spending.ContractFees = spending.ContractFees.Add(contract.TxnFee)
   106  			spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee)
   107  			// Calculate TotalAllocated
   108  			spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
   109  			// Calculate Spending
   110  			spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
   111  			spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
   112  			spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
   113  		} else if exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight {
   114  			// Calculate funds that are being withheld in contracts
   115  			spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds)
   116  			// Record the largest window size for worst case when reporting the spending
   117  			if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock {
   118  				spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay
   119  			}
   120  			// Calculate Previous spending
   121  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   122  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending)
   123  		} else {
   124  			// Calculate Previous spending
   125  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   126  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending)
   127  		}
   128  	}
   129  
   130  	// Calculate amount of spent money to get unspent money.
   131  	allSpending := spending.ContractFees
   132  	allSpending = allSpending.Add(spending.DownloadSpending)
   133  	allSpending = allSpending.Add(spending.UploadSpending)
   134  	allSpending = allSpending.Add(spending.StorageSpending)
   135  	if c.allowance.Funds.Cmp(allSpending) >= 0 {
   136  		spending.Unspent = c.allowance.Funds.Sub(allSpending)
   137  	}
   138  
   139  	return spending
   140  }
   141  
   142  // ContractByPublicKey returns the contract with the key specified, if it
   143  // exists. The contract will be resolved if possible to the most recent child
   144  // contract.
   145  func (c *Contractor) ContractByPublicKey(pk types.SiaPublicKey) (modules.RenterContract, bool) {
   146  	c.mu.RLock()
   147  	id, ok := c.pubKeysToContractID[string(pk.Key)]
   148  	c.mu.RUnlock()
   149  	if !ok {
   150  		return modules.RenterContract{}, false
   151  	}
   152  	return c.staticContracts.View(id)
   153  }
   154  
   155  // Contracts returns the contracts formed by the contractor in the current
   156  // allowance period. Only contracts formed with currently online hosts are
   157  // returned.
   158  func (c *Contractor) Contracts() []modules.RenterContract {
   159  	return c.staticContracts.ViewAll()
   160  }
   161  
   162  // OldContracts returns the contracts formed by the contractor that have
   163  // expired
   164  func (c *Contractor) OldContracts() []modules.RenterContract {
   165  	c.mu.Lock()
   166  	defer c.mu.Unlock()
   167  	contracts := make([]modules.RenterContract, 0, len(c.oldContracts))
   168  	for _, c := range c.oldContracts {
   169  		contracts = append(contracts, c)
   170  	}
   171  	return contracts
   172  }
   173  
   174  // ContractUtility returns the utility fields for the given contract.
   175  func (c *Contractor) ContractUtility(pk types.SiaPublicKey) (modules.ContractUtility, bool) {
   176  	c.mu.RLock()
   177  	id, ok := c.pubKeysToContractID[string(pk.Key)]
   178  	c.mu.RUnlock()
   179  	if !ok {
   180  		return modules.ContractUtility{}, false
   181  	}
   182  	return c.managedContractUtility(id)
   183  }
   184  
   185  // CurrentPeriod returns the height at which the current allowance period
   186  // began.
   187  func (c *Contractor) CurrentPeriod() types.BlockHeight {
   188  	c.mu.RLock()
   189  	defer c.mu.RUnlock()
   190  	return c.currentPeriod
   191  }
   192  
   193  // ResolveIDToPubKey returns the ID of the most recent renewal of id.
   194  func (c *Contractor) ResolveIDToPubKey(id types.FileContractID) types.SiaPublicKey {
   195  	c.mu.RLock()
   196  	defer c.mu.RUnlock()
   197  	pk, exists := c.contractIDToPubKey[id]
   198  	if !exists {
   199  		panic("renewed should never miss an id")
   200  	}
   201  	return pk
   202  }
   203  
   204  // RateLimits sets the bandwidth limits for connections created by the
   205  // contractSet.
   206  func (c *Contractor) RateLimits() (readBPW int64, writeBPS int64, packetSize uint64) {
   207  	return c.staticContracts.RateLimits()
   208  }
   209  
   210  // SetRateLimits sets the bandwidth limits for connections created by the
   211  // contractSet.
   212  func (c *Contractor) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) {
   213  	c.staticContracts.SetRateLimits(readBPS, writeBPS, packetSize)
   214  }
   215  
   216  // Close closes the Contractor.
   217  func (c *Contractor) Close() error {
   218  	return c.tg.Stop()
   219  }
   220  
   221  // New returns a new Contractor.
   222  func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
   223  	// Check for nil inputs.
   224  	if cs == nil {
   225  		return nil, errNilCS
   226  	}
   227  	if wallet == nil {
   228  		return nil, errNilWallet
   229  	}
   230  	if tpool == nil {
   231  		return nil, errNilTpool
   232  	}
   233  
   234  	// Create the persist directory if it does not yet exist.
   235  	if err := os.MkdirAll(persistDir, 0700); err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	// Convert the old persist file(s), if necessary. This must occur before
   240  	// loading the contract set.
   241  	if err := convertPersist(persistDir); err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	// Create the contract set.
   246  	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), modules.ProdDependencies)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	// Create the logger.
   251  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	// Create Contractor using production dependencies.
   257  	return NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies)
   258  }
   259  
   260  // NewCustomContractor creates a Contractor using the provided dependencies.
   261  func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) {
   262  	// Create the Contractor object.
   263  	c := &Contractor{
   264  		cs:         cs,
   265  		staticDeps: deps,
   266  		hdb:        hdb,
   267  		log:        l,
   268  		persist:    p,
   269  		tpool:      tp,
   270  		wallet:     w,
   271  
   272  		interruptMaintenance: make(chan struct{}),
   273  
   274  		staticContracts:     contractSet,
   275  		downloaders:         make(map[types.FileContractID]*hostDownloader),
   276  		editors:             make(map[types.FileContractID]*hostEditor),
   277  		oldContracts:        make(map[types.FileContractID]modules.RenterContract),
   278  		contractIDToPubKey:  make(map[types.FileContractID]types.SiaPublicKey),
   279  		pubKeysToContractID: make(map[string]types.FileContractID),
   280  		renewing:            make(map[types.FileContractID]bool),
   281  		revising:            make(map[types.FileContractID]bool),
   282  	}
   283  
   284  	// Close the contract set and logger upon shutdown.
   285  	c.tg.AfterStop(func() {
   286  		if err := c.staticContracts.Close(); err != nil {
   287  			c.log.Println("Failed to close contract set:", err)
   288  		}
   289  		if err := c.log.Close(); err != nil {
   290  			fmt.Println("Failed to close the contractor logger:", err)
   291  		}
   292  	})
   293  
   294  	// Load the prior persistence structures.
   295  	err := c.load()
   296  	if err != nil && !os.IsNotExist(err) {
   297  		return nil, err
   298  	}
   299  
   300  	// Subscribe to the consensus set.
   301  	err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   302  	if err == modules.ErrInvalidConsensusChangeID {
   303  		// Reset the contractor consensus variables and try rescanning.
   304  		c.blockHeight = 0
   305  		c.lastChange = modules.ConsensusChangeBeginning
   306  		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   307  	}
   308  	if err != nil {
   309  		return nil, errors.New("contractor subscription failed: " + err.Error())
   310  	}
   311  	// Unsubscribe from the consensus set upon shutdown.
   312  	c.tg.OnStop(func() {
   313  		cs.Unsubscribe(c)
   314  	})
   315  
   316  	// We may have upgraded persist or resubscribed. Save now so that we don't
   317  	// lose our work.
   318  	c.mu.Lock()
   319  	err = c.save()
   320  	c.mu.Unlock()
   321  	if err != nil {
   322  		return nil, err
   323  	}
   324  
   325  	// Initialize the contractIDToPubKey map
   326  	for _, contract := range c.oldContracts {
   327  		c.contractIDToPubKey[contract.ID] = contract.HostPublicKey
   328  		c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID
   329  	}
   330  	for _, contract := range c.staticContracts.ViewAll() {
   331  		c.contractIDToPubKey[contract.ID] = contract.HostPublicKey
   332  		c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID
   333  	}
   334  
   335  	return c, nil
   336  }