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

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"reflect"
     6  
     7  	"SiaPrime/modules"
     8  )
     9  
    10  var (
    11  	errAllowanceNoHosts    = errors.New("hosts must be non-zero")
    12  	errAllowanceNotSynced  = errors.New("you must be synced to set an allowance")
    13  	errAllowanceWindowSize = errors.New("renew window must be less than period")
    14  	errAllowanceZeroPeriod = errors.New("period must be non-zero")
    15  
    16  	// ErrAllowanceZeroWindow is returned when the caller requests a
    17  	// zero-length renewal window. This will happen if the caller sets the
    18  	// period to 1 block, since RenewWindow := period / 2.
    19  	ErrAllowanceZeroWindow = errors.New("renew window must be non-zero")
    20  )
    21  
    22  // SetAllowance sets the amount of money the Contractor is allowed to spend on
    23  // contracts over a given time period, divided among the number of hosts
    24  // specified. Note that Contractor can start forming contracts as soon as
    25  // SetAllowance is called; that is, it may block.
    26  //
    27  // In most cases, SetAllowance will renew existing contracts instead of
    28  // forming new ones. This preserves the data on those hosts. When this occurs,
    29  // the renewed contracts will atomically replace their previous versions. If
    30  // SetAllowance is interrupted, renewed contracts may be lost, though the
    31  // allocated funds will eventually be returned.
    32  //
    33  // If a is the empty allowance, SetAllowance will archive the current contract
    34  // set. The contracts cannot be used to create Editors or Downloads, and will
    35  // not be renewed.
    36  //
    37  // NOTE: At this time, transaction fees are not counted towards the allowance.
    38  // This means the contractor may spend more than allowance.Funds.
    39  func (c *Contractor) SetAllowance(a modules.Allowance) error {
    40  	if reflect.DeepEqual(a, modules.Allowance{}) {
    41  		return c.managedCancelAllowance()
    42  	}
    43  	if reflect.DeepEqual(a, c.allowance) {
    44  		return nil
    45  	}
    46  
    47  	// sanity checks
    48  	if a.Hosts == 0 {
    49  		return errAllowanceNoHosts
    50  	} else if a.Period == 0 {
    51  		return errAllowanceZeroPeriod
    52  	} else if a.RenewWindow == 0 {
    53  		return ErrAllowanceZeroWindow
    54  	} else if a.RenewWindow >= a.Period {
    55  		return errAllowanceWindowSize
    56  	} else if !c.cs.Synced() {
    57  		return errAllowanceNotSynced
    58  	}
    59  
    60  	c.log.Println("INFO: setting allowance to", a)
    61  	c.mu.Lock()
    62  	// set the current period to the blockheight if the existing allowance is
    63  	// empty. the current period is set in the past by the renew window to make sure
    64  	// the first period aligns with the first period contracts in the same way
    65  	// that future periods align with contracts
    66  	if reflect.DeepEqual(c.allowance, modules.Allowance{}) {
    67  		c.currentPeriod = c.blockHeight - a.RenewWindow
    68  	}
    69  	c.allowance = a
    70  	err := c.saveSync()
    71  	c.mu.Unlock()
    72  	if err != nil {
    73  		c.log.Println("Unable to save contractor after setting allowance:", err)
    74  	}
    75  
    76  	// Cycle through all contracts and unlock them again since they might have
    77  	// been locked by managedCancelAllowance previously.
    78  	ids := c.staticContracts.IDs()
    79  	for _, id := range ids {
    80  		contract, exists := c.staticContracts.Acquire(id)
    81  		if !exists {
    82  			continue
    83  		}
    84  		utility := contract.Utility()
    85  		utility.Locked = false
    86  		err := contract.UpdateUtility(utility)
    87  		c.staticContracts.Return(contract)
    88  		if err != nil {
    89  			return err
    90  		}
    91  	}
    92  
    93  	// We changed the allowance successfully. Update the hostdb.
    94  	err = c.hdb.SetAllowance(a)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	// Interrupt any existing maintenance and launch a new round of
   100  	// maintenance.
   101  	c.managedInterruptContractMaintenance()
   102  	go c.threadedContractMaintenance()
   103  	return nil
   104  }
   105  
   106  // managedCancelAllowance handles the special case where the allowance is empty.
   107  func (c *Contractor) managedCancelAllowance() error {
   108  	c.log.Println("INFO: canceling allowance")
   109  	// first need to invalidate any active editors
   110  	// NOTE: this code is the same as in managedRenewContracts
   111  	ids := c.staticContracts.IDs()
   112  	c.mu.Lock()
   113  	for _, id := range ids {
   114  		// we aren't renewing, but we don't want new editors or downloaders to
   115  		// be created
   116  		c.renewing[id] = true
   117  	}
   118  	c.mu.Unlock()
   119  	defer func() {
   120  		c.mu.Lock()
   121  		for _, id := range ids {
   122  			delete(c.renewing, id)
   123  		}
   124  		c.mu.Unlock()
   125  	}()
   126  	for _, id := range ids {
   127  		c.mu.RLock()
   128  		e, eok := c.editors[id]
   129  		c.mu.RUnlock()
   130  		if eok {
   131  			e.invalidate()
   132  		}
   133  	}
   134  
   135  	// Clear out the allowance and save.
   136  	c.mu.Lock()
   137  	c.allowance = modules.Allowance{}
   138  	c.currentPeriod = 0
   139  	err := c.saveSync()
   140  	c.mu.Unlock()
   141  	if err != nil {
   142  		return err
   143  	}
   144  
   145  	// Issue an interrupt to any in-progress contract maintenance thread.
   146  	c.managedInterruptContractMaintenance()
   147  
   148  	// Cycle through all contracts and mark them as !goodForRenew and !goodForUpload
   149  	ids = c.staticContracts.IDs()
   150  	for _, id := range ids {
   151  		contract, exists := c.staticContracts.Acquire(id)
   152  		if !exists {
   153  			continue
   154  		}
   155  		utility := contract.Utility()
   156  		utility.GoodForRenew = false
   157  		utility.GoodForUpload = false
   158  		utility.Locked = true
   159  		err := contract.UpdateUtility(utility)
   160  		c.staticContracts.Return(contract)
   161  		if err != nil {
   162  			return err
   163  		}
   164  	}
   165  	return nil
   166  }