gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/merkleroots_test.go (about) 1 package proto 2 3 import ( 4 "fmt" 5 "os" 6 "path" 7 "reflect" 8 "testing" 9 10 "gitlab.com/NebulousLabs/errors" 11 "gitlab.com/NebulousLabs/fastrand" 12 13 "gitlab.com/SkynetLabs/skyd/build" 14 "go.sia.tech/siad/crypto" 15 ) 16 17 // cmpRoots is a helper function that compares the in-memory file structure and 18 // on-disk roots of two merkleRoots objects. 19 func cmpRoots(m1, m2 *merkleRoots) error { 20 roots1, err1 := m1.merkleRoots() 21 roots2, err2 := m2.merkleRoots() 22 if err1 != nil || err2 != nil { 23 return errors.AddContext(errors.Compose(err1, err2), "failed to compare on-disk roots") 24 } 25 if len(roots1) != len(roots2) { 26 return fmt.Errorf("len of roots on disk doesn't match %v != %v", 27 len(roots1), len(roots2)) 28 } 29 if len(m1.uncachedRoots) != len(m2.uncachedRoots) { 30 return fmt.Errorf("len of uncachedRoots doesn't match %v != %v", 31 len(m1.uncachedRoots), len(m2.uncachedRoots)) 32 } 33 if len(m1.cachedSubTrees) != len(m2.cachedSubTrees) { 34 return fmt.Errorf("len of cached subTrees doesn't match %v != %v", 35 len(m1.cachedSubTrees), len(m2.cachedSubTrees)) 36 } 37 if m1.numMerkleRoots != m2.numMerkleRoots { 38 return fmt.Errorf("numMerkleRoots fields don't match %v != %v", 39 m1.numMerkleRoots, m2.numMerkleRoots) 40 } 41 for i := 0; i < len(roots1); i++ { 42 if roots1[i] != roots2[i] { 43 return errors.New("on-disk roots don't match") 44 } 45 } 46 for i := 0; i < len(m1.uncachedRoots); i++ { 47 if m1.uncachedRoots[i] != m2.uncachedRoots[i] { 48 return errors.New("uncached roots don't match") 49 } 50 } 51 for i := 0; i < len(m1.cachedSubTrees); i++ { 52 if !reflect.DeepEqual(m1.cachedSubTrees[i], m2.cachedSubTrees[i]) { 53 return fmt.Errorf("cached trees at index %v don't match", i) 54 } 55 } 56 return nil 57 } 58 59 // TestLoadExistingMerkleRoots tests if it is possible to load existing merkle 60 // roots from disk. 61 func TestLoadExistingMerkleRoots(t *testing.T) { 62 if testing.Short() { 63 t.SkipNow() 64 } 65 // Create a file for the test. 66 dir := build.TempDir(t.Name()) 67 if err := os.MkdirAll(dir, 0755); err != nil { 68 t.Fatal(err) 69 } 70 filePath := path.Join(dir, "file.dat") 71 file, err := os.Create(filePath) 72 if err != nil { 73 t.Fatal(err) 74 } 75 76 // Create sector roots. 77 merkleRoots := newMerkleRoots(file) 78 for i := 0; i < 200; i++ { 79 hash := crypto.Hash{} 80 copy(hash[:], fastrand.Bytes(crypto.HashSize)[:]) 81 merkleRoots.push(hash) 82 } 83 84 // Load the existing file using LoadExistingMerkleRoots 85 merkleRoots2, applyTxns, err := loadExistingMerkleRoots(file) 86 if err != nil || applyTxns { 87 t.Fatal(err) 88 } 89 if merkleRoots2.len() != merkleRoots.len() { 90 t.Errorf("expected len %v but was %v", merkleRoots.len(), merkleRoots2.len()) 91 } 92 // Check if they have the same roots. 93 roots, err := merkleRoots.merkleRoots() 94 roots2, err2 := merkleRoots2.merkleRoots() 95 if errors.Compose(err, err2) != nil { 96 t.Fatal(err) 97 } 98 for i := 0; i < len(roots); i++ { 99 if roots[i] != roots2[i] { 100 t.Errorf("roots at index %v don't match", i) 101 } 102 } 103 // Check if the cached subTrees match. 104 if len(merkleRoots.cachedSubTrees) != len(merkleRoots2.cachedSubTrees) { 105 t.Fatalf("expected %v cached trees but got %v", 106 len(merkleRoots.cachedSubTrees), len(merkleRoots2.cachedSubTrees)) 107 } 108 109 // Check if the computed roots match. 110 if merkleRoots.root() != merkleRoots2.root() { 111 t.Fatal("the roots don't match") 112 } 113 } 114 115 // TestInsertMerkleRoot tests the merkleRoots' insert method. 116 func TestInsertMerkleRoot(t *testing.T) { 117 if testing.Short() { 118 t.SkipNow() 119 } 120 dir := build.TempDir(t.Name()) 121 if err := os.MkdirAll(dir, 0755); err != nil { 122 t.Fatal(err) 123 } 124 filePath := path.Join(dir, "file.dat") 125 file, err := os.Create(filePath) 126 if err != nil { 127 t.Fatal(err) 128 } 129 130 // Create sector roots. 131 merkleRoots := newMerkleRoots(file) 132 for i := 0; i < 200; i++ { 133 hash := crypto.Hash{} 134 copy(hash[:], fastrand.Bytes(crypto.HashSize)[:]) 135 merkleRoots.push(hash) 136 } 137 138 // Replace the last root with a new hash. It shouldn't be cached and 139 // therefore no cached tree needs to be updated. 140 newHash := crypto.Hash{} 141 insertIndex := merkleRoots.len() - 1 142 copy(newHash[:], fastrand.Bytes(crypto.HashSize)[:]) 143 if err := merkleRoots.insert(insertIndex, newHash); err != nil { 144 t.Fatal("failed to insert root", err) 145 } 146 // Insert again at the same index to make sure insert is idempotent. 147 if err := merkleRoots.insert(insertIndex, newHash); err != nil { 148 t.Fatal("failed to insert root", err) 149 } 150 // Check if the last root matches the new hash. 151 roots, err := merkleRoots.merkleRoots() 152 if err != nil { 153 t.Fatal("failed to get roots from disk", err) 154 } 155 if roots[len(roots)-1] != newHash { 156 t.Fatal("root wasn't updated correctly on disk") 157 } 158 // Reload the roots. The in-memory structure and the roots on disk should 159 // still be consistent. 160 loadedRoots, applyTxns, err := loadExistingMerkleRootsFromSection(merkleRoots.rootsFile) 161 if err != nil || applyTxns { 162 t.Fatal("failed to load existing roots", err) 163 } 164 if err := cmpRoots(merkleRoots, loadedRoots); err != nil { 165 t.Fatal("loaded roots are inconsistent", err) 166 } 167 // Replace the first root with the new hash. It should be cached and 168 // therefore the first cached tree should also change. 169 if err := merkleRoots.insert(0, newHash); err != nil { 170 t.Fatal("failed to insert root", err) 171 } 172 // Check if the first root matches the new hash. 173 roots, err = merkleRoots.merkleRoots() 174 if err != nil { 175 t.Fatal("failed to get roots from disk", err) 176 } 177 if roots[0] != newHash { 178 t.Fatal("root wasn't updated correctly on disk") 179 } 180 if merkleRoots.cachedSubTrees[0].sum != newCachedSubTree(roots[:merkleRootsPerCache]).sum { 181 t.Fatal("cachedSubtree doesn't have expected sum") 182 } 183 // Reload the roots. The in-memory structure and the roots on disk should 184 // still be consistent. 185 loadedRoots, applyTxns, err = loadExistingMerkleRootsFromSection(merkleRoots.rootsFile) 186 if err != nil || applyTxns { 187 t.Fatal("failed to load existing roots", err) 188 } 189 if err := cmpRoots(merkleRoots, loadedRoots); err != nil { 190 t.Fatal("loaded roots are inconsistent", err) 191 } 192 } 193 194 // TestDeleteLastRoot tests the deleteLastRoot method. 195 func TestDeleteLastRoot(t *testing.T) { 196 if testing.Short() { 197 t.SkipNow() 198 } 199 dir := build.TempDir(t.Name()) 200 if err := os.MkdirAll(dir, 0755); err != nil { 201 t.Fatal(err) 202 } 203 filePath := path.Join(dir, "file.dat") 204 file, err := os.Create(filePath) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 // Create sector roots. We choose the number of merkle roots in a way that 210 // makes the first delete remove a uncached root and the second delete has 211 // to remove a cached tree. 212 numMerkleRoots := merkleRootsPerCache + 1 213 merkleRoots := newMerkleRoots(file) 214 for i := 0; i < numMerkleRoots; i++ { 215 hash := crypto.Hash{} 216 copy(hash[:], fastrand.Bytes(crypto.HashSize)[:]) 217 merkleRoots.push(hash) 218 } 219 220 // Delete the last sector root. This should call deleteLastRoot internally. 221 lastRoot, truncateSize, err := merkleRoots.prepareDelete(numMerkleRoots - 1) 222 if err != nil { 223 t.Fatal(err) 224 } 225 if err := merkleRoots.delete(numMerkleRoots-1, lastRoot, truncateSize); err != nil { 226 t.Fatal("failed to delete last root", err) 227 } 228 numMerkleRoots-- 229 // Check if the number of roots actually decreased. 230 if merkleRoots.numMerkleRoots != numMerkleRoots { 231 t.Fatal("numMerkleRoots wasn't decreased") 232 } 233 // Check if the file was truncated. 234 if roots, err := merkleRoots.merkleRoots(); err != nil { 235 t.Fatal("failed to get roots from disk", err) 236 } else if len(roots) != merkleRoots.numMerkleRoots { 237 t.Fatal("roots on disk don't match number of roots in memory") 238 } 239 // There should be 0 uncached roots now. 240 if len(merkleRoots.uncachedRoots) != 0 { 241 t.Fatal("expected 0 uncached roots but was", len(merkleRoots.uncachedRoots)) 242 } 243 // There should be 1 cached roots. 244 if len(merkleRoots.cachedSubTrees) != 1 { 245 t.Fatal("expected 1 cached root but was", len(merkleRoots.cachedSubTrees)) 246 } 247 248 // Delete the last sector root again. This time a cached root should be deleted too. 249 lastRoot, truncateSize, err = merkleRoots.prepareDelete(numMerkleRoots - 1) 250 if err != nil { 251 t.Fatal(err) 252 } 253 if err := merkleRoots.delete(numMerkleRoots-1, lastRoot, truncateSize); err != nil { 254 t.Fatal("failed to delete last root", err) 255 } 256 numMerkleRoots-- 257 // Check if the number of roots actually decreased. 258 if merkleRoots.numMerkleRoots != numMerkleRoots { 259 t.Fatal("numMerkleRoots wasn't decreased") 260 } 261 // Check if the file was truncated. 262 if roots, err := merkleRoots.merkleRoots(); err != nil { 263 t.Fatal("failed to get roots from disk", err) 264 } else if len(roots) != merkleRoots.numMerkleRoots { 265 t.Fatal("roots on disk don't match number of roots in memory") 266 } 267 // There should be 2^merkleRootsCacheHeight - 1 uncached roots now. 268 if len(merkleRoots.uncachedRoots) != merkleRootsPerCache-1 { 269 t.Fatal("expected 2^merkleRootsCacheHeight - 1 uncached roots but was", 270 len(merkleRoots.uncachedRoots)) 271 } 272 // There should be 0 cached roots. 273 if len(merkleRoots.cachedSubTrees) != 0 { 274 t.Fatal("expected 0 cached roots but was", len(merkleRoots.cachedSubTrees)) 275 } 276 277 // Reload the roots. The in-memory structure and the roots on disk should 278 // still be consistent. 279 loadedRoots, applyTxns, err := loadExistingMerkleRootsFromSection(merkleRoots.rootsFile) 280 if err != nil || applyTxns { 281 t.Fatal("failed to load existing roots", err) 282 } 283 if err := cmpRoots(merkleRoots, loadedRoots); err != nil { 284 t.Fatal("loaded roots are inconsistent", err) 285 } 286 } 287 288 // TestDelete tests the deleteRoot method by creating many roots 289 // and deleting random indices until there are no more roots left. 290 func TestDelete(t *testing.T) { 291 if testing.Short() { 292 t.SkipNow() 293 } 294 dir := build.TempDir(t.Name()) 295 if err := os.MkdirAll(dir, 0755); err != nil { 296 t.Fatal(err) 297 } 298 filePath := path.Join(dir, "file.dat") 299 file, err := os.Create(filePath) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 // Create many sector roots. 305 numMerkleRoots := 1000 306 merkleRoots := newMerkleRoots(file) 307 for i := 0; i < numMerkleRoots; i++ { 308 hash := crypto.Hash{} 309 copy(hash[:], fastrand.Bytes(crypto.HashSize)[:]) 310 merkleRoots.push(hash) 311 } 312 313 for merkleRoots.numMerkleRoots > 0 { 314 // 1% chance to reload the roots and check if they are consistent. 315 if fastrand.Intn(100) == 0 { 316 loadedRoots, applyTxns, err := loadExistingMerkleRootsFromSection(merkleRoots.rootsFile) 317 if err != nil || applyTxns { 318 t.Fatal("failed to load existing roots", err) 319 } 320 if err := cmpRoots(loadedRoots, merkleRoots); err != nil { 321 t.Fatal(err) 322 } 323 } 324 // Randomly choose a root to delete. 325 deleteIndex := fastrand.Intn(merkleRoots.numMerkleRoots) 326 327 // Get some metrics to be able to check if delete was working as expected. 328 numRoots := merkleRoots.numMerkleRoots 329 numCached := len(merkleRoots.cachedSubTrees) 330 numUncached := len(merkleRoots.uncachedRoots) 331 cachedIndex, cached := merkleRoots.isIndexCached(deleteIndex) 332 333 // Call delete twice to make sure it's idempotent. 334 lastRoot, truncateSize, err := merkleRoots.prepareDelete(deleteIndex) 335 if err != nil { 336 t.Fatal(err) 337 } 338 if err := merkleRoots.delete(deleteIndex, lastRoot, truncateSize); err != nil { 339 t.Fatal("failed to delete random index", deleteIndex, err) 340 } 341 if err := merkleRoots.delete(deleteIndex, lastRoot, truncateSize); err != nil { 342 t.Fatal("failed to delete random index", deleteIndex, err) 343 } 344 // Number of roots should have decreased. 345 if merkleRoots.numMerkleRoots != numRoots-1 { 346 t.Fatal("number of roots in memory should have decreased") 347 } 348 // Number of roots on disk should have decreased. 349 if roots, err := merkleRoots.merkleRoots(); err != nil { 350 t.Fatal("failed to get roots from disk") 351 } else if len(roots) != numRoots-1 { 352 t.Fatal("number of roots on disk should have decreased") 353 } 354 // If the number of uncached roots was >0 the cached roots should be 355 // the same and the number of uncached roots should have decreased by 1. 356 if numUncached > 0 && !(len(merkleRoots.cachedSubTrees) == numCached && len(merkleRoots.uncachedRoots) == numUncached-1) { 357 t.Fatal("deletion of uncached root failed") 358 } 359 // If the number of uncached roots was 0, there should be 1 less cached 360 // root and the uncached roots should have length 361 // 2^merkleRootsCacheHeight-1. 362 if numUncached == 0 && !(len(merkleRoots.cachedSubTrees) == numCached-1 && len(merkleRoots.uncachedRoots) == (1<<merkleRootsCacheHeight)-1) { 363 t.Fatal("deletion of cached root failed") 364 } 365 // If the deleted root was cached we expect the cache to have the 366 // correct, updated value. 367 if cached && len(merkleRoots.cachedSubTrees) > cachedIndex { 368 subTreeLen := merkleRootsPerCache 369 from := cachedIndex * merkleRootsPerCache 370 roots, err := merkleRoots.merkleRootsFromIndexFromDisk(from, from+subTreeLen) 371 if err != nil { 372 t.Fatal("failed to read roots of subTree", err) 373 } 374 if merkleRoots.cachedSubTrees[cachedIndex].sum != newCachedSubTree(roots).sum { 375 t.Fatal("new cached root sum doesn't match expected sum") 376 } 377 } 378 } 379 } 380 381 // TestMerkleRootsRandom creates a large number of merkle roots and runs random 382 // valid operations on them that shouldn't result in any errors. 383 func TestMerkleRootsRandom(t *testing.T) { 384 if testing.Short() { 385 t.SkipNow() 386 } 387 dir := build.TempDir(t.Name()) 388 if err := os.MkdirAll(dir, 0755); err != nil { 389 t.Fatal(err) 390 } 391 filePath := path.Join(dir, "file.dat") 392 file, err := os.Create(filePath) 393 if err != nil { 394 t.Fatal(err) 395 } 396 397 // Create many sector roots. 398 numMerkleRoots := 10000 399 merkleRoots := newMerkleRoots(file) 400 for i := 0; i < numMerkleRoots; i++ { 401 hash := crypto.Hash{} 402 copy(hash[:], fastrand.Bytes(crypto.HashSize)[:]) 403 merkleRoots.push(hash) 404 } 405 406 // Randomly insert or delete elements. 407 for i := 0; i < numMerkleRoots; i++ { 408 // 1% chance to reload the roots and check if they are consistent. 409 if fastrand.Intn(100) == 0 { 410 loadedRoots, applyTxns, err := loadExistingMerkleRootsFromSection(merkleRoots.rootsFile) 411 if err != nil || applyTxns { 412 t.Fatal("failed to load existing roots") 413 } 414 if err := cmpRoots(loadedRoots, merkleRoots); err != nil { 415 t.Fatal(err) 416 } 417 } 418 operation := fastrand.Intn(2) 419 420 // Delete 421 if operation == 0 { 422 index := fastrand.Intn(merkleRoots.numMerkleRoots) 423 lastRoot, truncateSize, err := merkleRoots.prepareDelete(index) 424 if err != nil { 425 t.Fatal(err) 426 } 427 if err := merkleRoots.delete(index, lastRoot, truncateSize); err != nil { 428 t.Fatalf("failed to delete %v: %v", index, err) 429 } 430 continue 431 } 432 433 // Insert 434 var hash crypto.Hash 435 copy(hash[:], fastrand.Bytes(len(hash))) 436 index := fastrand.Intn(merkleRoots.numMerkleRoots + 1) 437 if err := merkleRoots.insert(index, hash); err != nil { 438 t.Fatalf("failed to insert %v at %v: %v", hash, index, err) 439 } 440 } 441 }