gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/update_test.go (about) 1 package contractor 2 3 import ( 4 "fmt" 5 "testing" 6 "time" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/NebulousLabs/fastrand" 10 11 "gitlab.com/SkynetLabs/skyd/build" 12 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 13 "gitlab.com/SkynetLabs/skyd/skymodules" 14 "go.sia.tech/siad/crypto" 15 "go.sia.tech/siad/modules" 16 "go.sia.tech/siad/types" 17 ) 18 19 // TestContractsToDelete is a unit test for contractsToDelete. 20 func TestContractsToDelete(t *testing.T) { 21 t.Parallel() 22 23 tests := []struct { 24 numContracts int 25 toDelete int 26 }{ 27 { 28 numContracts: 100, 29 toDelete: 1, 30 }, 31 { 32 numContracts: 200, 33 toDelete: 2, 34 }, 35 { 36 numContracts: 199, 37 toDelete: 1, 38 }, 39 { 40 numContracts: 99, 41 toDelete: 1, 42 }, 43 { 44 numContracts: 1, 45 toDelete: 1, 46 }, 47 { 48 numContracts: 0, 49 toDelete: 0, 50 }, 51 } 52 for _, test := range tests { 53 toDelete := contractsToDelete(test.numContracts) 54 if toDelete != test.toDelete { 55 t.Errorf("unexpted value %v != %v", toDelete, test.toDelete) 56 } 57 } 58 } 59 60 // TestIntegrationAutoRenew tests that contracts are automatically renewed at 61 // the expected block height. 62 func TestIntegrationAutoRenew(t *testing.T) { 63 if testing.Short() { 64 t.SkipNow() 65 } 66 t.Parallel() 67 // create testing trio 68 _, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{}) 69 if err != nil { 70 t.Fatal(err) 71 } 72 defer tryClose(cf, t) 73 74 // form a contract with the host 75 a := skymodules.Allowance{ 76 Funds: types.SiacoinPrecision.Mul64(100), // 100 SC 77 Hosts: 1, 78 Period: 50, 79 RenewWindow: 10, 80 ExpectedStorage: skymodules.DefaultAllowance.ExpectedStorage, 81 ExpectedUpload: skymodules.DefaultAllowance.ExpectedUpload, 82 ExpectedDownload: skymodules.DefaultAllowance.ExpectedDownload, 83 ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy, 84 MaxPeriodChurn: skymodules.DefaultAllowance.MaxPeriodChurn, 85 } 86 err = c.SetAllowance(a) 87 if err != nil { 88 t.Fatal(err) 89 } 90 numRetries := 0 91 err = build.Retry(100, 100*time.Millisecond, func() error { 92 if numRetries%10 == 0 { 93 if _, err := m.AddBlock(); err != nil { 94 return err 95 } 96 } 97 numRetries++ 98 if len(c.Contracts()) == 0 { 99 return errors.New("contracts were not formed") 100 } 101 return nil 102 }) 103 if err != nil { 104 t.Fatal(err) 105 } 106 contract := c.Contracts()[0] 107 108 // Grab the editor in a retry statement, because there is a race condition 109 // between the contract set having contracts in it and the editor having 110 // access to the new contract. 111 var editor Editor 112 err = build.Retry(100, 100*time.Millisecond, func() error { 113 editor, err = c.Editor(contract.HostPublicKey, nil) 114 if err != nil { 115 return err 116 } 117 return nil 118 }) 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 data := fastrand.Bytes(int(modules.SectorSize)) 124 // insert the sector 125 _, err = editor.Upload(data) 126 if err != nil { 127 t.Fatal(err) 128 } 129 err = editor.Close() 130 if err != nil { 131 t.Fatal(err) 132 } 133 134 // mine until we enter the renew window 135 renewHeight := contract.EndHeight - c.allowance.RenewWindow 136 for c.blockHeight < renewHeight { 137 _, err := m.AddBlock() 138 if err != nil { 139 t.Fatal(err) 140 } 141 } 142 // wait for goroutine in ProcessConsensusChange to finish 143 time.Sleep(100 * time.Millisecond) 144 c.maintenanceLock.Lock() 145 c.maintenanceLock.Unlock() 146 147 // check renewed contract 148 contract = c.Contracts()[0] 149 endHeight := c.contractEndHeight() 150 if contract.EndHeight != endHeight { 151 t.Fatalf("Wrong end height, expected %v got %v\n", endHeight, contract.EndHeight) 152 } 153 } 154 155 // TestIntegrationRenewInvalidate tests that editors and downloaders are 156 // properly invalidated when a renew is queued. 157 func TestIntegrationRenewInvalidate(t *testing.T) { 158 if testing.Short() { 159 t.SkipNow() 160 } 161 t.Parallel() 162 // create testing trio 163 _, c, m, cf, err := newTestingTrioWithContractorDeps(t.Name(), &dependencies.DependencyLegacyRenew{}) 164 if err != nil { 165 t.Fatal(err) 166 } 167 defer tryClose(cf, t) 168 169 // form a contract with the host 170 a := skymodules.Allowance{ 171 Funds: types.SiacoinPrecision.Mul64(100), // 100 SC 172 Hosts: 1, 173 Period: 50, 174 RenewWindow: 10, 175 ExpectedStorage: skymodules.DefaultAllowance.ExpectedStorage, 176 ExpectedUpload: skymodules.DefaultAllowance.ExpectedUpload, 177 ExpectedDownload: skymodules.DefaultAllowance.ExpectedDownload, 178 ExpectedRedundancy: skymodules.DefaultAllowance.ExpectedRedundancy, 179 MaxPeriodChurn: skymodules.DefaultAllowance.MaxPeriodChurn, 180 } 181 err = c.SetAllowance(a) 182 if err != nil { 183 t.Fatal(err) 184 } 185 numRetries := 0 186 err = build.Retry(100, 100*time.Millisecond, func() error { 187 if numRetries%10 == 0 { 188 if _, err := m.AddBlock(); err != nil { 189 return err 190 } 191 } 192 numRetries++ 193 // Check for number of contracts and number of pubKeys as there is a 194 // slight delay between the contract being added to the contract set and 195 // the pubkey being added to the contractor map 196 c.mu.Lock() 197 numPubKeys := len(c.pubKeysToContractID) 198 c.mu.Unlock() 199 numContracts := len(c.Contracts()) 200 if numContracts != 1 { 201 return fmt.Errorf("Expected 1 contracts, found %v", numContracts) 202 } 203 if numPubKeys != 1 { 204 return fmt.Errorf("Expected 1 pubkey, found %v", numPubKeys) 205 } 206 return nil 207 }) 208 if err != nil { 209 t.Fatal(err) 210 } 211 contract := c.Contracts()[0] 212 213 // revise the contract 214 editor, err := c.Editor(contract.HostPublicKey, nil) 215 if err != nil { 216 t.Fatal(err) 217 } 218 data := fastrand.Bytes(int(modules.SectorSize)) 219 // insert the sector 220 _, err = editor.Upload(data) 221 if err != nil { 222 t.Fatal(err) 223 } 224 225 // mine until we enter the renew window; the editor should be invalidated 226 renewHeight := contract.EndHeight - c.allowance.RenewWindow 227 for c.blockHeight < renewHeight { 228 _, err := m.AddBlock() 229 if err != nil { 230 t.Fatal(err) 231 } 232 } 233 // wait for goroutine in ProcessConsensusChange to finish 234 time.Sleep(100 * time.Millisecond) 235 c.maintenanceLock.Lock() 236 c.maintenanceLock.Unlock() 237 238 // check renewed contract 239 contract = c.Contracts()[0] 240 endHeight := c.contractEndHeight() 241 c.mu.Lock() 242 if contract.EndHeight != endHeight { 243 t.Fatalf("Wrong end height, expected %v got %v\n", endHeight, contract.EndHeight) 244 } 245 c.mu.Unlock() 246 247 // editor should have been invalidated 248 _, err = editor.Upload(make([]byte, modules.SectorSize)) 249 if !errors.Contains(err, errInvalidEditor) && !errors.Contains(err, errInvalidSession) { 250 t.Error("expected invalid editor error; got", err) 251 } 252 editor.Close() 253 254 // create a downloader 255 downloader, err := c.Downloader(contract.HostPublicKey, nil) 256 if err != nil { 257 t.Fatal(err) 258 } 259 // mine until we enter the renew window 260 renewHeight = contract.EndHeight - c.allowance.RenewWindow 261 for c.blockHeight < renewHeight { 262 _, err := m.AddBlock() 263 if err != nil { 264 t.Fatal(err) 265 } 266 } 267 268 // downloader should have been invalidated 269 err = build.Retry(50, 100*time.Millisecond, func() error { 270 // wait for goroutine in ProcessConsensusChange to finish 271 c.maintenanceLock.Lock() 272 c.maintenanceLock.Unlock() 273 _, err2 := downloader.Download(crypto.Hash{}, 0, 0) 274 if !errors.Contains(err2, errInvalidDownloader) && !errors.Contains(err2, errInvalidSession) { 275 return errors.AddContext(err, "expected invalid downloader error") 276 } 277 return downloader.Close() 278 }) 279 if err != nil { 280 t.Fatal(err) 281 } 282 }