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