gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skyfile_test.go (about) 1 package renter 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "reflect" 9 "strings" 10 "testing" 11 "time" 12 13 "gitlab.com/NebulousLabs/errors" 14 "gitlab.com/NebulousLabs/fastrand" 15 "gitlab.com/SkynetLabs/skyd/build" 16 "gitlab.com/SkynetLabs/skyd/skymodules" 17 "go.sia.tech/siad/crypto" 18 "go.sia.tech/siad/modules" 19 ) 20 21 // TestIsCompressedFanout is a unit test for isCompressedFanout. 22 func TestIsCompressedFanout(t *testing.T) { 23 tests := []struct { 24 minPieces int 25 ct crypto.CipherType 26 expected bool 27 }{ 28 { 29 minPieces: 1, 30 ct: crypto.TypePlain, 31 expected: true, 32 }, 33 { 34 minPieces: 1, 35 ct: crypto.TypeTwofish, 36 expected: false, 37 }, 38 { 39 minPieces: 1, 40 ct: crypto.TypeThreefish, 41 expected: false, 42 }, 43 { 44 minPieces: 1, 45 ct: crypto.TypeXChaCha20, 46 expected: false, 47 }, 48 { 49 minPieces: 2, 50 ct: crypto.TypePlain, 51 expected: false, 52 }, 53 } 54 55 for i, test := range tests { 56 ec, err := skymodules.NewRSSubCode(test.minPieces, 1, crypto.SegmentSize) 57 if err != nil { 58 t.Fatal(err) 59 } 60 if isCompressedFanout(ec, test.ct) != test.expected { 61 t.Fatalf("testcase %v failed", i+1) 62 } 63 } 64 } 65 66 // TestTryResolveSkylinkV2 is a unit test for managedTryResolveSkylinkV2. 67 func TestTryResolveSkylinkV2(t *testing.T) { 68 if testing.Short() { 69 t.SkipNow() 70 } 71 t.Parallel() 72 73 wt, err := newWorkerTester(t.Name()) 74 if err != nil { 75 t.Fatal(err) 76 } 77 78 var mr crypto.Hash 79 fastrand.Read(mr[:]) 80 skylinkV1, err := skymodules.NewSkylinkV1(mr, 0, 0) 81 if err != nil { 82 t.Fatal(err) 83 } 84 85 // Set skylink on host. 86 srv, spk, sk := randomRegistryValue() 87 srv.Data = skylinkV1.Bytes() 88 srv.Revision++ 89 srv = srv.Sign(sk) 90 err = wt.UpdateRegistry(context.Background(), spk, srv) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 // Get the v2 skylink. 96 skylinkV2 := skymodules.NewSkylinkV2(spk, srv.Tweak) 97 98 // Resolve it. 99 slV1, entries, err := wt.rt.renter.managedTryResolveSkylinkV2(context.Background(), skylinkV2, true, false) 100 if err != nil { 101 t.Fatal(err) 102 } 103 104 // Skylinks should match. 105 if !reflect.DeepEqual(skylinkV1, slV1) { 106 t.Fatal("skylinks don't match") 107 } 108 109 // Should only be one entry 110 if len(entries) != 1 { 111 t.Fatal("Expected only 1 entry, got", len(entries)) 112 } 113 entry := entries[0] 114 115 // Entry shouldn't be nil. 116 expectedSRV := skymodules.NewRegistryEntry(spk, srv) 117 if !reflect.DeepEqual(entry, expectedSRV) { 118 t.Log(entry) 119 t.Log(expectedSRV) 120 t.Fatal("entry mismatch") 121 } 122 123 // Try resolving the v1 skylink. Should be a no-op. 124 slV1, entries, err = wt.rt.renter.managedTryResolveSkylinkV2(context.Background(), skylinkV1, true, false) 125 if err != nil { 126 t.Fatal(err) 127 } 128 if !reflect.DeepEqual(skylinkV1, slV1) { 129 t.Fatal("skylinks don't match") 130 } 131 if entries != nil { 132 t.Fatal("entries should be nil") 133 } 134 } 135 136 // TestShortFanoutPanic is a regression test for when the size of a fanout 137 // doesn't match the filesize. 138 func TestShortFanoutPanic(t *testing.T) { 139 if testing.Short() { 140 t.SkipNow() 141 } 142 t.Parallel() 143 144 wt, err := newWorkerTester(t.Name()) 145 if err != nil { 146 t.Fatal(err) 147 } 148 149 // Add 2 more hosts. 150 if _, err = wt.rt.addHost(t.Name() + "1"); err != nil { 151 t.Fatal(err) 152 } 153 if _, err = wt.rt.addHost(t.Name() + "2"); err != nil { 154 t.Fatal(err) 155 } 156 r := wt.rt.renter 157 158 // Wait for them to show up as workers. 159 err = build.Retry(600, 100*time.Millisecond, func() error { 160 _, err := wt.rt.miner.AddBlock() 161 if err != nil { 162 return err 163 } 164 r.staticWorkerPool.callUpdate(r) 165 workers := r.staticWorkerPool.callWorkers() 166 if len(workers) < 3 { 167 return fmt.Errorf("expected %v workers but got %v", 3, len(workers)) 168 } 169 return nil 170 }) 171 if err != nil { 172 t.Fatal(err) 173 } 174 175 // Prepare a metadata for a basic file. 176 fileSize := modules.SectorSize * 3 177 md := skymodules.SkyfileMetadata{ 178 Filename: "test", 179 Length: fileSize, 180 } 181 metadataBytes, err := skymodules.SkyfileMetadataBytes(md) 182 if err != nil { 183 t.Fatal(err) 184 } 185 186 // Get some random data and fanout. 187 data := fastrand.Bytes(int(fileSize)) 188 ec, err := skymodules.NewRSSubCode(2, 1, crypto.SegmentSize) 189 if err != nil { 190 t.Fatal(err) 191 } 192 193 // Init the upload. 194 fileNode, err := r.managedInitFileNode(skymodules.FileUploadParams{ 195 CipherType: crypto.TypePlain, 196 ErasureCode: ec, 197 SiaPath: skymodules.RandomSiaPath(), 198 }, 0) 199 if err != nil { 200 t.Fatal(err) 201 } 202 203 // Upload the fanout. 204 chunkReader := NewFanoutChunkReader(bytes.NewReader(data), fileNode.ErasureCode(), fileNode.MasterKey()) 205 _, err = r.callUploadStreamFromReaderWithFileNode(context.Background(), fileNode, chunkReader, 0) 206 if err != nil { 207 t.Fatal(err) 208 } 209 if err := fileNode.Close(); err != nil { 210 t.Fatal(err) 211 } 212 fanout := chunkReader.Fanout() 213 214 // Shrink the fanout to 1 chunk. 215 fanout = fanout[:ec.NumPieces()*crypto.HashSize] 216 217 // Prepare a layout. 218 sl := skymodules.SkyfileLayout{ 219 Version: skymodules.SkyfileVersion, 220 Filesize: fileSize, 221 MetadataSize: uint64(len(metadataBytes)), 222 FanoutSize: uint64(len(fanout)), 223 FanoutDataPieces: uint8(ec.MinPieces()), 224 FanoutParityPieces: uint8(ec.NumPieces() - ec.MinPieces()), 225 CipherType: crypto.TypePlain, 226 } 227 228 // Prepare a base sector with fanout but also with the file data. 229 bs, fetchSize, _ := skymodules.BuildBaseSector(sl.Encode(), fanout, metadataBytes, nil) 230 if err != nil { 231 t.Fatal(err) 232 } 233 skylink, err := skymodules.NewSkylinkV1(crypto.MerkleRoot(bs), 0, fetchSize) 234 if err != nil { 235 t.Fatal(err) 236 } 237 238 // Upload the base sector. 239 err = r.managedUploadBaseSector(context.Background(), skymodules.SkyfileUploadParameters{ 240 SiaPath: skymodules.RandomSiaPath(), 241 }, bs, skylink) 242 if err != nil { 243 t.Fatal(err) 244 } 245 246 // Download the file. This should fail due to the short fanout. 247 _, _, err = r.DownloadSkylink(skylink, time.Hour, skymodules.DefaultSkynetPricePerMS) 248 if err == nil || !strings.Contains(err.Error(), skymodules.ErrMalformedBaseSector.Error()) { 249 t.Fatal(err) 250 } 251 } 252 253 // TestParseSkyfileMetadata tests parsing a recursive skyfile metadata. 254 func TestParseSkyfileMetadataRecursive(t *testing.T) { 255 if testing.Short() { 256 t.SkipNow() 257 } 258 259 wt, err := newWorkerTester(t.Name()) 260 if err != nil { 261 t.Fatal(err) 262 } 263 defer func() { 264 if err := wt.Close(); err != nil { 265 t.Fatal(err) 266 } 267 }() 268 r := wt.rt.renter 269 270 // Prepare a metadata for a basic file. 271 fileSize := 3 * modules.SectorSize * modules.SectorSize 272 md := skymodules.SkyfileMetadata{ 273 Filename: "test", 274 Length: fileSize, 275 } 276 metadataBytes, err := skymodules.SkyfileMetadataBytes(md) 277 if err != nil { 278 t.Fatal(err) 279 } 280 281 // Upload a huge amount of data to force a base sector recursion of 282 // depth 1. 283 data := fastrand.Bytes(int(fileSize)) 284 ec, err := skymodules.NewRSSubCode(2, 1, crypto.SegmentSize) 285 if err != nil { 286 t.Fatal(err) 287 } 288 289 // Upload the fanout. We don't really upload it here. Instead we just 290 // use a chunkreader to figure out what the fanout would be for that 291 // file. 292 fileNode, err := r.managedInitFileNode(skymodules.FileUploadParams{ 293 CipherType: crypto.TypePlain, 294 ErasureCode: ec, 295 SiaPath: skymodules.RandomSiaPath(), 296 }, 0) 297 if err != nil { 298 t.Fatal(err) 299 } 300 chunkReader := NewFanoutChunkReader(bytes.NewReader(data), fileNode.ErasureCode(), fileNode.MasterKey()) 301 if err := fileNode.Close(); err != nil { 302 t.Fatal(err) 303 } 304 305 // Read all chunks. 306 for { 307 _, _, err := chunkReader.ReadChunk() 308 if errors.Contains(err, io.EOF) { 309 break // done 310 } 311 if err != nil { 312 t.Fatal(err) 313 } 314 } 315 fanout := chunkReader.Fanout() 316 317 // Prepare a valid layout. 318 sl := skymodules.NewSkyfileLayout(fileSize, uint64(len(metadataBytes)), uint64(len(fanout)), ec, crypto.TypePlain) 319 320 // Prepare a base sector with fanout but also with the file data. 321 bs, fetchSize, extension := skymodules.BuildBaseSector(sl.Encode(), fanout, metadataBytes, nil) 322 if err != nil { 323 t.Fatal(err) 324 } 325 if len(extension) != 2 { 326 t.Fatal("the depth of the returned extension should be 2", len(extension)) 327 } 328 if len(bs) != int(modules.SectorSize) { 329 t.Fatal("basesector should be exactly a sectorsize large at this point", len(bs)) 330 } 331 332 skylink, err := skymodules.NewSkylinkV1(crypto.MerkleRoot(bs), 0, fetchSize) 333 if err != nil { 334 t.Fatal(err) 335 } 336 for _, ext := range extension { 337 bs = append(bs, ext...) 338 } 339 340 // Upload the base sector. 341 err = r.managedUploadBaseSector(context.Background(), skymodules.SkyfileUploadParameters{ 342 SiaPath: skymodules.RandomSkynetFilePath(), 343 }, bs, skylink) 344 if err != nil { 345 t.Fatal(err) 346 } 347 348 // Download the base sector using the skylink and parse it. 349 offset, fetchSize, err := skylink.OffsetAndFetchSize() 350 if err != nil { 351 t.Fatal(err) 352 } 353 var bs2 []byte 354 err = build.Retry(600, 100*time.Millisecond, func() error { 355 bs2, _, _, err = r.managedDownloadByRoot(context.Background(), skylink.MerkleRoot(), offset, fetchSize, skymodules.DefaultSkynetPricePerMS) 356 if err != nil { 357 r.staticWorkerPool.callUpdate(r) 358 return err 359 } 360 return nil 361 }) 362 if err != nil { 363 t.Fatal(err) 364 } 365 366 // Compare base sectors. 367 if !bytes.Equal(bs[:modules.SectorSize], bs2) { 368 t.Fatal("base sectors don't match") 369 } 370 371 var sl2 skymodules.SkyfileLayout 372 var fanout2, rawSM []byte 373 err = build.Retry(600, 100*time.Millisecond, func() error { 374 sl2, fanout2, _, rawSM, _, _, err = r.ParseSkyfileMetadata(bs2) 375 if err != nil { 376 r.staticWorkerPool.callUpdate(r) 377 return err 378 } 379 return nil 380 }) 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 // Compare fanouts. 386 if !bytes.Equal(sl.Encode(), sl2.Encode()) { 387 t.Fatal("fanout mismatch") 388 } 389 if !bytes.Equal(metadataBytes, rawSM) { 390 t.Fatal("md mismatch") 391 } 392 if !bytes.Equal(fanout, fanout2) { 393 t.Fatal("fanout mismatch") 394 } 395 }