gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/proto/contractset.go (about) 1 package proto 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "sync" 8 9 "gitlab.com/NebulousLabs/ratelimit" 10 "gitlab.com/SiaPrime/SiaPrime/build" 11 "gitlab.com/SiaPrime/SiaPrime/crypto" 12 "gitlab.com/SiaPrime/SiaPrime/modules" 13 "gitlab.com/SiaPrime/SiaPrime/types" 14 "gitlab.com/SiaPrime/writeaheadlog" 15 16 "gitlab.com/NebulousLabs/errors" 17 ) 18 19 // A ContractSet provides safe concurrent access to a set of contracts. Its 20 // purpose is to serialize modifications to individual contracts, as well as 21 // to provide operations on the set as a whole. 22 type ContractSet struct { 23 contracts map[types.FileContractID]*SafeContract 24 pubKeys map[string]types.FileContractID 25 deps modules.Dependencies 26 dir string 27 mu sync.Mutex 28 rl *ratelimit.RateLimit 29 globalRL *ratelimit.RateLimit 30 wal *writeaheadlog.WAL 31 } 32 33 // Acquire looks up the contract for the specified host key and locks it before 34 // returning it. If the contract is not present in the set, Acquire returns 35 // false and a zero-valued RenterContract. 36 func (cs *ContractSet) Acquire(id types.FileContractID) (*SafeContract, bool) { 37 cs.mu.Lock() 38 safeContract, ok := cs.contracts[id] 39 cs.mu.Unlock() 40 if !ok { 41 return nil, false 42 } 43 safeContract.revisionMu.Lock() 44 // We need to check if the contract is still in the map or if it has been 45 // deleted in the meantime. 46 cs.mu.Lock() 47 _, ok = cs.contracts[id] 48 cs.mu.Unlock() 49 if !ok { 50 safeContract.revisionMu.Unlock() 51 return nil, false 52 } 53 return safeContract, true 54 } 55 56 // Delete removes a contract from the set. The contract must have been 57 // previously acquired by Acquire. If the contract is not present in the set, 58 // Delete is a no-op. 59 func (cs *ContractSet) Delete(c *SafeContract) { 60 cs.mu.Lock() 61 _, ok := cs.contracts[c.header.ID()] 62 if !ok { 63 cs.mu.Unlock() 64 build.Critical("Delete called on already deleted contract") 65 return 66 } 67 delete(cs.contracts, c.header.ID()) 68 delete(cs.pubKeys, c.header.HostPublicKey().String()) 69 cs.mu.Unlock() 70 c.revisionMu.Unlock() 71 // delete contract file 72 path := filepath.Join(cs.dir, c.header.ID().String()+contractExtension) 73 err := errors.Compose(c.headerFile.Close(), os.Remove(path)) 74 if err != nil { 75 build.Critical("Failed to delete SafeContract from disk:", err) 76 } 77 } 78 79 // IDs returns the fcid of each contract with in the set. The contracts are not 80 // locked. 81 func (cs *ContractSet) IDs() []types.FileContractID { 82 cs.mu.Lock() 83 defer cs.mu.Unlock() 84 pks := make([]types.FileContractID, 0, len(cs.contracts)) 85 for fcid := range cs.contracts { 86 pks = append(pks, fcid) 87 } 88 return pks 89 } 90 91 // InsertContract inserts an existing contract into the set. 92 func (cs *ContractSet) InsertContract(rc modules.RecoverableContract, revTxn types.Transaction, roots []crypto.Hash, sk crypto.SecretKey) (modules.RenterContract, error) { 93 return cs.managedInsertContract(contractHeader{ 94 Transaction: revTxn, 95 SecretKey: sk, 96 StartHeight: rc.StartHeight, 97 DownloadSpending: types.NewCurrency64(1), // TODO set this 98 StorageSpending: types.NewCurrency64(1), // TODO set this 99 UploadSpending: types.NewCurrency64(1), // TODO set this 100 TotalCost: types.NewCurrency64(1), // TODO set this 101 ContractFee: types.NewCurrency64(1), // TODO set this 102 TxnFee: rc.TxnFee, 103 SiafundFee: types.Tax(rc.StartHeight, rc.Payout), 104 }, roots) 105 } 106 107 // Len returns the number of contracts in the set. 108 func (cs *ContractSet) Len() int { 109 cs.mu.Lock() 110 defer cs.mu.Unlock() 111 return len(cs.contracts) 112 } 113 114 // Return returns a locked contract to the set and unlocks it. The contract 115 // must have been previously acquired by Acquire. If the contract is not 116 // present in the set, Return panics. 117 func (cs *ContractSet) Return(c *SafeContract) { 118 cs.mu.Lock() 119 _, ok := cs.contracts[c.header.ID()] 120 if !ok { 121 cs.mu.Unlock() 122 build.Critical("no contract with that key") 123 } 124 cs.mu.Unlock() 125 c.revisionMu.Unlock() 126 } 127 128 // RateLimits sets the bandwidth limits for connections created by the 129 // contractSet. 130 func (cs *ContractSet) RateLimits() (readBPS int64, writeBPS int64, packetSize uint64) { 131 return cs.rl.Limits() 132 } 133 134 // SetRateLimits sets the bandwidth limits for connections created by the 135 // contractSet. 136 func (cs *ContractSet) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) { 137 cs.rl.SetLimits(readBPS, writeBPS, packetSize) 138 } 139 140 // View returns a copy of the contract with the specified host key. The 141 // contracts is not locked. Certain fields, including the MerkleRoots, are set 142 // to nil for safety reasons. If the contract is not present in the set, View 143 // returns false and a zero-valued RenterContract. 144 func (cs *ContractSet) View(id types.FileContractID) (modules.RenterContract, bool) { 145 cs.mu.Lock() 146 defer cs.mu.Unlock() 147 safeContract, ok := cs.contracts[id] 148 if !ok { 149 return modules.RenterContract{}, false 150 } 151 return safeContract.Metadata(), true 152 } 153 154 // ViewAll returns the metadata of each contract in the set. The contracts are 155 // not locked. 156 func (cs *ContractSet) ViewAll() []modules.RenterContract { 157 cs.mu.Lock() 158 defer cs.mu.Unlock() 159 contracts := make([]modules.RenterContract, 0, len(cs.contracts)) 160 for _, safeContract := range cs.contracts { 161 contracts = append(contracts, safeContract.Metadata()) 162 } 163 return contracts 164 } 165 166 // Close closes all contracts in a contract set, this means rendering it unusable for I/O 167 func (cs *ContractSet) Close() error { 168 for _, c := range cs.contracts { 169 c.headerFile.Close() 170 } 171 _, err := cs.wal.CloseIncomplete() 172 return err 173 } 174 175 // NewContractSet returns a ContractSet storing its contracts in the specified 176 // dir. 177 func NewContractSet(dir string, deps modules.Dependencies) (*ContractSet, error) { 178 if err := os.MkdirAll(dir, 0700); err != nil { 179 return nil, err 180 } 181 d, err := os.Open(dir) 182 if err != nil { 183 return nil, err 184 } else if stat, err := d.Stat(); err != nil { 185 return nil, err 186 } else if !stat.IsDir() { 187 return nil, errors.New("not a directory") 188 } 189 defer d.Close() 190 191 // Load the WAL. Any recovered updates will be applied after loading 192 // contracts. 193 // COMPATv1.3.1RC2 Rename old wals to have the 'wal' extension if new file 194 // doesn't exist. 195 if err := v131RC2RenameWAL(dir); err != nil { 196 return nil, err 197 } 198 walTxns, wal, err := writeaheadlog.New(filepath.Join(dir, "contractset.wal")) 199 if err != nil { 200 return nil, err 201 } 202 203 cs := &ContractSet{ 204 contracts: make(map[types.FileContractID]*SafeContract), 205 pubKeys: make(map[string]types.FileContractID), 206 207 deps: deps, 208 dir: dir, 209 wal: wal, 210 } 211 // Set the initial rate limit to 'unlimited' bandwidth with 4kib packets. 212 cs.rl = ratelimit.NewRateLimit(0, 0, 0) 213 214 // Load the contract files. 215 dirNames, err := d.Readdirnames(-1) 216 if err != nil { 217 return nil, err 218 } 219 220 for _, filename := range dirNames { 221 if filepath.Ext(filename) != contractExtension { 222 continue 223 } 224 path := filepath.Join(dir, filename) 225 if err := cs.loadSafeContract(path, walTxns); err != nil { 226 extErr := fmt.Errorf("failed to load safecontract %v", path) 227 return nil, errors.Compose(extErr, err) 228 } 229 } 230 231 return cs, nil 232 } 233 234 // v131RC2RenameWAL renames an existing old wal file from contractset.log to 235 // contractset.wal 236 func v131RC2RenameWAL(dir string) error { 237 oldPath := filepath.Join(dir, "contractset.log") 238 newPath := filepath.Join(dir, "contractset.wal") 239 _, errOld := os.Stat(oldPath) 240 _, errNew := os.Stat(newPath) 241 if !os.IsNotExist(errOld) && os.IsNotExist(errNew) { 242 return build.ExtendErr("failed to rename contractset.log to contractset.wal", 243 os.Rename(oldPath, newPath)) 244 } 245 return nil 246 }