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 }