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 }