github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/modules/renter/contractor/contractor.go (about) 1 package contractor 2 3 // TODO: We are in the middle of migrating the contractor to a new concurrency 4 // model. The contractor should never call out to another package while under a 5 // lock (except for the proto package). This is because the renter is going to 6 // start calling contractor methods while holding the renter lock, so we need to 7 // be absolutely confident that no contractor thread will attempt to grab a 8 // renter lock. 9 10 import ( 11 "errors" 12 "fmt" 13 "os" 14 "path/filepath" 15 "sync" 16 17 "github.com/Synthesix/Sia/modules" 18 "github.com/Synthesix/Sia/modules/renter/proto" 19 "github.com/Synthesix/Sia/persist" 20 siasync "github.com/Synthesix/Sia/sync" 21 "github.com/Synthesix/Sia/types" 22 ) 23 24 var ( 25 errNilCS = errors.New("cannot create contractor with nil consensus set") 26 errNilTpool = errors.New("cannot create contractor with nil transaction pool") 27 errNilWallet = errors.New("cannot create contractor with nil wallet") 28 29 // COMPATv1.0.4-lts 30 // metricsContractID identifies a special contract that contains aggregate 31 // financial metrics from older contractors 32 metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'} 33 ) 34 35 // A Contractor negotiates, revises, renews, and provides access to file 36 // contracts. 37 type Contractor struct { 38 // dependencies 39 cs consensusSet 40 deps modules.Dependencies 41 hdb hostDB 42 log *persist.Logger 43 mu sync.RWMutex 44 persist persister 45 tg siasync.ThreadGroup 46 tpool transactionPool 47 wallet wallet 48 49 // Only one thread should be performing contract maintenance at a time. 50 interruptMaintenance chan struct{} 51 maintenanceLock siasync.TryMutex 52 53 allowance modules.Allowance 54 blockHeight types.BlockHeight 55 currentPeriod types.BlockHeight 56 lastChange modules.ConsensusChangeID 57 58 downloaders map[types.FileContractID]*hostDownloader 59 editors map[types.FileContractID]*hostEditor 60 renewing map[types.FileContractID]bool // prevent revising during renewal 61 revising map[types.FileContractID]bool // prevent overlapping revisions 62 63 // The contract utility values are not persisted in any way, instead get 64 // set based on the values in the hostdb at startup. During startup, the 65 // 'managedMarkContractsUtility' needs to be called so that the utility is 66 // set correctly. 67 contracts *proto.ContractSet 68 contractUtilities map[types.FileContractID]modules.ContractUtility 69 oldContracts map[types.FileContractID]modules.RenterContract 70 renewedIDs map[types.FileContractID]types.FileContractID 71 } 72 73 // resolveID returns the ID of the most recent renewal of id. 74 func (c *Contractor) resolveID(id types.FileContractID) types.FileContractID { 75 newID, exists := c.renewedIDs[id] 76 for exists { 77 id = newID 78 newID, exists = c.renewedIDs[id] 79 } 80 return id 81 } 82 83 // Allowance returns the current allowance. 84 func (c *Contractor) Allowance() modules.Allowance { 85 c.mu.RLock() 86 defer c.mu.RUnlock() 87 return c.allowance 88 } 89 90 // PeriodSpending returns the amount spent on contracts during the current 91 // billing period. 92 func (c *Contractor) PeriodSpending() modules.ContractorSpending { 93 c.mu.RLock() 94 defer c.mu.RUnlock() 95 96 var spending modules.ContractorSpending 97 for _, contract := range c.contracts.ViewAll() { 98 spending.ContractSpending = spending.ContractSpending.Add(contract.TotalCost) 99 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 100 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 101 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 102 // TODO: fix PreviousContracts 103 // for _, pre := range contract.PreviousContracts { 104 // spending.ContractSpending = spending.ContractSpending.Add(pre.TotalCost) 105 // spending.DownloadSpending = spending.DownloadSpending.Add(pre.DownloadSpending) 106 // spending.UploadSpending = spending.UploadSpending.Add(pre.UploadSpending) 107 // spending.StorageSpending = spending.StorageSpending.Add(pre.StorageSpending) 108 // } 109 } 110 allSpending := spending.ContractSpending.Add(spending.DownloadSpending).Add(spending.UploadSpending).Add(spending.StorageSpending) 111 112 // If the allowance is smaller than the spending, the unspent funds are 0 113 if !(c.allowance.Funds.Cmp(allSpending) < 0) { 114 spending.Unspent = c.allowance.Funds.Sub(allSpending) 115 } 116 return spending 117 } 118 119 // ContractByID returns the contract with the id specified, if it exists. The 120 // contract will be resolved if possible to the most recent child contract. 121 func (c *Contractor) ContractByID(id types.FileContractID) (modules.RenterContract, bool) { 122 c.mu.RLock() 123 defer c.mu.RUnlock() 124 return c.contracts.View(c.resolveID(id)) 125 } 126 127 // Contracts returns the contracts formed by the contractor in the current 128 // allowance period. Only contracts formed with currently online hosts are 129 // returned. 130 func (c *Contractor) Contracts() []modules.RenterContract { 131 c.mu.RLock() 132 defer c.mu.RUnlock() 133 return c.contracts.ViewAll() 134 } 135 136 // ContractUtility returns the utility fields for the given contract. 137 func (c *Contractor) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) { 138 c.mu.RLock() 139 utility, exists := c.contractUtilities[c.resolveID(id)] 140 c.mu.RUnlock() 141 return utility, exists 142 } 143 144 // CurrentPeriod returns the height at which the current allowance period 145 // began. 146 func (c *Contractor) CurrentPeriod() types.BlockHeight { 147 c.mu.RLock() 148 defer c.mu.RUnlock() 149 return c.currentPeriod 150 } 151 152 // ResolveID returns the ID of the most recent renewal of id. 153 func (c *Contractor) ResolveID(id types.FileContractID) types.FileContractID { 154 c.mu.RLock() 155 newID := c.resolveID(id) 156 c.mu.RUnlock() 157 return newID 158 } 159 160 // SetRateLimits sets the bandwidth limits for connections created by the 161 // contractSet. 162 func (c *Contractor) SetRateLimits(readBPS, writeBPS int64, packetSize uint64) { 163 c.contracts.SetRateLimits(readBPS, writeBPS, packetSize) 164 } 165 166 // Close closes the Contractor. 167 func (c *Contractor) Close() error { 168 return c.tg.Stop() 169 } 170 171 // New returns a new Contractor. 172 func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) { 173 // Check for nil inputs. 174 if cs == nil { 175 return nil, errNilCS 176 } 177 if wallet == nil { 178 return nil, errNilWallet 179 } 180 if tpool == nil { 181 return nil, errNilTpool 182 } 183 184 // Create the persist directory if it does not yet exist. 185 if err := os.MkdirAll(persistDir, 0700); err != nil { 186 return nil, err 187 } 188 189 // Convert the old persist file(s), if necessary. This must occur before 190 // loading the contract set. 191 if err := convertPersist(persistDir); err != nil { 192 return nil, err 193 } 194 195 // Create the contract set. 196 contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), modules.ProdDependencies) 197 if err != nil { 198 return nil, err 199 } 200 // Create the logger. 201 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log")) 202 if err != nil { 203 return nil, err 204 } 205 206 // Create Contractor using production dependencies. 207 return NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies) 208 } 209 210 // NewCustomContractor creates a Contractor using the provided dependencies. 211 func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) { 212 // Create the Contractor object. 213 c := &Contractor{ 214 cs: cs, 215 deps: deps, 216 hdb: hdb, 217 log: l, 218 persist: p, 219 tpool: tp, 220 wallet: w, 221 222 interruptMaintenance: make(chan struct{}), 223 224 contracts: contractSet, 225 downloaders: make(map[types.FileContractID]*hostDownloader), 226 editors: make(map[types.FileContractID]*hostEditor), 227 contractUtilities: make(map[types.FileContractID]modules.ContractUtility), 228 oldContracts: make(map[types.FileContractID]modules.RenterContract), 229 renewedIDs: make(map[types.FileContractID]types.FileContractID), 230 renewing: make(map[types.FileContractID]bool), 231 revising: make(map[types.FileContractID]bool), 232 } 233 234 // Close the contract set and logger upon shutdown. 235 c.tg.AfterStop(func() { 236 if err := c.contracts.Close(); err != nil { 237 c.log.Println("Failed to close contract set:", err) 238 } 239 if err := c.log.Close(); err != nil { 240 fmt.Println("Failed to close the contractor logger:", err) 241 } 242 }) 243 244 // Load the prior persistence structures. 245 err := c.load() 246 if err != nil && !os.IsNotExist(err) { 247 return nil, err 248 } 249 250 // Mark contract utility. 251 c.managedMarkContractsUtility() 252 253 // Subscribe to the consensus set. 254 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 255 if err == modules.ErrInvalidConsensusChangeID { 256 // Reset the contractor consensus variables and try rescanning. 257 c.blockHeight = 0 258 c.lastChange = modules.ConsensusChangeBeginning 259 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 260 } 261 if err != nil { 262 return nil, errors.New("contractor subscription failed: " + err.Error()) 263 } 264 // Unsubscribe from the consensus set upon shutdown. 265 c.tg.OnStop(func() { 266 cs.Unsubscribe(c) 267 }) 268 269 // We may have upgraded persist or resubscribed. Save now so that we don't 270 // lose our work. 271 c.mu.Lock() 272 err = c.save() 273 c.mu.Unlock() 274 if err != nil { 275 return nil, err 276 } 277 278 return c, nil 279 }