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  }