gitlab.com/jokerrs1/Sia@v1.3.2/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 persist persister 43 mu sync.RWMutex 44 tg siasync.ThreadGroup 45 tpool transactionPool 46 wallet wallet 47 48 // Only one thread should be performing contract maintenance at a time. 49 interruptMaintenance chan struct{} 50 maintenanceLock siasync.TryMutex 51 52 allowance modules.Allowance 53 blockHeight types.BlockHeight 54 currentPeriod types.BlockHeight 55 lastChange modules.ConsensusChangeID 56 57 downloaders map[types.FileContractID]*hostDownloader 58 editors map[types.FileContractID]*hostEditor 59 renewing map[types.FileContractID]bool // prevent revising during renewal 60 revising map[types.FileContractID]bool // prevent overlapping revisions 61 62 // The contract utility values are not persisted in any way, instead get 63 // set based on the values in the hostdb at startup. During startup, the 64 // 'managedMarkContractsUtility' needs to be called so that the utility is 65 // set correctly. 66 contracts *proto.ContractSet 67 contractUtilities map[types.FileContractID]modules.ContractUtility 68 oldContracts map[types.FileContractID]modules.RenterContract 69 renewedIDs map[types.FileContractID]types.FileContractID 70 } 71 72 // resolveID returns the ID of the most recent renewal of id. 73 func (c *Contractor) resolveID(id types.FileContractID) types.FileContractID { 74 newID, exists := c.renewedIDs[id] 75 for exists { 76 id = newID 77 newID, exists = c.renewedIDs[id] 78 } 79 return id 80 } 81 82 // Allowance returns the current allowance. 83 func (c *Contractor) Allowance() modules.Allowance { 84 c.mu.RLock() 85 defer c.mu.RUnlock() 86 return c.allowance 87 } 88 89 // PeriodSpending returns the amount spent on contracts during the current 90 // billing period. 91 func (c *Contractor) PeriodSpending() modules.ContractorSpending { 92 c.mu.RLock() 93 defer c.mu.RUnlock() 94 95 var spending modules.ContractorSpending 96 for _, contract := range c.contracts.ViewAll() { 97 spending.ContractSpending = spending.ContractSpending.Add(contract.TotalCost) 98 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 99 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 100 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 101 // TODO: fix PreviousContracts 102 // for _, pre := range contract.PreviousContracts { 103 // spending.ContractSpending = spending.ContractSpending.Add(pre.TotalCost) 104 // spending.DownloadSpending = spending.DownloadSpending.Add(pre.DownloadSpending) 105 // spending.UploadSpending = spending.UploadSpending.Add(pre.UploadSpending) 106 // spending.StorageSpending = spending.StorageSpending.Add(pre.StorageSpending) 107 // } 108 } 109 allSpending := spending.ContractSpending.Add(spending.DownloadSpending).Add(spending.UploadSpending).Add(spending.StorageSpending) 110 111 // If the allowance is smaller than the spending, the unspent funds are 0 112 if !(c.allowance.Funds.Cmp(allSpending) < 0) { 113 spending.Unspent = c.allowance.Funds.Sub(allSpending) 114 } 115 return spending 116 } 117 118 // ContractByID returns the contract with the id specified, if it exists. The 119 // contract will be resolved if possible to the most recent child contract. 120 func (c *Contractor) ContractByID(id types.FileContractID) (modules.RenterContract, bool) { 121 c.mu.RLock() 122 defer c.mu.RUnlock() 123 return c.contracts.View(c.resolveID(id)) 124 } 125 126 // Contracts returns the contracts formed by the contractor in the current 127 // allowance period. Only contracts formed with currently online hosts are 128 // returned. 129 func (c *Contractor) Contracts() []modules.RenterContract { 130 c.mu.RLock() 131 defer c.mu.RUnlock() 132 return c.contracts.ViewAll() 133 } 134 135 // ContractUtility returns the utility fields for the given contract. 136 func (c *Contractor) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) { 137 c.mu.RLock() 138 utility, exists := c.contractUtilities[c.resolveID(id)] 139 c.mu.RUnlock() 140 return utility, exists 141 } 142 143 // CurrentPeriod returns the height at which the current allowance period 144 // began. 145 func (c *Contractor) CurrentPeriod() types.BlockHeight { 146 c.mu.RLock() 147 defer c.mu.RUnlock() 148 return c.currentPeriod 149 } 150 151 // ResolveID returns the ID of the most recent renewal of id. 152 func (c *Contractor) ResolveID(id types.FileContractID) types.FileContractID { 153 c.mu.RLock() 154 newID := c.resolveID(id) 155 c.mu.RUnlock() 156 return newID 157 } 158 159 // Close closes the Contractor. 160 func (c *Contractor) Close() error { 161 return c.tg.Stop() 162 } 163 164 // New returns a new Contractor. 165 func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) { 166 // Check for nil inputs. 167 if cs == nil { 168 return nil, errNilCS 169 } 170 if wallet == nil { 171 return nil, errNilWallet 172 } 173 if tpool == nil { 174 return nil, errNilTpool 175 } 176 177 // Create the persist directory if it does not yet exist. 178 if err := os.MkdirAll(persistDir, 0700); err != nil { 179 return nil, err 180 } 181 182 // Convert the old persist file(s), if necessary. This must occur before 183 // loading the contract set. 184 if err := convertPersist(persistDir); err != nil { 185 return nil, err 186 } 187 188 // Create the contract set. 189 contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts")) 190 if err != nil { 191 return nil, err 192 } 193 // Create the logger. 194 logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log")) 195 if err != nil { 196 return nil, err 197 } 198 199 // Create Contractor using production dependencies. 200 return newContractor(cs, &walletBridge{w: wallet}, tpool, hdb, contractSet, newPersist(persistDir), logger) 201 } 202 203 // newContractor creates a Contractor using the provided dependencies. 204 func newContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger) (*Contractor, error) { 205 // Create the Contractor object. 206 c := &Contractor{ 207 cs: cs, 208 hdb: hdb, 209 log: l, 210 persist: p, 211 tpool: tp, 212 wallet: w, 213 214 interruptMaintenance: make(chan struct{}), 215 216 contracts: contractSet, 217 downloaders: make(map[types.FileContractID]*hostDownloader), 218 editors: make(map[types.FileContractID]*hostEditor), 219 contractUtilities: make(map[types.FileContractID]modules.ContractUtility), 220 oldContracts: make(map[types.FileContractID]modules.RenterContract), 221 renewedIDs: make(map[types.FileContractID]types.FileContractID), 222 renewing: make(map[types.FileContractID]bool), 223 revising: make(map[types.FileContractID]bool), 224 } 225 // Close the contract set and logger upon shutdown. 226 c.tg.AfterStop(func() { 227 if err := c.contracts.Close(); err != nil { 228 c.log.Println("Failed to close contract set:", err) 229 } 230 if err := c.log.Close(); err != nil { 231 fmt.Println("Failed to close the contractor logger:", err) 232 } 233 }) 234 235 // Load the prior persistence structures. 236 err := c.load() 237 if err != nil && !os.IsNotExist(err) { 238 return nil, err 239 } 240 241 // Mark contract utility. 242 c.managedMarkContractsUtility() 243 244 // Subscribe to the consensus set. 245 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 246 if err == modules.ErrInvalidConsensusChangeID { 247 // Reset the contractor consensus variables and try rescanning. 248 c.blockHeight = 0 249 c.lastChange = modules.ConsensusChangeBeginning 250 err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan()) 251 } 252 if err != nil { 253 return nil, errors.New("contractor subscription failed: " + err.Error()) 254 } 255 // Unsubscribe from the consensus set upon shutdown. 256 c.tg.OnStop(func() { 257 cs.Unsubscribe(c) 258 }) 259 260 // We may have upgraded persist or resubscribed. Save now so that we don't 261 // lose our work. 262 c.mu.Lock() 263 err = c.save() 264 c.mu.Unlock() 265 if err != nil { 266 return nil, err 267 } 268 269 return c, nil 270 }