github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/contractor/contractor.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 10 "SiaPrime/modules" 11 "SiaPrime/modules/renter/proto" 12 "SiaPrime/persist" 13 siasync "SiaPrime/sync" 14 "SiaPrime/types" 15 ) 16 17 var ( 18 errNilCS = errors.New("cannot create contractor with nil consensus set") 19 errNilTpool = errors.New("cannot create contractor with nil transaction pool") 20 errNilWallet = errors.New("cannot create contractor with nil wallet") 21 22 // COMPATv1.0.4-lts 23 // metricsContractID identifies a special contract that contains aggregate 24 // financial metrics from older contractors 25 metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'} 26 ) 27 28 // A Contractor negotiates, revises, renews, and provides access to file 29 // contracts. 30 type Contractor struct { 31 // dependencies 32 cs consensusSet 33 hdb hostDB 34 log *persist.Logger 35 mu sync.RWMutex 36 persist persister 37 staticDeps modules.Dependencies 38 tg siasync.ThreadGroup 39 tpool transactionPool 40 wallet wallet 41 42 // Only one thread should be performing contract maintenance at a time. 43 interruptMaintenance chan struct{} 44 maintenanceLock siasync.TryMutex 45 46 allowance modules.Allowance 47 blockHeight types.BlockHeight 48 currentPeriod types.BlockHeight 49 lastChange modules.ConsensusChangeID 50 51 downloaders map[types.FileContractID]*hostDownloader 52 editors map[types.FileContractID]*hostEditor 53 numFailedRenews map[types.FileContractID]types.BlockHeight 54 pubKeysToContractID map[string]types.FileContractID 55 contractIDToPubKey map[types.FileContractID]types.SiaPublicKey 56 renewing map[types.FileContractID]bool // prevent revising during renewal 57 58 // renewedFrom links the new contract's ID to the old contract's ID 59 // renewedTo links the old contract's ID to the new contract's ID 60 staticContracts *proto.ContractSet 61 oldContracts map[types.FileContractID]modules.RenterContract 62 renewedFrom map[types.FileContractID]types.FileContractID 63 renewedTo map[types.FileContractID]types.FileContractID 64 } 65 66 // Allowance returns the current allowance. 67 func (c *Contractor) Allowance() modules.Allowance { 68 c.mu.RLock() 69 defer c.mu.RUnlock() 70 return c.allowance 71 } 72 73 // PeriodSpending returns the amount spent on contracts during the current 74 // billing period. 75 func (c *Contractor) PeriodSpending() modules.ContractorSpending { 76 allContracts := c.staticContracts.ViewAll() 77 c.mu.RLock() 78 defer c.mu.RUnlock() 79 80 var spending modules.ContractorSpending 81 for _, contract := range allContracts { 82 // Calculate ContractFees 83 spending.ContractFees = spending.ContractFees.Add(contract.ContractFee) 84 spending.ContractFees = spending.ContractFees.Add(contract.TxnFee) 85 spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee) 86 // Calculate TotalAllocated 87 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 88 spending.ContractSpendingDeprecated = spending.TotalAllocated 89 // Calculate Spending 90 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 91 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 92 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 93 } 94 95 // Calculate needed spending to be reported from old contracts 96 for _, contract := range c.oldContracts { 97 host, exist := c.hdb.Host(contract.HostPublicKey) 98 if contract.StartHeight >= c.currentPeriod { 99 // Calculate spending from contracts that were renewed during the current period 100 // Calculate ContractFees 101 spending.ContractFees = spending.ContractFees.Add(contract.ContractFee) 102 spending.ContractFees = spending.ContractFees.Add(contract.TxnFee) 103 spending.ContractFees = spending.ContractFees.Add(contract.SiafundFee) 104 // Calculate TotalAllocated 105 spending.TotalAllocated = spending.TotalAllocated.Add(contract.TotalCost) 106 // Calculate Spending 107 spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending) 108 spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending) 109 spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending) 110 } else if exist && contract.EndHeight+host.WindowSize+types.MaturityDelay > c.blockHeight { 111 // Calculate funds that are being withheld in contracts 112 spending.WithheldFunds = spending.WithheldFunds.Add(contract.RenterFunds) 113 // Record the largest window size for worst case when reporting the spending 114 if contract.EndHeight+host.WindowSize+types.MaturityDelay >= spending.ReleaseBlock { 115 spending.ReleaseBlock = contract.EndHeight + host.WindowSize + types.MaturityDelay 116 } 117 // Calculate Previous spending 118 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 119 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending) 120 } else { 121 // Calculate Previous spending 122 spending.PreviousSpending = spending.PreviousSpending.Add(contract.ContractFee).Add(contract.TxnFee). 123 Add(contract.SiafundFee).Add(contract.DownloadSpending).Add(contract.UploadSpending).Add(contract.StorageSpending) 124 } 125 } 126 127 // Calculate amount of spent money to get unspent money. 128 allSpending := spending.ContractFees 129 allSpending = allSpending.Add(spending.DownloadSpending) 130 allSpending = allSpending.Add(spending.UploadSpending) 131 allSpending = allSpending.Add(spending.StorageSpending) 132 if c.allowance.Funds.Cmp(allSpending) >= 0 { 133 spending.Unspent = c.allowance.Funds.Sub(allSpending) 134 } 135 136 return spending 137 } 138 139 // CurrentPeriod returns the height at which the current allowance period 140 // began. 141 func (c *Contractor) CurrentPeriod() types.BlockHeight { 142 c.mu.RLock() 143 defer c.mu.RUnlock() 144 return c.currentPeriod 145 } 146 147 // RateLimits sets the bandwidth limits for connections created by the 148 // contractSet. 149 func (c *Contractor) RateLimits() (readBPW int64, writeBPS int64, packetSize uint64) { 150 return c.staticContracts.RateLimits() 151 } 152 153 // SetRateLimits sets the bandwidth limits for connections created by the 154 // contractSet. 155 func (c *Contractor) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) { 156 c.staticContracts.SetRateLimits(readBPS, writeBPS, packetSize) 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"), modules.ProdDependencies) 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 NewCustomContractor(cs, &WalletBridge{W: wallet}, tpool, hdb, contractSet, NewPersist(persistDir), logger, modules.ProdDependencies) 201 } 202 203 // NewCustomContractor creates a Contractor using the provided dependencies. 204 func NewCustomContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger, deps modules.Dependencies) (*Contractor, error) { 205 // Create the Contractor object. 206 c := &Contractor{ 207 cs: cs, 208 staticDeps: deps, 209 hdb: hdb, 210 log: l, 211 persist: p, 212 tpool: tp, 213 wallet: w, 214 215 interruptMaintenance: make(chan struct{}), 216 217 staticContracts: contractSet, 218 downloaders: make(map[types.FileContractID]*hostDownloader), 219 editors: make(map[types.FileContractID]*hostEditor), 220 oldContracts: make(map[types.FileContractID]modules.RenterContract), 221 contractIDToPubKey: make(map[types.FileContractID]types.SiaPublicKey), 222 pubKeysToContractID: make(map[string]types.FileContractID), 223 renewing: make(map[types.FileContractID]bool), 224 renewedFrom: make(map[types.FileContractID]types.FileContractID), 225 renewedTo: make(map[types.FileContractID]types.FileContractID), 226 } 227 228 // Close the contract set and logger upon shutdown. 229 c.tg.AfterStop(func() { 230 if err := c.staticContracts.Close(); err != nil { 231 c.log.Println("Failed to close contract set:", err) 232 } 233 if err := c.log.Close(); err != nil { 234 fmt.Println("Failed to close the contractor logger:", err) 235 } 236 }) 237 238 // Load the prior persistence structures. 239 err := c.load() 240 if err != nil && !os.IsNotExist(err) { 241 return nil, err 242 } 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 // Initialize the contractIDToPubKey map 270 for _, contract := range c.oldContracts { 271 c.contractIDToPubKey[contract.ID] = contract.HostPublicKey 272 c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID 273 } 274 for _, contract := range c.staticContracts.ViewAll() { 275 c.contractIDToPubKey[contract.ID] = contract.HostPublicKey 276 c.pubKeysToContractID[string(contract.HostPublicKey.Key)] = contract.ID 277 } 278 279 // Update the allowance in the hostdb with the one that was loaded from 280 // disk. 281 err = c.hdb.SetAllowance(c.allowance) 282 if err != nil { 283 return nil, err 284 } 285 return c, nil 286 }