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