gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/renter_test.go (about) 1 package renter 2 3 import ( 4 "io/ioutil" 5 "path/filepath" 6 "reflect" 7 "testing" 8 9 "gitlab.com/NebulousLabs/fastrand" 10 "gitlab.com/SiaPrime/SiaPrime/build" 11 "gitlab.com/SiaPrime/SiaPrime/crypto" 12 "gitlab.com/SiaPrime/SiaPrime/modules" 13 "gitlab.com/SiaPrime/SiaPrime/modules/consensus" 14 "gitlab.com/SiaPrime/SiaPrime/modules/gateway" 15 "gitlab.com/SiaPrime/SiaPrime/modules/miner" 16 "gitlab.com/SiaPrime/SiaPrime/modules/renter/contractor" 17 "gitlab.com/SiaPrime/SiaPrime/modules/renter/hostdb" 18 "gitlab.com/SiaPrime/SiaPrime/modules/transactionpool" 19 "gitlab.com/SiaPrime/SiaPrime/modules/wallet" 20 "gitlab.com/SiaPrime/SiaPrime/persist" 21 "gitlab.com/SiaPrime/SiaPrime/types" 22 ) 23 24 // renterTester contains all of the modules that are used while testing the renter. 25 type renterTester struct { 26 cs modules.ConsensusSet 27 gateway modules.Gateway 28 miner modules.TestMiner 29 tpool modules.TransactionPool 30 wallet modules.Wallet 31 walletKey crypto.CipherKey 32 33 renter *Renter 34 dir string 35 } 36 37 // Close shuts down the renter tester. 38 func (rt *renterTester) Close() error { 39 rt.wallet.Lock() 40 rt.cs.Close() 41 rt.gateway.Close() 42 return nil 43 } 44 45 // addRenter adds a renter to the renter tester and then make sure there is 46 // money in the wallet 47 func (rt *renterTester) addRenter(r *Renter) error { 48 rt.renter = r 49 // Mine blocks until there is money in the wallet. 50 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 51 _, err := rt.miner.AddBlock() 52 if err != nil { 53 return err 54 } 55 } 56 return nil 57 } 58 59 // createZeroByteFileOnDisk creates a 0 byte file on disk so that a Stat of the 60 // local path won't return an error 61 func (rt *renterTester) createZeroByteFileOnDisk() (string, error) { 62 path := filepath.Join(rt.renter.staticFilesDir, persist.RandomSuffix()) 63 err := ioutil.WriteFile(path, []byte{}, 0600) 64 if err != nil { 65 return "", err 66 } 67 return path, nil 68 } 69 70 // newRenterTester creates a ready-to-use renter tester with money in the 71 // wallet. 72 func newRenterTester(name string) (*renterTester, error) { 73 testdir := build.TempDir("renter", name) 74 rt, err := newRenterTesterNoRenter(testdir) 75 if err != nil { 76 return nil, err 77 } 78 r, err := New(rt.gateway, rt.cs, rt.wallet, rt.tpool, filepath.Join(testdir, modules.RenterDir)) 79 if err != nil { 80 return nil, err 81 } 82 err = rt.addRenter(r) 83 if err != nil { 84 return nil, err 85 } 86 return rt, nil 87 } 88 89 // newRenterTesterNoRenter creates all the modules for the renter tester except 90 // the renter. A renter will need to be added and blocks mined to add money to 91 // the wallet. 92 func newRenterTesterNoRenter(testdir string) (*renterTester, error) { 93 // Create the modules. 94 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 95 if err != nil { 96 return nil, err 97 } 98 cs, err := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 99 if err != nil { 100 return nil, err 101 } 102 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 103 if err != nil { 104 return nil, err 105 } 106 w, err := wallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 107 if err != nil { 108 return nil, err 109 } 110 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 111 _, err = w.Encrypt(key) 112 if err != nil { 113 return nil, err 114 } 115 err = w.Unlock(key) 116 if err != nil { 117 return nil, err 118 } 119 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 120 if err != nil { 121 return nil, err 122 } 123 124 // Assemble all pieces into a renter tester. 125 return &renterTester{ 126 cs: cs, 127 gateway: g, 128 miner: m, 129 tpool: tp, 130 wallet: w, 131 132 dir: testdir, 133 }, nil 134 } 135 136 // newRenterTesterWithDependency creates a ready-to-use renter tester with money in the 137 // wallet. 138 func newRenterTesterWithDependency(name string, deps modules.Dependencies) (*renterTester, error) { 139 testdir := build.TempDir("renter", name) 140 rt, err := newRenterTesterNoRenter(testdir) 141 if err != nil { 142 return nil, err 143 } 144 r, err := newRenterWithDependency(rt.gateway, rt.cs, rt.wallet, rt.tpool, filepath.Join(testdir, modules.RenterDir), deps) 145 if err != nil { 146 return nil, err 147 } 148 err = rt.addRenter(r) 149 if err != nil { 150 return nil, err 151 } 152 return rt, nil 153 } 154 155 // newRenterWithDependency creates a Renter with custom dependency 156 func newRenterWithDependency(g modules.Gateway, cs modules.ConsensusSet, wallet modules.Wallet, tpool modules.TransactionPool, persistDir string, deps modules.Dependencies) (*Renter, error) { 157 hdb, err := hostdb.New(g, cs, tpool, persistDir) 158 if err != nil { 159 return nil, err 160 } 161 hc, err := contractor.New(cs, wallet, tpool, hdb, persistDir) 162 if err != nil { 163 return nil, err 164 } 165 return NewCustomRenter(g, cs, tpool, hdb, wallet, hc, persistDir, deps) 166 } 167 168 // stubHostDB is the minimal implementation of the hostDB interface. It can be 169 // embedded in other mock hostDB types, removing the need to re-implement all 170 // of the hostDB's methods on every mock. 171 type stubHostDB struct{} 172 173 func (stubHostDB) ActiveHosts() []modules.HostDBEntry { return nil } 174 func (stubHostDB) AllHosts() []modules.HostDBEntry { return nil } 175 func (stubHostDB) AverageContractPrice() types.Currency { return types.Currency{} } 176 func (stubHostDB) Close() error { return nil } 177 func (stubHostDB) Filter() (modules.FilterMode, map[string]types.SiaPublicKey) { 178 return 0, make(map[string]types.SiaPublicKey) 179 } 180 func (stubHostDB) SetFilterMode(fm modules.FilterMode, hosts []types.SiaPublicKey) error { return nil } 181 func (stubHostDB) IsOffline(modules.NetAddress) bool { return true } 182 func (stubHostDB) RandomHosts(int, []types.SiaPublicKey) ([]modules.HostDBEntry, error) { 183 return []modules.HostDBEntry{}, nil 184 } 185 func (stubHostDB) EstimateHostScore(modules.HostDBEntry, modules.Allowance) (modules.HostScoreBreakdown, error) { 186 return modules.HostScoreBreakdown{}, nil 187 } 188 func (stubHostDB) Host(types.SiaPublicKey) (modules.HostDBEntry, bool) { 189 return modules.HostDBEntry{}, false 190 } 191 func (stubHostDB) ScoreBreakdown(modules.HostDBEntry) (modules.HostScoreBreakdown, error) { 192 return modules.HostScoreBreakdown{}, nil 193 } 194 195 // stubContractor is the minimal implementation of the hostContractor 196 // interface. 197 type stubContractor struct{} 198 199 func (stubContractor) SetAllowance(modules.Allowance) error { return nil } 200 func (stubContractor) Allowance() modules.Allowance { return modules.Allowance{} } 201 func (stubContractor) Contract(modules.NetAddress) (modules.RenterContract, bool) { 202 return modules.RenterContract{}, false 203 } 204 func (stubContractor) Contracts() []modules.RenterContract { return nil } 205 func (stubContractor) CurrentPeriod() types.BlockHeight { return 0 } 206 func (stubContractor) IsOffline(modules.NetAddress) bool { return false } 207 func (stubContractor) Editor(types.FileContractID) (contractor.Editor, error) { return nil, nil } 208 func (stubContractor) Downloader(types.FileContractID) (contractor.Downloader, error) { 209 return nil, nil 210 } 211 212 type pricesStub struct { 213 stubHostDB 214 215 dbEntries []modules.HostDBEntry 216 } 217 218 func (pricesStub) InitialScanComplete() (bool, error) { return true, nil } 219 func (pricesStub) IPViolationsCheck() bool { return true } 220 221 func (ps pricesStub) RandomHosts(_ int, _, _ []types.SiaPublicKey) ([]modules.HostDBEntry, error) { 222 return ps.dbEntries, nil 223 } 224 func (ps pricesStub) RandomHostsWithAllowance(_ int, _, _ []types.SiaPublicKey, _ modules.Allowance) ([]modules.HostDBEntry, error) { 225 return ps.dbEntries, nil 226 } 227 func (ps pricesStub) SetIPViolationCheck(enabled bool) { return } 228 229 // TestRenterPricesDivideByZero verifies that the Price Estimation catches 230 // divide by zero errors. 231 func TestRenterPricesDivideByZero(t *testing.T) { 232 if testing.Short() { 233 t.SkipNow() 234 } 235 rt, err := newRenterTester(t.Name()) 236 if err != nil { 237 t.Fatal(err) 238 } 239 defer rt.Close() 240 241 // Confirm price estimation returns error if there are no hosts available 242 _, _, err = rt.renter.PriceEstimation(modules.Allowance{}) 243 if err == nil { 244 t.Fatal("Expected error due to no hosts") 245 } 246 247 // Create a stubbed hostdb, add an entry. 248 hdb := &pricesStub{} 249 id := rt.renter.mu.Lock() 250 rt.renter.hostDB = hdb 251 rt.renter.mu.Unlock(id) 252 dbe := modules.HostDBEntry{} 253 dbe.ContractPrice = types.SiacoinPrecision 254 dbe.DownloadBandwidthPrice = types.SiacoinPrecision 255 dbe.UploadBandwidthPrice = types.SiacoinPrecision 256 dbe.StoragePrice = types.SiacoinPrecision 257 pk := fastrand.Bytes(crypto.EntropySize) 258 dbe.PublicKey = types.SiaPublicKey{Key: pk} 259 hdb.dbEntries = append(hdb.dbEntries, dbe) 260 261 // Confirm price estimation does not return an error now that there is a 262 // host available 263 _, _, err = rt.renter.PriceEstimation(modules.Allowance{}) 264 if err != nil { 265 t.Fatal(err) 266 } 267 268 // Set allowance funds and host contract price such that the allowance funds 269 // are not sufficient to cover the contract price 270 allowance := modules.Allowance{ 271 Funds: types.SiacoinPrecision, 272 Hosts: 1, 273 Period: 12096, 274 RenewWindow: 4032, 275 } 276 dbe.ContractPrice = allowance.Funds.Mul64(2) 277 278 // Confirm price estimation returns error because of the contract and 279 // funding prices 280 _, _, err = rt.renter.PriceEstimation(allowance) 281 if err == nil { 282 t.Fatal("Expected error due to allowance funds inefficient") 283 } 284 } 285 286 // TestRenterPricesVolatility verifies that the renter caches its price 287 // estimation, and subsequent calls result in non-volatile results. 288 func TestRenterPricesVolatility(t *testing.T) { 289 if testing.Short() { 290 t.SkipNow() 291 } 292 rt, err := newRenterTester(t.Name()) 293 if err != nil { 294 t.Fatal(err) 295 } 296 defer rt.Close() 297 298 // create a stubbed hostdb, query it with one contract, add another, verify 299 // the price estimation remains constant until the timeout has passed. 300 hdb := &pricesStub{} 301 id := rt.renter.mu.Lock() 302 rt.renter.hostDB = hdb 303 rt.renter.mu.Unlock(id) 304 dbe := modules.HostDBEntry{} 305 dbe.ContractPrice = types.SiacoinPrecision 306 dbe.DownloadBandwidthPrice = types.SiacoinPrecision 307 dbe.UploadBandwidthPrice = types.SiacoinPrecision 308 dbe.StoragePrice = types.SiacoinPrecision 309 // Add 4 host entries in the database with different public keys. 310 for len(hdb.dbEntries) < modules.PriceEstimationScope { 311 pk := fastrand.Bytes(crypto.EntropySize) 312 dbe.PublicKey = types.SiaPublicKey{Key: pk} 313 hdb.dbEntries = append(hdb.dbEntries, dbe) 314 } 315 allowance := modules.Allowance{} 316 initial, _, err := rt.renter.PriceEstimation(allowance) 317 if err != nil { 318 t.Fatal(err) 319 } 320 // Changing the contract price should be enough to trigger a change 321 // if the hosts are not cached. 322 dbe.ContractPrice = dbe.ContractPrice.Mul64(2) 323 pk := fastrand.Bytes(crypto.EntropySize) 324 dbe.PublicKey = types.SiaPublicKey{Key: pk} 325 hdb.dbEntries = append(hdb.dbEntries, dbe) 326 after, _, err := rt.renter.PriceEstimation(allowance) 327 if err != nil { 328 t.Fatal(err) 329 } 330 if !reflect.DeepEqual(initial, after) { 331 t.Log(initial) 332 t.Log(after) 333 t.Fatal("expected renter price estimation to be constant") 334 } 335 }