gitlab.com/jokerrs1/Sia@v1.3.2/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  	// Interrupt any existing maintenance and launch a new round of
    78  	// maintenance.
    79  	c.managedInterruptContractMaintenance()
    80  	go c.threadedContractMaintenance()
    81  	return nil
    82  }
    83  
    84  // managedCancelAllowance handles the special case where the allowance is empty.
    85  func (c *Contractor) managedCancelAllowance() error {
    86  	c.log.Println("INFO: canceling allowance")
    87  	// first need to invalidate any active editors/downloaders
    88  	// NOTE: this code is the same as in managedRenewContracts
    89  	c.mu.Lock()
    90  	ids := c.contracts.IDs()
    91  	for _, id := range ids {
    92  		// we aren't renewing, but we don't want new editors or downloaders to
    93  		// be created
    94  		c.renewing[id] = true
    95  	}
    96  	c.mu.Unlock()
    97  	defer func() {
    98  		c.mu.Lock()
    99  		for _, id := range ids {
   100  			delete(c.renewing, id)
   101  		}
   102  		c.mu.Unlock()
   103  	}()
   104  	for _, id := range ids {
   105  		c.mu.RLock()
   106  		e, eok := c.editors[id]
   107  		d, dok := c.downloaders[id]
   108  		c.mu.RUnlock()
   109  		if eok {
   110  			e.invalidate()
   111  		}
   112  		if dok {
   113  			d.invalidate()
   114  		}
   115  	}
   116  
   117  	// Clear out the allowance and save.
   118  	c.mu.Lock()
   119  	c.allowance = modules.Allowance{}
   120  	c.currentPeriod = 0
   121  	err := c.saveSync()
   122  	c.mu.Unlock()
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	// Issue an interrupt to any in-progress contract maintenance thread.
   128  	c.managedInterruptContractMaintenance()
   129  
   130  	// Cycle through all contracts and delete them.
   131  	ids = c.contracts.IDs()
   132  	for _, id := range ids {
   133  		contract, exists := c.contracts.Acquire(id)
   134  		if !exists {
   135  			continue
   136  		}
   137  		c.contracts.Delete(contract)
   138  	}
   139  	return nil
   140  }