github.com/nebulouslabs/sia@v1.3.7/modules/renter/contractor/allowance.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"reflect"
     6  
     7  	"github.com/NebulousLabs/Sia/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  // TODO: can an Editor or Downloader be used across renewals?
    38  // TODO: will hosts allow renewing the same contract twice?
    39  //
    40  // NOTE: At this time, transaction fees are not counted towards the allowance.
    41  // This means the contractor may spend more than allowance.Funds.
    42  func (c *Contractor) SetAllowance(a modules.Allowance) error {
    43  	if reflect.DeepEqual(a, modules.Allowance{}) {
    44  		return c.managedCancelAllowance()
    45  	}
    46  	if reflect.DeepEqual(a, c.allowance) {
    47  		return nil
    48  	}
    49  
    50  	// sanity checks
    51  	if a.Hosts == 0 {
    52  		return errAllowanceNoHosts
    53  	} else if a.Period == 0 {
    54  		return errAllowanceZeroPeriod
    55  	} else if a.RenewWindow == 0 {
    56  		return ErrAllowanceZeroWindow
    57  	} else if a.RenewWindow >= a.Period {
    58  		return errAllowanceWindowSize
    59  	} else if !c.cs.Synced() {
    60  		return errAllowanceNotSynced
    61  	}
    62  
    63  	c.log.Println("INFO: setting allowance to", a)
    64  	c.mu.Lock()
    65  	// set the current period to the blockheight if the existing allowance is
    66  	// empty
    67  	if reflect.DeepEqual(c.allowance, modules.Allowance{}) {
    68  		c.currentPeriod = c.blockHeight
    69  	}
    70  	c.allowance = a
    71  	err := c.saveSync()
    72  	c.mu.Unlock()
    73  	if err != nil {
    74  		c.log.Println("Unable to save contractor after setting allowance:", err)
    75  	}
    76  
    77  	// Cycle through all contracts and unlock them again since they might have
    78  	// been locked by managedCancelAllowance previously.
    79  	ids := c.staticContracts.IDs()
    80  	for _, id := range ids {
    81  		contract, exists := c.staticContracts.Acquire(id)
    82  		if !exists {
    83  			continue
    84  		}
    85  		utility := contract.Utility()
    86  		utility.Locked = false
    87  		err := contract.UpdateUtility(utility)
    88  		c.staticContracts.Return(contract)
    89  		if err != nil {
    90  			return err
    91  		}
    92  	}
    93  
    94  	// Interrupt any existing maintenance and launch a new round of
    95  	// maintenance.
    96  	c.managedInterruptContractMaintenance()
    97  	go c.threadedContractMaintenance()
    98  	return nil
    99  }
   100  
   101  // managedCancelAllowance handles the special case where the allowance is empty.
   102  func (c *Contractor) managedCancelAllowance() error {
   103  	c.log.Println("INFO: canceling allowance")
   104  	// first need to invalidate any active editors
   105  	// NOTE: this code is the same as in managedRenewContracts
   106  	ids := c.staticContracts.IDs()
   107  	c.mu.Lock()
   108  	for _, id := range ids {
   109  		// we aren't renewing, but we don't want new editors or downloaders to
   110  		// be created
   111  		c.renewing[id] = true
   112  	}
   113  	c.mu.Unlock()
   114  	defer func() {
   115  		c.mu.Lock()
   116  		for _, id := range ids {
   117  			delete(c.renewing, id)
   118  		}
   119  		c.mu.Unlock()
   120  	}()
   121  	for _, id := range ids {
   122  		c.mu.RLock()
   123  		e, eok := c.editors[id]
   124  		c.mu.RUnlock()
   125  		if eok {
   126  			e.invalidate()
   127  		}
   128  	}
   129  
   130  	// Clear out the allowance and save.
   131  	c.mu.Lock()
   132  	c.allowance = modules.Allowance{}
   133  	c.currentPeriod = 0
   134  	err := c.saveSync()
   135  	c.mu.Unlock()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	// Issue an interrupt to any in-progress contract maintenance thread.
   141  	c.managedInterruptContractMaintenance()
   142  
   143  	// Cycle through all contracts and mark them as !goodForRenew and !goodForUpload
   144  	ids = c.staticContracts.IDs()
   145  	for _, id := range ids {
   146  		contract, exists := c.staticContracts.Acquire(id)
   147  		if !exists {
   148  			continue
   149  		}
   150  		utility := contract.Utility()
   151  		utility.GoodForRenew = false
   152  		utility.GoodForUpload = false
   153  		utility.Locked = true
   154  		err := contract.UpdateUtility(utility)
   155  		c.staticContracts.Return(contract)
   156  		if err != nil {
   157  			return err
   158  		}
   159  	}
   160  	return nil
   161  }