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  }