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 }