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