gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/skynetblocklist/skynetblocklist_test.go (about) 1 package skynetblocklist 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "gitlab.com/NebulousLabs/encoding" 12 "gitlab.com/NebulousLabs/errors" 13 "gitlab.com/NebulousLabs/fastrand" 14 "gitlab.com/SkynetLabs/skyd/build" 15 "gitlab.com/SkynetLabs/skyd/skymodules" 16 "go.sia.tech/siad/crypto" 17 "go.sia.tech/siad/persist" 18 ) 19 20 // testDir is a helper function for creating the testing directory 21 func testDir(name string) string { 22 return build.TempDir("skynetblocklist", name) 23 } 24 25 // checkNumPersistedLinks checks that the expected number of links has been 26 // persisted on disk by checking the size of the persistence file. 27 func checkNumPersistedLinks(blocklistPath string, numLinks int) error { 28 expectedSize := numLinks*int(persistSize) + int(persist.MetadataPageSize) 29 if fi, err := os.Stat(blocklistPath); err != nil { 30 return errors.AddContext(err, "failed to get blocklist filesize") 31 } else if fi.Size() != int64(expectedSize) { 32 return fmt.Errorf("expected %v links and to have a filesize of %v but was %v", numLinks, expectedSize, fi.Size()) 33 } 34 return nil 35 } 36 37 // checkIsBlocked is a helper to check the IsBlocked method for both if the 38 // skylink is blocked but also if the data should be deleted. 39 func checkIsBlocked(sb *SkynetBlocklist, isBlockedExpected, shouldDeleteExpected bool, skylink skymodules.Skylink) error { 40 shouldDelete, isBlocked := sb.IsBlocked(skylink) 41 if isBlocked != isBlockedExpected { 42 return fmt.Errorf("isBlocked %v, expected %v", isBlocked, isBlockedExpected) 43 } 44 if shouldDelete != shouldDeleteExpected { 45 return fmt.Errorf("should delete %v, expected %v", shouldDelete, shouldDeleteExpected) 46 } 47 return nil 48 } 49 50 // TestPersist tests the persistence of the Skynet blocklist. 51 func TestPersist(t *testing.T) { 52 if testing.Short() { 53 t.SkipNow() 54 } 55 t.Parallel() 56 57 // Define blocklist tests 58 var skylink skymodules.Skylink 59 blocklistTest := func(hash crypto.Hash, ppe int64, shouldDeleteExpected bool) error { 60 // Create a new SkynetBlocklist 61 testdir := testDir(t.Name()) 62 sb, err := New(testdir) 63 if err != nil { 64 t.Fatal(err) 65 } 66 67 filename := filepath.Join(testdir, persistFile) 68 if filename != sb.staticAop.FilePath() { 69 t.Fatalf("Expected filepath %v, was %v", filename, sb.staticAop.FilePath()) 70 } 71 72 // There should be no skylinks in the blocklist 73 if len(sb.hashes) != 0 { 74 t.Fatal("Expected blocklist to be empty but found:", len(sb.hashes)) 75 } 76 77 // Create the inputs and update the initial blocklist 78 add := []crypto.Hash{hash} 79 remove := []crypto.Hash{hash} 80 err = sb.UpdateBlocklist(add, remove, ppe) 81 if err != nil { 82 return err 83 } 84 85 // Blocklist should be empty because we added and then removed the same 86 // skylink 87 if len(sb.hashes) != 0 { 88 return fmt.Errorf("Expected blocklist to be empty but found: %v", len(sb.hashes)) 89 } 90 91 // Verify that the correct number of links were persisted to verify no links 92 // are being truncated 93 if err := checkNumPersistedLinks(filename, 2); err != nil { 94 return fmt.Errorf("error verifying correct number of links: %v", err) 95 } 96 97 // Add the skylink again 98 err = sb.UpdateBlocklist(add, []crypto.Hash{}, ppe) 99 if err != nil { 100 return err 101 } 102 103 // There should be 1 element in the blocklist now 104 if len(sb.hashes) != 1 { 105 return fmt.Errorf("Expected 1 element in the blocklist but found: %v", len(sb.hashes)) 106 } 107 if err := checkIsBlocked(sb, true, shouldDeleteExpected, skylink); err != nil { 108 return err 109 } 110 111 // Verify persist file size 112 if err := checkNumPersistedLinks(filename, 3); err != nil { 113 return fmt.Errorf("error verifying correct number of links: %v", err) 114 } 115 116 // Load a new Skynet Blocklist to verify the contents from disk get loaded 117 // properly 118 sb2, err := New(testdir) 119 if err != nil { 120 return err 121 } 122 123 // Verify that the correct number of links were persisted to verify no links 124 // are being truncated 125 if err := checkNumPersistedLinks(filename, 3); err != nil { 126 return fmt.Errorf("error verifying correct number of links: %v", err) 127 } 128 129 // There should be 1 element in the blocklist 130 if len(sb2.hashes) != 1 { 131 return fmt.Errorf("Expected 1 element in the blocklist but found: %v", len(sb2.hashes)) 132 } 133 if err := checkIsBlocked(sb, true, shouldDeleteExpected, skylink); err != nil { 134 return err 135 } 136 137 // Add the skylink again 138 err = sb2.UpdateBlocklist(add, []crypto.Hash{}, ppe) 139 if err != nil { 140 return err 141 } 142 143 // There should still only be 1 element in the blocklist 144 if len(sb2.hashes) != 1 { 145 return fmt.Errorf("Expected 1 element in the blocklist but found: %v", len(sb2.hashes)) 146 } 147 if err := checkIsBlocked(sb2, true, shouldDeleteExpected, skylink); err != nil { 148 return err 149 } 150 151 // Verify persist file size 152 if err := checkNumPersistedLinks(filename, 3); err != nil { 153 return fmt.Errorf("error verifying correct number of links: %v", err) 154 } 155 156 // Load another new Skynet Blocklist to verify the contents from disk get loaded 157 // properly 158 sb3, err := New(testdir) 159 if err != nil { 160 return err 161 } 162 163 // Verify that the correct number of links were persisted to verify no links 164 // are being truncated 165 if err := checkNumPersistedLinks(filename, 3); err != nil { 166 return fmt.Errorf("error verifying correct number of links: %v", err) 167 } 168 169 // There should be 1 element in the blocklist 170 if len(sb3.hashes) != 1 { 171 return fmt.Errorf("Expected 1 element in the blocklist but found: %v", len(sb3.hashes)) 172 } 173 if err := checkIsBlocked(sb3, true, shouldDeleteExpected, skylink); err != nil { 174 return err 175 } 176 return nil 177 } 178 179 // Check once where the file should not be deleted due to being in the probationary period 180 hash := crypto.HashObject(skylink.MerkleRoot()) 181 probationaryPeriodEnd := time.Now().Add(time.Hour).Unix() 182 err := blocklistTest(hash, probationaryPeriodEnd, false) 183 if err != nil { 184 t.Fatal(err) 185 } 186 187 // Check once where the file should be deleted due to not being in the probationary period 188 probationaryPeriodEnd = 0 189 err = blocklistTest(hash, probationaryPeriodEnd, true) 190 if err != nil { 191 t.Fatal(err) 192 } 193 } 194 195 // TestPersistCorruption tests the persistence of the Skynet blocklist when corruption occurs. 196 func TestPersistCorruption(t *testing.T) { 197 if testing.Short() { 198 t.SkipNow() 199 } 200 t.Parallel() 201 202 // Create a new SkynetBlocklist 203 testdir := testDir(t.Name()) 204 sb, err := New(testdir) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 filename := filepath.Join(testdir, persistFile) 210 if filename != sb.staticAop.FilePath() { 211 t.Fatalf("Expected filepath %v, was %v", filename, sb.staticAop.FilePath()) 212 } 213 214 // There should be no skylinks in the blocklist 215 if len(sb.hashes) != 0 { 216 t.Fatal("Expected blocklist to be empty but found:", len(sb.hashes)) 217 } 218 219 // Append a bunch of random data to the end of the blocklist file to test 220 // corruption 221 f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, skymodules.DefaultFilePerm) 222 if err != nil { 223 t.Fatal(err) 224 } 225 minNumBytes := int(2 * persist.MetadataPageSize) 226 _, err = f.Write(fastrand.Bytes(minNumBytes + fastrand.Intn(minNumBytes))) 227 if err != nil { 228 t.Fatal(err) 229 } 230 err = f.Close() 231 if err != nil { 232 t.Fatal(err) 233 } 234 235 // The filesize with corruption should be greater than the persist length. 236 fi, err := os.Stat(filename) 237 if err != nil { 238 t.Fatal(err) 239 } 240 filesize := fi.Size() 241 if uint64(filesize) <= sb.staticAop.PersistLength() { 242 t.Fatalf("Expected file size greater than %v, got %v", sb.staticAop.PersistLength(), filesize) 243 } 244 245 // Update blocklist 246 var skylink skymodules.Skylink 247 hash := crypto.HashObject(skylink.MerkleRoot()) 248 add := []crypto.Hash{hash} 249 remove := []crypto.Hash{hash} 250 err = sb.UpdateBlocklist(add, remove, 0) 251 if err != nil { 252 t.Fatal(err) 253 } 254 255 // The filesize should be equal to the persist length now due to the 256 // truncate when updating. 257 fi, err = os.Stat(filename) 258 if err != nil { 259 t.Fatal(err) 260 } 261 filesize = fi.Size() 262 if uint64(filesize) != sb.staticAop.PersistLength() { 263 t.Fatalf("Expected file size %v, got %v", sb.staticAop.PersistLength(), filesize) 264 } 265 266 // Blocklist should be empty because we added and then removed the same 267 // skylink 268 if len(sb.hashes) != 0 { 269 t.Fatal("Expected blocklist to be empty but found:", len(sb.hashes)) 270 } 271 272 // Add the skylink again 273 err = sb.UpdateBlocklist(add, []crypto.Hash{}, 0) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 // There should be 1 element in the blocklist now 279 if len(sb.hashes) != 1 { 280 t.Fatal("Expected 1 element in the blocklist but found:", len(sb.hashes)) 281 } 282 err = checkIsBlocked(sb, true, true, skylink) 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 // Load a new Skynet Blocklist to verify the contents from disk get loaded 288 // properly 289 sb2, err := New(testdir) 290 if err != nil { 291 t.Fatal(err) 292 } 293 294 // There should be 1 element in the blocklist 295 if len(sb2.hashes) != 1 { 296 t.Fatal("Expected 1 element in the blocklist but found:", len(sb2.hashes)) 297 } 298 err = checkIsBlocked(sb2, true, true, skylink) 299 if err != nil { 300 t.Fatal(err) 301 } 302 303 // Add the skylink again 304 err = sb2.UpdateBlocklist(add, []crypto.Hash{}, 0) 305 if err != nil { 306 t.Fatal(err) 307 } 308 309 // There should still only be 1 element in the blocklist 310 if len(sb2.hashes) != 1 { 311 t.Fatal("Expected 1 element in the blocklist but found:", len(sb2.hashes)) 312 } 313 err = checkIsBlocked(sb2, true, true, skylink) 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 // Load another new Skynet Blocklist to verify the contents from disk get loaded 319 // properly 320 sb3, err := New(testdir) 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 // There should be 1 element in the blocklist 326 if len(sb3.hashes) != 1 { 327 t.Fatal("Expected 1 element in the blocklist but found:", len(sb3.hashes)) 328 } 329 err = checkIsBlocked(sb3, true, true, skylink) 330 if err != nil { 331 t.Fatal(err) 332 } 333 334 // The final filesize should be equal to the persist length. 335 fi, err = os.Stat(filename) 336 if err != nil { 337 t.Fatal(err) 338 } 339 filesize = fi.Size() 340 if uint64(filesize) != sb3.staticAop.PersistLength() { 341 t.Fatalf("Expected file size %v, got %v", sb3.staticAop.PersistLength(), filesize) 342 } 343 344 // Verify that the correct number of links were persisted to verify no links 345 // are being truncated. Expect 3 links for the Add, remove, Add. Then final 346 // add would not be persisted because it already existed. 347 if err = checkNumPersistedLinks(filename, 3); err != nil { 348 t.Errorf("error verifying correct number of links: %v", err) 349 } 350 } 351 352 // TestMarshalSia probes the marshalSia and unmarshalSia methods 353 func TestMarshalSia(t *testing.T) { 354 t.Parallel() 355 // Test MarshalSia 356 var skylink skymodules.Skylink 357 var buf bytes.Buffer 358 merkleRoot := skylink.MerkleRoot() 359 merkleRootHash := crypto.HashObject(merkleRoot) 360 listed := false 361 probationaryPeriodEnd := int64(123) 362 ll := persistEntry{merkleRootHash, probationaryPeriodEnd, listed} 363 writtenBytes := encoding.Marshal(ll) 364 buf.Write(writtenBytes) 365 if uint64(buf.Len()) != persistSize { 366 t.Fatalf("Expected buf to be of size %v but got %v", persistSize, buf.Len()) 367 } 368 ll.Listed = true 369 writtenBytes = encoding.Marshal(ll) 370 buf.Write(writtenBytes) 371 if uint64(buf.Len()) != 2*persistSize { 372 t.Fatalf("Expected buf to be of size %v but got %v", 2*persistSize, buf.Len()) 373 } 374 375 readBytes := buf.Bytes() 376 if uint64(len(readBytes)) != 2*persistSize { 377 t.Fatalf("Expected %v read bytes but got %v", 2*persistSize, len(readBytes)) 378 } 379 err := encoding.Unmarshal(readBytes[:persistSize], &ll) 380 if err != nil { 381 t.Fatal(err) 382 } 383 if merkleRootHash != ll.Hash { 384 t.Fatalf("MerkleRoot hashes don't match, expected %v, got %v", merkleRootHash, ll.Hash) 385 } 386 if probationaryPeriodEnd != ll.ProbationaryPeriodEnd { 387 t.Fatalf("Probationary periods don't match, expected %v, got %v", probationaryPeriodEnd, ll.ProbationaryPeriodEnd) 388 } 389 if ll.Listed { 390 t.Fatal("expected persisted link to not be blocked") 391 } 392 err = encoding.Unmarshal(readBytes[persistSize:2*persistSize], &ll) 393 if err != nil { 394 t.Fatal(err) 395 } 396 if merkleRootHash != ll.Hash { 397 t.Fatalf("MerkleRoot hashes don't match, expected %v, got %v", merkleRootHash, ll.Hash) 398 } 399 if probationaryPeriodEnd != ll.ProbationaryPeriodEnd { 400 t.Fatalf("Probationary periods don't match, expected %v, got %v", probationaryPeriodEnd, ll.ProbationaryPeriodEnd) 401 } 402 if !ll.Listed { 403 t.Fatal("expected persisted link to be blocked") 404 } 405 406 // Test unmarshalBlocklist 407 blocklist, err := unmarshalObjects(&buf, metadataVersion) 408 if err != nil { 409 t.Fatal(err) 410 } 411 412 // Since the merkleroot is the same the blocklist should only have a length 413 // of 1 since the non blocklisted merkleroot was added first 414 if len(blocklist) != 1 { 415 t.Fatalf("Incorrect number of blocklisted merkleRoots, expected %v, got %v", 1, len(blocklist)) 416 } 417 ppe, ok := blocklist[merkleRootHash] 418 if !ok { 419 t.Fatal("merkleroot not found in blocklist") 420 } 421 if ppe != probationaryPeriodEnd { 422 t.Fatalf("probationaryPeriodEnd mismatch; found %v, expected %v", ppe, probationaryPeriodEnd) 423 } 424 }