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  }