gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/persist_test.go (about) 1 package contractor 2 3 import ( 4 "io/ioutil" 5 "os" 6 "path/filepath" 7 "reflect" 8 "testing" 9 10 "gitlab.com/NebulousLabs/fastrand" 11 "gitlab.com/NebulousLabs/ratelimit" 12 13 "gitlab.com/SkynetLabs/skyd/build" 14 "gitlab.com/SkynetLabs/skyd/skymodules" 15 "gitlab.com/SkynetLabs/skyd/skymodules/renter/proto" 16 "go.sia.tech/siad/modules" 17 "go.sia.tech/siad/persist" 18 "go.sia.tech/siad/types" 19 ) 20 21 // TestSaveLoad tests that the contractor can save and load itself. 22 func TestSaveLoad(t *testing.T) { 23 if testing.Short() { 24 t.SkipNow() 25 } 26 t.Parallel() 27 // create contractor with mocked persist dependency 28 persistDir := build.TempDir("contractor", "mock") 29 os.MkdirAll(persistDir, 0700) 30 c := &Contractor{ 31 persistDir: persistDir, 32 preferredHosts: make(map[string]struct{}), 33 synced: make(chan struct{}), 34 } 35 36 c.staticWatchdog = newWatchdog(c) 37 expectedFileContractStatus := &fileContractStatus{ 38 formationSweepHeight: 543210, 39 contractFound: true, 40 revisionFound: 400, 41 storageProofFound: 987123, 42 43 formationTxnSet: []types.Transaction{ 44 { 45 ArbitraryData: [][]byte{{1, 2, 3, 4, 5}}, 46 }, 47 }, 48 parentOutputs: map[types.SiacoinOutputID]struct{}{ 49 {4}: {}, 50 }, 51 52 sweepTxn: types.Transaction{ 53 ArbitraryData: [][]byte{{1, 2, 3}}, 54 }, 55 56 sweepParents: []types.Transaction{{ 57 ArbitraryData: [][]byte{{4, 5, 63}}, 58 }}, 59 60 windowStart: 5, 61 windowEnd: 10, 62 } 63 c.staticWatchdog.contracts = map[types.FileContractID]*fileContractStatus{ 64 {1}: expectedFileContractStatus, 65 } 66 67 expectedArchivedContract := skymodules.ContractWatchStatus{ 68 Archived: true, 69 FormationSweepHeight: 11, 70 ContractFound: true, 71 LatestRevisionFound: 3883889, 72 StorageProofFoundAtHeight: 12312, 73 DoubleSpendHeight: 12333333, 74 WindowStart: 1111111231209, 75 WindowEnd: 123808900, 76 } 77 c.staticWatchdog.archivedContracts = map[types.FileContractID]skymodules.ContractWatchStatus{ 78 {2}: expectedArchivedContract, 79 } 80 81 c.oldContracts = map[types.FileContractID]skymodules.RenterContract{ 82 {0}: {ID: types.FileContractID{0}, HostPublicKey: types.SiaPublicKey{Key: []byte("foo")}}, 83 {1}: {ID: types.FileContractID{1}, HostPublicKey: types.SiaPublicKey{Key: []byte("bar")}}, 84 {2}: {ID: types.FileContractID{2}, HostPublicKey: types.SiaPublicKey{Key: []byte("baz")}}, 85 } 86 87 c.renewedFrom = map[types.FileContractID]types.FileContractID{ 88 {1}: {2}, 89 } 90 c.renewedTo = map[types.FileContractID]types.FileContractID{ 91 {1}: {2}, 92 } 93 c.preferredHosts["host"] = struct{}{} 94 close(c.synced) 95 96 c.staticChurnLimiter = newChurnLimiter(c) 97 c.staticChurnLimiter.aggregateCurrentPeriodChurn = 123456 98 c.staticChurnLimiter.remainingChurnBudget = -789 99 100 // save, clear, and reload 101 err := c.save() 102 if err != nil { 103 t.Fatal(err) 104 } 105 c.oldContracts = make(map[types.FileContractID]skymodules.RenterContract) 106 c.renewedFrom = make(map[types.FileContractID]types.FileContractID) 107 c.renewedTo = make(map[types.FileContractID]types.FileContractID) 108 err = c.load() 109 if err != nil { 110 t.Fatal(err) 111 } 112 // Check that all fields were restored 113 _, ok0 := c.oldContracts[types.FileContractID{0}] 114 _, ok1 := c.oldContracts[types.FileContractID{1}] 115 _, ok2 := c.oldContracts[types.FileContractID{2}] 116 if !ok0 || !ok1 || !ok2 { 117 t.Fatal("oldContracts were not restored properly:", c.oldContracts) 118 } 119 id := types.FileContractID{2} 120 if c.renewedFrom[types.FileContractID{1}] != id { 121 t.Fatal("renewedFrom not restored properly:", c.renewedFrom) 122 } 123 if c.renewedTo[types.FileContractID{1}] != id { 124 t.Fatal("renewedTo not restored properly:", c.renewedTo) 125 } 126 if _, exists := c.preferredHosts["host"]; !exists { 127 t.Fatal("preferred host wasn't loaded") 128 } 129 if len(c.preferredHosts) != 1 { 130 t.Fatal("wrong length") 131 } 132 select { 133 case <-c.synced: 134 default: 135 t.Fatal("contractor should be synced") 136 } 137 // use stdPersist instead of mock 138 c.persistDir = build.TempDir("contractor", t.Name()) 139 os.MkdirAll(c.persistDir, 0700) 140 141 // COMPATv136 save the allowance but make sure that the newly added fields 142 // are 0. After loading them from disk they should be set to the default 143 // values. 144 c.allowance = skymodules.DefaultAllowance 145 c.allowance.ExpectedStorage = 0 146 c.allowance.ExpectedUpload = 0 147 c.allowance.ExpectedDownload = 0 148 c.allowance.ExpectedRedundancy = 0 149 c.allowance.MaxPeriodChurn = 0 150 151 // save, clear, and reload 152 err = c.save() 153 if err != nil { 154 t.Fatal(err) 155 } 156 c.oldContracts = make(map[types.FileContractID]skymodules.RenterContract) 157 c.renewedFrom = make(map[types.FileContractID]types.FileContractID) 158 c.renewedTo = make(map[types.FileContractID]types.FileContractID) 159 c.synced = make(chan struct{}) 160 err = c.load() 161 if err != nil { 162 t.Fatal(err) 163 } 164 // check that all fields were restored 165 _, ok0 = c.oldContracts[types.FileContractID{0}] 166 _, ok1 = c.oldContracts[types.FileContractID{1}] 167 _, ok2 = c.oldContracts[types.FileContractID{2}] 168 if !ok0 || !ok1 || !ok2 { 169 t.Fatal("oldContracts were not restored properly:", c.oldContracts) 170 } 171 if c.renewedFrom[types.FileContractID{1}] != id { 172 t.Fatal("renewedFrom not restored properly:", c.renewedFrom) 173 } 174 if c.renewedTo[types.FileContractID{1}] != id { 175 t.Fatal("renewedTo not restored properly:", c.renewedTo) 176 } 177 if _, exists := c.preferredHosts["host"]; !exists { 178 t.Fatal("preferred host wasn't loaded") 179 } 180 if len(c.preferredHosts) != 1 { 181 t.Fatal("wrong length") 182 } 183 select { 184 case <-c.synced: 185 default: 186 t.Fatal("contractor should be synced") 187 } 188 if c.allowance.ExpectedStorage != skymodules.DefaultAllowance.ExpectedStorage { 189 t.Errorf("ExpectedStorage was %v but should be %v", 190 c.allowance.ExpectedStorage, skymodules.DefaultAllowance.ExpectedStorage) 191 } 192 if c.allowance.ExpectedUpload != skymodules.DefaultAllowance.ExpectedUpload { 193 t.Errorf("ExpectedUpload was %v but should be %v", 194 c.allowance.ExpectedUpload, skymodules.DefaultAllowance.ExpectedUpload) 195 } 196 if c.allowance.ExpectedDownload != skymodules.DefaultAllowance.ExpectedDownload { 197 t.Errorf("ExpectedDownload was %v but should be %v", 198 c.allowance.ExpectedDownload, skymodules.DefaultAllowance.ExpectedDownload) 199 } 200 if c.allowance.ExpectedRedundancy != skymodules.DefaultAllowance.ExpectedRedundancy { 201 t.Errorf("ExpectedRedundancy was %v but should be %v", 202 c.allowance.ExpectedRedundancy, skymodules.DefaultAllowance.ExpectedRedundancy) 203 } 204 205 // Change the expected* fields of the allowance again, save, clear and reload. 206 c.allowance.ExpectedStorage = uint64(fastrand.Intn(100)) 207 c.allowance.ExpectedUpload = uint64(fastrand.Intn(100)) 208 c.allowance.ExpectedDownload = uint64(fastrand.Intn(100)) 209 c.allowance.ExpectedRedundancy = float64(fastrand.Intn(100)) 210 c.allowance.MaxPeriodChurn = 1357 211 a := c.allowance 212 // Save 213 err = c.save() 214 if err != nil { 215 t.Fatal(err) 216 } 217 // Clear allowance. 218 c.allowance = skymodules.Allowance{} 219 // Load 220 err = c.load() 221 if err != nil { 222 t.Fatal(err) 223 } 224 // Check if fields were restored. 225 if c.allowance.ExpectedStorage != a.ExpectedStorage { 226 t.Errorf("ExpectedStorage was %v but should be %v", 227 c.allowance.ExpectedStorage, a.ExpectedStorage) 228 } 229 if c.allowance.ExpectedUpload != a.ExpectedUpload { 230 t.Errorf("ExpectedUpload was %v but should be %v", 231 c.allowance.ExpectedUpload, a.ExpectedUpload) 232 } 233 if c.allowance.ExpectedDownload != a.ExpectedDownload { 234 t.Errorf("ExpectedDownload was %v but should be %v", 235 c.allowance.ExpectedDownload, a.ExpectedDownload) 236 } 237 if c.allowance.ExpectedRedundancy != a.ExpectedRedundancy { 238 t.Errorf("ExpectedRedundancy was %v but should be %v", 239 c.allowance.ExpectedRedundancy, a.ExpectedRedundancy) 240 } 241 if c.allowance.MaxPeriodChurn != a.MaxPeriodChurn { 242 t.Errorf("MaxPeriodChurn was %v but should be %v", 243 c.allowance.MaxPeriodChurn, a.MaxPeriodChurn) 244 } 245 246 // Check the watchdog settings. 247 if c.staticWatchdog == nil { 248 t.Fatal("Watchdog not restored") 249 } 250 contract, ok := c.staticWatchdog.contracts[types.FileContractID{1}] 251 if !ok { 252 t.Fatal("Contract not found", len(c.staticWatchdog.contracts)) 253 } 254 if contract.formationSweepHeight != expectedFileContractStatus.formationSweepHeight { 255 t.Fatal("watchdog not restored properly", contract.formationSweepHeight) 256 } 257 if contract.contractFound != expectedFileContractStatus.contractFound { 258 t.Fatal("watchdog not restored properly") 259 } 260 if contract.revisionFound != expectedFileContractStatus.revisionFound { 261 t.Fatal("watchdog not restored properly", contract.revisionFound) 262 } 263 if contract.storageProofFound != expectedFileContractStatus.storageProofFound { 264 t.Fatal("watchdog not restored properly", contract.storageProofFound) 265 } 266 if len(contract.formationTxnSet) != 1 { 267 t.Fatal("watchdog not restored properly", contract) 268 } 269 if contract.formationTxnSet[0].ID() != expectedFileContractStatus.formationTxnSet[0].ID() { 270 t.Fatal("watchdog not restored properly", contract.formationTxnSet) 271 } 272 if len(contract.parentOutputs) != 1 { 273 t.Fatal("watchdog not restored properly", contract.parentOutputs) 274 } 275 if _, foundOutput := contract.parentOutputs[types.SiacoinOutputID{4}]; !foundOutput { 276 t.Fatal("watchdog not restored properly", contract.parentOutputs) 277 } 278 if contract.sweepTxn.ID() != expectedFileContractStatus.sweepTxn.ID() { 279 t.Fatal("watchdog not restored properly", contract) 280 } 281 if len(contract.sweepParents) != len(expectedFileContractStatus.sweepParents) { 282 t.Fatal("watchdog not restored properly", contract) 283 } 284 if contract.sweepParents[0].ID() != expectedFileContractStatus.sweepParents[0].ID() { 285 t.Fatal("watchdog not restored properly", contract) 286 } 287 if contract.windowStart != expectedFileContractStatus.windowStart { 288 t.Fatal("watchdog not restored properly", contract) 289 } 290 if contract.windowEnd != expectedFileContractStatus.windowEnd { 291 t.Fatal("watchdog not restored properly", contract) 292 } 293 if len(c.staticWatchdog.archivedContracts) != 1 { 294 t.Fatal("watchdog not restored properly", c.staticWatchdog.archivedContracts) 295 } 296 archivedContract, ok := c.staticWatchdog.archivedContracts[types.FileContractID{2}] 297 if !ok { 298 t.Fatal("watchdog not restored properly", c.staticWatchdog.archivedContracts) 299 } 300 if !reflect.DeepEqual(archivedContract, expectedArchivedContract) { 301 t.Fatal("Archived contract not restored properly", archivedContract) 302 } 303 304 // Check churnLimiter state. 305 aggregateChurn, maxChurn := c.staticChurnLimiter.managedAggregateAndMaxChurn() 306 if aggregateChurn != 123456 { 307 t.Fatal("Expected 123456 aggregate churn", aggregateChurn) 308 } 309 if maxChurn != a.MaxPeriodChurn { 310 t.Fatal("Expected 1357 max churn", maxChurn) 311 } 312 remainingChurnBudget, periodBudget := c.staticChurnLimiter.managedChurnBudget() 313 if remainingChurnBudget != -789 { 314 t.Fatal("Expected -789 remainingChurnBudget", remainingChurnBudget) 315 } 316 expectedPeriodBudget := 1357 - 123456 317 if periodBudget != expectedPeriodBudget { 318 t.Fatal("Expected remainingChurnBudget", periodBudget) 319 } 320 } 321 322 // TestConvertPersist tests that contracts previously stored in the 323 // .journal format can be converted to the .contract format. 324 func TestConvertPersist(t *testing.T) { 325 if testing.Short() { 326 t.SkipNow() 327 } 328 329 dir := build.TempDir(filepath.Join("contractor", t.Name())) 330 os.MkdirAll(dir, 0700) 331 // copy the test data into the temp folder 332 testdata, err := ioutil.ReadFile(filepath.Join("testdata", "TestConvertPersist.journal")) 333 if err != nil { 334 t.Fatal(err) 335 } 336 err = ioutil.WriteFile(filepath.Join(dir, "contractor.journal"), testdata, 0600) 337 if err != nil { 338 t.Fatal(err) 339 } 340 341 // convert the journal 342 err = convertPersist(dir, ratelimit.NewRateLimit(0, 0, 0)) 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 // load the persist 348 var p contractorPersist 349 err = persist.LoadJSON(persistMeta, &p, filepath.Join(dir, PersistFilename)) 350 if err != nil { 351 t.Fatal(err) 352 } 353 if !p.Allowance.Funds.Equals64(10) || p.Allowance.Hosts != 7 || p.Allowance.Period != 3 || p.Allowance.RenewWindow != 20 { 354 t.Fatal("recovered allowance was wrong:", p.Allowance) 355 } 356 357 // load the contracts 358 cs, err := proto.NewContractSet(filepath.Join(dir, "contracts"), ratelimit.NewRateLimit(0, 0, 0), modules.ProdDependencies) 359 if err != nil { 360 t.Fatal(err) 361 } 362 if cs.Len() != 1 { 363 t.Fatal("expected 1 contract, got", cs.Len()) 364 } 365 m := cs.ViewAll()[0] 366 if m.ID.String() != "792b5eec683819d78416a9e80cba454ebcb5a52eeac4f17b443d177bd425fc5c" { 367 t.Fatal("recovered contract has wrong ID", m.ID) 368 } 369 }