gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/allowance.go (about)

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