github.com/NebulousLabs/Sia@v1.3.7/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/NebulousLabs/Sia/modules" 18 "github.com/NebulousLabs/Sia/modules/renter/proto" 19 "github.com/NebulousLabs/Sia/persist" 20 siasync "github.com/NebulousLabs/Sia/sync" 21 "github.com/NebulousLabs/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 hdb hostDB 41 log *persist.Logger 42 mu sync.RWMutex 43 persist persister 44 staticDeps modules.Dependencies 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 numFailedRenews map[types.FileContractID]types.BlockHeight 61 pubKeysToContractID map[string]types.FileContractID 62 contractIDToPubKey map[types.FileContractID]types.SiaPublicKey 63 renewing map[types.FileContractID]bool // prevent revising during renewal 64 revising map[types.FileContractID]bool // prevent overlapping revisions 65 66 staticContracts *proto.ContractSet 67 oldContracts map[types.FileContractID]modules.RenterContract 68 } 69 70 // Allowance returns the current allowance. 71 func (c *Contractor) Allowance() modules.Allowance { 72 c.mu.RLock() 73 defer c.mu.RUnlock() 74 return c.allowance 75 } 76 77 // PeriodSpending returns the amount spent on contracts during the current 78 // billing period. 79 func (c *Contractor) PeriodSpending() modules.ContractorSpending { 80 c.mu.RLock() 81 defer c.mu.RUnlock() 82 83 var spending modules.ContractorSpending 84 for _, contract := range c.staticContracts.ViewAll() { 85 // Calculate ContractFees 86 spending.ContractFees = spending.ContractFees.Add(contract.ContractFee) 87 spending.ContractFees = spending.ContractFees.Add(contract.TxnFee) 88 spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee) 89 // Calculate TotalAllocated 90 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 91 spending.ContractSpendingDeprecated = spending.TotalAllocated 92 // Calculate Spending 93 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 94 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 95 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 96 } 97 98 // Calculate needed spending to be reported from old contracts 99 for _, contract := range c.oldContracts { 100 host, exist := c.hdb.Host(contract.HostPublicKey) 101 if contract.StartHeight >= c.currentPeriod { 102 // Calculate spending from contracts that were renewed during the current period 103 // Calculate ContractFees 104 spending.ContractFees = spending.ContractFees.Add(contract.ContractFee) 105 spending.ContractFees = spending.ContractFees.Add(contract.TxnFee) 106 spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee) 107 // Calculate TotalAllocated 108 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 109 // Calculate Spending 110 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 111 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 112 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 113 } else if exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight { 114 // Calculate funds that are being withheld in contracts 115 spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds) 116 // Record the largest window size for worst case when reporting the spending 117 if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock { 118 spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay 119 } 120 // Calculate Previous spending 121 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 122 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending) 123 } else { 124 // Calculate Previous spending 125 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 126 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending) 127 } 128 } 129 130 // Calculate amount of spent money to get unspent money. 131 allSpending := spending.ContractFees 132 allSpending = allSpending.Add(spending.DownloadSpending) 133 allSpending = allSpending.Add(spending.UploadSpending) 134 allSpending = allSpending.Add(spending.StorageSpending) 135 if c.allowance.Funds.Cmp(allSpending) >= 0 { 136 spending.Unspent = c.allowance.Funds.Sub(allSpending) 137 } 138 139 return spending 140 } 141 142 // ContractByPublicKey returns the contract with the key specified, if it 143 // exists. The contract will be resolved if possible to the most recent child 144 // contract. 145 func (c *Contractor) ContractByPublicKey(pk types.SiaPublicKey) (modules.RenterContract, bool) { 146 c.mu.RLock() 147 id, ok := c.pubKeysToContractID[string(pk.Key)] 148 c.mu.RUnlock() 149 if !ok { 150 return modules.RenterContract{}, false 151 } 152 return c.staticContracts.View(id) 153 } 154 155 // Contracts returns the contracts formed by the contractor in the current 156 // allowance period. Only contracts formed with currently online hosts are 157 // returned. 158 func (c *Contractor) Contracts() []modules.RenterContract { 159 return c.staticContracts.ViewAll() 160 } 161 162 // OldContracts returns the contracts formed by the contractor that have 163 // expired 164 func (c *Contractor) OldContracts() []modules.RenterContract { 165 c.mu.Lock() 166 defer c.mu.Unlock() 167 contracts := make([]modules.RenterContract, 0, len(c.oldContracts)) 168 for _, c := range c.oldContracts { 169 contracts = append(contracts, c) 170 } 171 return contracts 172 } 173 174 // ContractUtility returns the utility fields for the given contract. 175 func (c *Contractor) ContractUtility(pk types.SiaPublicKey) (modules.ContractUtility, bool) { 176 c.mu.RLock() 177 id, ok := c.pubKeysToContractID[string(pk.Key)] 178 c.mu.RUnlock() 179 if !ok { 180 return modules.ContractUtility{}, false 181 } 182 return c.managedContractUtility(id) 183 } 184 185 // CurrentPeriod returns the height at which the current allowance period 186 // began. 187 func (c *Contractor) CurrentPeriod() types.BlockHeight { 188 c.mu.RLock() 189 defer c.mu.RUnlock() 190 return c.currentPeriod 191 } 192 193 // ResolveIDToPubKey returns the ID of the most recent renewal of id. 194 func (c *Contractor) ResolveIDToPubKey(id types.FileContractID) types.SiaPublicKey { 195 c.mu.RLock() 196 defer c.mu.RUnlock() 197 pk, exists := c.contractIDToPubKey[id] 198 if !exists { 199 panic("renewed should never miss an id") 200 } 201 return pk 202 } 203 204 // RateLimits sets the bandwidth limits for connections created by the 205 // contractSet. 206 func (c *Contractor) RateLimits() (readBPW int64, writeBPS int64, packetSize uint64) { 207 return c.staticContracts.RateLimits() 208 } 209 210 // SetRateLimits sets the bandwidth limits for connections created by the 211 // contractSet. 212 func (c *Contractor) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) { 213 c.staticContracts.SetRateLimits(readBPS, writeBPS, packetSize) 214 } 215 216 // Close closes the Contractor. 217 func (c *Contractor) Close() error { 218 return c.tg.Stop() 219 } 220 221 // New returns a new Contractor. 222 func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) { 223 // Check for nil inputs. 224 if cs == nil { 225 return nil, errNilCS 226 } 227 if wallet == nil { 228 return nil, errNilWallet 229 } 230 if tpool == nil { 231 return nil, errNilTpool 232 } 233 234 // Create the persist directory if it does not yet exist. 235 if err := os.MkdirAll(persistDir, 0700); err != nil { 236 return nil, err 237 } 238 239 // Convert the old persist file(s), if necessary. This must occur before 240 // loading the contract set. 241 if err := convertPersist(persistDir); err != nil { 242 return nil, err 243 } 244 245 // Create the contract set. 246 contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"), modules.ProdDependencies) 247 if err != nil { 248 return nil, err 249 } 250 // Create the logger. 251 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log")) 252 if err != nil { 253 return nil, err 254 } 255 256 // Create Contractor using production dependencies. 257 return NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies) 258 } 259 260 // NewCustomContractor creates a Contractor using the provided dependencies. 261 func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) { 262 // Create the Contractor object. 263 c := &Contractor{ 264 cs: cs, 265 staticDeps: deps, 266 hdb: hdb, 267 log: l, 268 persist: p, 269 tpool: tp, 270 wallet: w, 271 272 interruptMaintenance: make(chan struct{}), 273 274 staticContracts: contractSet, 275 downloaders: make(map[types.FileContractID]*hostDownloader), 276 editors: make(map[types.FileContractID]*hostEditor), 277 oldContracts: make(map[types.FileContractID]modules.RenterContract), 278 contractIDToPubKey: make(map[types.FileContractID]types.SiaPublicKey), 279 pubKeysToContractID: make(map[string]types.FileContractID), 280 renewing: make(map[types.FileContractID]bool), 281 revising: make(map[types.FileContractID]bool), 282 } 283 284 // Close the contract set and logger upon shutdown. 285 c.tg.AfterStop(func() { 286 if err := c.staticContracts.Close(); err != nil { 287 c.log.Println("Failed to close contract set:", err) 288 } 289 if err := c.log.Close(); err != nil { 290 fmt.Println("Failed to close the contractor logger:", err) 291 } 292 }) 293 294 // Load the prior persistence structures. 295 err := c.load() 296 if err != nil && !os.IsNotExist(err) { 297 return nil, err 298 } 299 300 // Subscribe to the consensus set. 301 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 302 if err == modules.ErrInvalidConsensusChangeID { 303 // Reset the contractor consensus variables and try rescanning. 304 c.blockHeight = 0 305 c.lastChange = modules.ConsensusChangeBeginning 306 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 307 } 308 if err != nil { 309 return nil, errors.New("contractor subscription failed: " + err.Error()) 310 } 311 // Unsubscribe from the consensus set upon shutdown. 312 c.tg.OnStop(func() { 313 cs.Unsubscribe(c) 314 }) 315 316 // We may have upgraded persist or resubscribed. Save now so that we don't 317 // lose our work. 318 c.mu.Lock() 319 err = c.save() 320 c.mu.Unlock() 321 if err != nil { 322 return nil, err 323 } 324 325 // Initialize the contractIDToPubKey map 326 for _, contract := range c.oldContracts { 327 c.contractIDToPubKey[contract.ID] = contract.HostPublicKey 328 c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID 329 } 330 for _, contract := range c.staticContracts.ViewAll() { 331 c.contractIDToPubKey[contract.ID] = contract.HostPublicKey 332 c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID 333 } 334 335 return c, nil 336 }