github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/contractor/contractor.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sync"
     9  
    10  	"SiaPrime/modules"
    11  	"SiaPrime/modules/renter/proto"
    12  	"SiaPrime/persist"
    13  	siasync "SiaPrime/sync"
    14  	"SiaPrime/types"
    15  )
    16  
    17  var (
    18  	errNilCS     = errors.New("cannot create contractor with nil consensus set")
    19  	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
    20  	errNilWallet = errors.New("cannot create contractor with nil wallet")
    21  
    22  	// COMPATv1.0.4-lts
    23  	// metricsContractID identifies a special contract that contains aggregate
    24  	// financial metrics from older contractors
    25  	metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'}
    26  )
    27  
    28  // A Contractor negotiates, revises, renews, and provides access to file
    29  // contracts.
    30  type Contractor struct {
    31  	// dependencies
    32  	cs         consensusSet
    33  	hdb        hostDB
    34  	log        *persist.Logger
    35  	mu         sync.RWMutex
    36  	persist    persister
    37  	staticDeps modules.Dependencies
    38  	tg         siasync.ThreadGroup
    39  	tpool      transactionPool
    40  	wallet     wallet
    41  
    42  	// Only one thread should be performing contract maintenance at a time.
    43  	interruptMaintenance chan struct{}
    44  	maintenanceLock      siasync.TryMutex
    45  
    46  	allowance     modules.Allowance
    47  	blockHeight   types.BlockHeight
    48  	currentPeriod types.BlockHeight
    49  	lastChange    modules.ConsensusChangeID
    50  
    51  	downloaders         map[types.FileContractID]*hostDownloader
    52  	editors             map[types.FileContractID]*hostEditor
    53  	numFailedRenews     map[types.FileContractID]types.BlockHeight
    54  	pubKeysToContractID map[string]types.FileContractID
    55  	contractIDToPubKey  map[types.FileContractID]types.SiaPublicKey
    56  	renewing            map[types.FileContractID]bool // prevent revising during renewal
    57  
    58  	// renewedFrom links the new contract's ID to the old contract's ID
    59  	// renewedTo links the old contract's ID to the new contract's ID
    60  	staticContracts *proto.ContractSet
    61  	oldContracts    map[types.FileContractID]modules.RenterContract
    62  	renewedFrom     map[types.FileContractID]types.FileContractID
    63  	renewedTo       map[types.FileContractID]types.FileContractID
    64  }
    65  
    66  // Allowance returns the current allowance.
    67  func (c *Contractor) Allowance() modules.Allowance {
    68  	c.mu.RLock()
    69  	defer c.mu.RUnlock()
    70  	return c.allowance
    71  }
    72  
    73  // PeriodSpending returns the amount spent on contracts during the current
    74  // billing period.
    75  func (c *Contractor) PeriodSpending() modules.ContractorSpending {
    76  	allContracts := c.staticContracts.ViewAll()
    77  	c.mu.RLock()
    78  	defer c.mu.RUnlock()
    79  
    80  	var spending modules.ContractorSpending
    81  	for _, contract := range allContracts {
    82  		// Calculate ContractFees
    83  		spending.ContractFees = spending.ContractFees.Add(contract.ContractFee)
    84  		spending.ContractFees = spending.ContractFees.Add(contract.TxnFee)
    85  		spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee)
    86  		// Calculate TotalAllocated
    87  		spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
    88  		spending.ContractSpendingDeprecated = spending.TotalAllocated
    89  		// Calculate Spending
    90  		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
    91  		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
    92  		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
    93  	}
    94  
    95  	// Calculate needed spending to be reported from old contracts
    96  	for _, contract := range c.oldContracts {
    97  		host, exist := c.hdb.Host(contract.HostPublicKey)
    98  		if contract.StartHeight >= c.currentPeriod {
    99  			// Calculate spending from contracts that were renewed during the current period
   100  			// Calculate ContractFees
   101  			spending.ContractFees = spending.ContractFees.Add(contract.ContractFee)
   102  			spending.ContractFees = spending.ContractFees.Add(contract.TxnFee)
   103  			spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee)
   104  			// Calculate TotalAllocated
   105  			spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost)
   106  			// Calculate Spending
   107  			spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
   108  			spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
   109  			spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
   110  		} else if exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight {
   111  			// Calculate funds that are being withheld in contracts
   112  			spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds)
   113  			// Record the largest window size for worst case when reporting the spending
   114  			if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock {
   115  				spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay
   116  			}
   117  			// Calculate Previous spending
   118  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   119  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending)
   120  		} else {
   121  			// Calculate Previous spending
   122  			spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee).
   123  				Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending)
   124  		}
   125  	}
   126  
   127  	// Calculate amount of spent money to get unspent money.
   128  	allSpending := spending.ContractFees
   129  	allSpending = allSpending.Add(spending.DownloadSpending)
   130  	allSpending = allSpending.Add(spending.UploadSpending)
   131  	allSpending = allSpending.Add(spending.StorageSpending)
   132  	if c.allowance.Funds.Cmp(allSpending) >= 0 {
   133  		spending.Unspent = c.allowance.Funds.Sub(allSpending)
   134  	}
   135  
   136  	return spending
   137  }
   138  
   139  // CurrentPeriod returns the height at which the current allowance period
   140  // began.
   141  func (c *Contractor) CurrentPeriod() types.BlockHeight {
   142  	c.mu.RLock()
   143  	defer c.mu.RUnlock()
   144  	return c.currentPeriod
   145  }
   146  
   147  // RateLimits sets the bandwidth limits for connections created by the
   148  // contractSet.
   149  func (c *Contractor) RateLimits() (readBPW int64, writeBPS int64, packetSize uint64) {
   150  	return c.staticContracts.RateLimits()
   151  }
   152  
   153  // SetRateLimits sets the bandwidth limits for connections created by the
   154  // contractSet.
   155  func (c *Contractor) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) {
   156  	c.staticContracts.SetRateLimits(readBPS, writeBPS, packetSize)
   157  }
   158  
   159  // Close closes the Contractor.
   160  func (c *Contractor) Close() error {
   161  	return c.tg.Stop()
   162  }
   163  
   164  // New returns a new Contractor.
   165  func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
   166  	// Check for nil inputs.
   167  	if cs == nil {
   168  		return nil, errNilCS
   169  	}
   170  	if wallet == nil {
   171  		return nil, errNilWallet
   172  	}
   173  	if tpool == nil {
   174  		return nil, errNilTpool
   175  	}
   176  
   177  	// Create the persist directory if it does not yet exist.
   178  	if err := os.MkdirAll(persistDir, 0700); err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	// Convert the old persist file(s), if necessary. This must occur before
   183  	// loading the contract set.
   184  	if err := convertPersist(persistDir); err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	// Create the contract set.
   189  	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), modules.ProdDependencies)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	// Create the logger.
   194  	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	// Create Contractor using production dependencies.
   200  	return NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies)
   201  }
   202  
   203  // NewCustomContractor creates a Contractor using the provided dependencies.
   204  func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) {
   205  	// Create the Contractor object.
   206  	c := &Contractor{
   207  		cs:         cs,
   208  		staticDeps: deps,
   209  		hdb:        hdb,
   210  		log:        l,
   211  		persist:    p,
   212  		tpool:      tp,
   213  		wallet:     w,
   214  
   215  		interruptMaintenance: make(chan struct{}),
   216  
   217  		staticContracts:     contractSet,
   218  		downloaders:         make(map[types.FileContractID]*hostDownloader),
   219  		editors:             make(map[types.FileContractID]*hostEditor),
   220  		oldContracts:        make(map[types.FileContractID]modules.RenterContract),
   221  		contractIDToPubKey:  make(map[types.FileContractID]types.SiaPublicKey),
   222  		pubKeysToContractID: make(map[string]types.FileContractID),
   223  		renewing:            make(map[types.FileContractID]bool),
   224  		renewedFrom:         make(map[types.FileContractID]types.FileContractID),
   225  		renewedTo:           make(map[types.FileContractID]types.FileContractID),
   226  	}
   227  
   228  	// Close the contract set and logger upon shutdown.
   229  	c.tg.AfterStop(func() {
   230  		if err := c.staticContracts.Close(); err != nil {
   231  			c.log.Println("Failed to close contract set:", err)
   232  		}
   233  		if err := c.log.Close(); err != nil {
   234  			fmt.Println("Failed to close the contractor logger:", err)
   235  		}
   236  	})
   237  
   238  	// Load the prior persistence structures.
   239  	err := c.load()
   240  	if err != nil && !os.IsNotExist(err) {
   241  		return nil, err
   242  	}
   243  
   244  	// Subscribe to the consensus set.
   245  	err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   246  	if err == modules.ErrInvalidConsensusChangeID {
   247  		// Reset the contractor consensus variables and try rescanning.
   248  		c.blockHeight = 0
   249  		c.lastChange = modules.ConsensusChangeBeginning
   250  		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
   251  	}
   252  	if err != nil {
   253  		return nil, errors.New("contractor subscription failed: " + err.Error())
   254  	}
   255  	// Unsubscribe from the consensus set upon shutdown.
   256  	c.tg.OnStop(func() {
   257  		cs.Unsubscribe(c)
   258  	})
   259  
   260  	// We may have upgraded persist or resubscribed. Save now so that we don't
   261  	// lose our work.
   262  	c.mu.Lock()
   263  	err = c.save()
   264  	c.mu.Unlock()
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	// Initialize the contractIDToPubKey map
   270  	for _, contract := range c.oldContracts {
   271  		c.contractIDToPubKey[contract.ID] = contract.HostPublicKey
   272  		c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID
   273  	}
   274  	for _, contract := range c.staticContracts.ViewAll() {
   275  		c.contractIDToPubKey[contract.ID] = contract.HostPublicKey
   276  		c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID
   277  	}
   278  
   279  	// Update the allowance in the hostdb with the one that was loaded from
   280  	// disk.
   281  	err = c.hdb.SetAllowance(c.allowance)
   282  	if err != nil {
   283  		return nil, err
   284  	}
   285  	return c, nil
   286  }