gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/erasure_test.go (about)

     1  package skymodules
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"testing"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  	"go.sia.tech/siad/crypto"
    11  )
    12  
    13  // TestErasureCode groups all of the tests the three implementations of the
    14  // erasure code interface, as defined in erasure.go.
    15  func TestErasureCode(t *testing.T) {
    16  	t.Run("RSCode", testRSCode)
    17  	t.Run("RSSubCode", testRSSubCode)
    18  	t.Run("Passthrough", testPassthrough)
    19  	t.Run("UniqueIdentifier", testUniqueIdentifier)
    20  	t.Run("DefaultConstructors", testDefaultConstructors)
    21  }
    22  
    23  // testRSCode tests the RSCode EC.
    24  func testRSCode(t *testing.T) {
    25  	badParams := []struct {
    26  		data, parity int
    27  	}{
    28  		{-1, -1},
    29  		{-1, 0},
    30  		{0, -1},
    31  		{0, 0},
    32  		{0, 1},
    33  	}
    34  	for _, ps := range badParams {
    35  		if _, err := NewRSCode(ps.data, ps.parity); err == nil {
    36  			t.Error("expected bad parameter error, got nil")
    37  		}
    38  	}
    39  
    40  	rsc, err := NewRSCode(10, 3)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	data := fastrand.Bytes(777)
    46  
    47  	pieces, err := rsc.Encode(data)
    48  	if err != nil {
    49  		t.Fatal(err)
    50  	}
    51  	_, err = rsc.Encode(nil)
    52  	if err == nil {
    53  		t.Fatal("expected nil data error, got nil")
    54  	}
    55  
    56  	buf := new(bytes.Buffer)
    57  	err = rsc.Recover(pieces, 777, buf)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	err = rsc.Recover(nil, 777, buf)
    62  	if err == nil {
    63  		t.Fatal("expected nil pieces error, got nil")
    64  	}
    65  
    66  	if !bytes.Equal(data, buf.Bytes()) {
    67  		t.Fatal("recovered data does not match original")
    68  	}
    69  }
    70  
    71  // testRSSubCode checks that individual segments of an encoded piece can be
    72  // recovered using the RSSub Code.
    73  func testRSSubCode(t *testing.T) {
    74  	segmentSize := crypto.SegmentSize
    75  	pieceSize := 4096
    76  	dataPieces := 10
    77  	parityPieces := 20
    78  	data := fastrand.Bytes(pieceSize * dataPieces)
    79  	originalData := make([]byte, len(data))
    80  	copy(originalData, data)
    81  	// Create the erasure coder.
    82  	rsc, err := NewRSSubCode(dataPieces, parityPieces, uint64(segmentSize))
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	// Allocate space for the pieces.
    87  	pieces := make([][]byte, dataPieces)
    88  	for i := range pieces {
    89  		pieces[i] = make([]byte, pieceSize)
    90  	}
    91  	// Write the data to the pieces.
    92  	buf := bytes.NewBuffer(data)
    93  	for i := range pieces {
    94  		if buf.Len() < pieceSize {
    95  			t.Fatal("Buffer is empty")
    96  		}
    97  		pieces[i] = make([]byte, pieceSize)
    98  		copy(pieces[i], buf.Next(pieceSize))
    99  	}
   100  	// Encode the pieces.
   101  	encodedPieces, err := rsc.EncodeShards(pieces)
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  	// Check that the parity shards have been created.
   106  	if len(encodedPieces) != rsc.NumPieces() {
   107  		t.Fatalf("encodedPieces should've length %v but was %v", rsc.NumPieces(), len(encodedPieces))
   108  	}
   109  	// Every piece should have pieceSize.
   110  	for _, piece := range encodedPieces {
   111  		if len(piece) != pieceSize {
   112  			t.Fatalf("expecte len(piece) to be %v but was %v", pieceSize, len(piece))
   113  		}
   114  	}
   115  	// Delete as many random pieces as possible.
   116  	for _, i := range fastrand.Perm(len(encodedPieces))[:parityPieces] {
   117  		encodedPieces[i] = nil
   118  	}
   119  	// Recover every segment individually.
   120  	dataOffset := 0
   121  	decodedSegmentSize := segmentSize * dataPieces
   122  	for segmentIndex := 0; segmentIndex < pieceSize/segmentSize; segmentIndex++ {
   123  		buf := new(bytes.Buffer)
   124  		segment := ExtractSegment(encodedPieces, segmentIndex, uint64(segmentSize))
   125  		err = rsc.Recover(segment, uint64(segmentSize*rsc.MinPieces()), buf)
   126  		if err != nil {
   127  			t.Fatal(err)
   128  		}
   129  		if !bytes.Equal(buf.Bytes(), originalData[dataOffset:dataOffset+decodedSegmentSize]) {
   130  			t.Fatal("decoded bytes don't equal original segment")
   131  		}
   132  		dataOffset += decodedSegmentSize
   133  	}
   134  	// Recover all segments at once.
   135  	buf = new(bytes.Buffer)
   136  	err = rsc.Recover(encodedPieces, uint64(len(data)), buf)
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	if !bytes.Equal(buf.Bytes(), originalData) {
   141  		t.Fatal("decoded bytes don't equal original data")
   142  	}
   143  }
   144  
   145  // testPassthrough verifies the functionality of the Passthrough EC.
   146  func testPassthrough(t *testing.T) {
   147  	ptec := NewPassthroughErasureCoder()
   148  
   149  	if ptec.NumPieces() != 1 {
   150  		t.Fatal("unexpected")
   151  	}
   152  	if ptec.MinPieces() != 1 {
   153  		t.Fatal("unexpected")
   154  	}
   155  
   156  	data := fastrand.Bytes(777)
   157  	pieces, err := ptec.Encode(data)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	if len(pieces) != 1 {
   162  		t.Fatal("unexpected amount of pieces")
   163  	}
   164  	if !bytes.Equal(pieces[0], data) {
   165  		t.Fatal("unexpected piece")
   166  	}
   167  
   168  	if ptec.Identifier() != "ECPassthrough" {
   169  		t.Fatal("unexpected")
   170  	}
   171  	pieces = [][]byte{data}
   172  	encoded, err := ptec.EncodeShards(pieces)
   173  	if err != nil {
   174  		t.Fatal(err)
   175  	}
   176  	if len(pieces) != len(encoded) || len(pieces) != 1 {
   177  		t.Fatal("unexpected")
   178  	}
   179  	if !bytes.Equal(pieces[0], encoded[0]) {
   180  		t.Fatal("unexpected data")
   181  	}
   182  
   183  	err = ptec.Reconstruct(pieces)
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  
   188  	buf := new(bytes.Buffer)
   189  	err = ptec.Recover(encoded, uint64(len(data)), buf)
   190  	if err != nil {
   191  		t.Fatal(err)
   192  	}
   193  	if !bytes.Equal(buf.Bytes(), data) {
   194  		t.Fatal("decoded bytes don't equal original data")
   195  	}
   196  
   197  	size, supported := ptec.SupportsPartialEncoding()
   198  	if !supported || size != crypto.SegmentSize {
   199  		t.Fatal("unexpected")
   200  	}
   201  }
   202  
   203  // testUniqueIdentifier checks that different erasure coders produce unique
   204  // identifiers and that CombinedSiaFilePath also produces unique siapaths using
   205  // the identifiers.
   206  func testUniqueIdentifier(t *testing.T) {
   207  	ec1, err1 := NewRSCode(1, 2)
   208  	ec2, err2 := NewRSCode(1, 2)
   209  	ec3, err3 := NewRSCode(1, 3)
   210  	ec4, err4 := NewRSSubCode(1, 2, 64)
   211  	ec5, err5 := NewRSCode(1, 1)
   212  	ec6 := NewPassthroughErasureCoder()
   213  
   214  	if err := errors.Compose(err1, err2, err3, err4, err5); err != nil {
   215  		t.Fatal(err)
   216  	}
   217  	if ec1.Identifier() != "1+1+2" {
   218  		t.Error("wrong identifier for ec1")
   219  	}
   220  	if ec2.Identifier() != "1+1+2" {
   221  		t.Error("wrong identifier for ec2")
   222  	}
   223  	if ec3.Identifier() != "1+1+3" {
   224  		t.Error("wrong identifier for ec3")
   225  	}
   226  	if ec4.Identifier() != "2+1+2" {
   227  		t.Error("wrong identifier for ec4")
   228  	}
   229  	if ec5.Identifier() != "1+1+1" {
   230  		t.Error("wrong identifier for ec5")
   231  	}
   232  	if ec6.Identifier() != "ECPassthrough" {
   233  		t.Error("wrong identifier for ec6")
   234  	}
   235  }
   236  
   237  // testDefaultConstructors verifies the default constructor create erasure codes
   238  // with the correct parameters
   239  func testDefaultConstructors(t *testing.T) {
   240  	rs, err := NewRSCode(RenterDefaultDataPieces, RenterDefaultParityPieces)
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	rsd := NewRSCodeDefault()
   245  	if rs.Identifier() != rsd.Identifier() {
   246  		t.Fatal("Unexpected parameters used in default")
   247  	}
   248  
   249  	rss, err := NewRSSubCode(RenterDefaultDataPieces, RenterDefaultParityPieces, crypto.SegmentSize)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	rssd := NewRSSubCodeDefault()
   254  	if rss.Identifier() != rssd.Identifier() {
   255  		t.Fatal("Unexpected parameters used in default")
   256  	}
   257  }
   258  
   259  // BenchmarkRSEncode benchmarks the 'Encode' function of the RSCode EC.
   260  func BenchmarkRSEncode(b *testing.B) {
   261  	rsc, err := NewRSCode(80, 20)
   262  	if err != nil {
   263  		b.Fatal(err)
   264  	}
   265  	data := fastrand.Bytes(1 << 20)
   266  
   267  	b.SetBytes(1 << 20)
   268  	b.ResetTimer()
   269  	for i := 0; i < b.N; i++ {
   270  		rsc.Encode(data)
   271  	}
   272  }
   273  
   274  // BenchmarkRSEncode benchmarks the 'Recover' function of the RSCode EC.
   275  func BenchmarkRSRecover(b *testing.B) {
   276  	rsc, err := NewRSCode(50, 200)
   277  	if err != nil {
   278  		b.Fatal(err)
   279  	}
   280  	data := fastrand.Bytes(1 << 20)
   281  	pieces, err := rsc.Encode(data)
   282  	if err != nil {
   283  		b.Fatal(err)
   284  	}
   285  
   286  	b.SetBytes(1 << 20)
   287  	b.ResetTimer()
   288  	for i := 0; i < b.N; i++ {
   289  		for j := 0; j < len(pieces)/2; j += 2 {
   290  			pieces[j] = nil
   291  		}
   292  		rsc.Recover(pieces, 1<<20, ioutil.Discard)
   293  	}
   294  }
   295  
   296  // BenchmarkRSSubCodeRecover benchmarks the 'Recover' function of the RSSubCode
   297  // EC.
   298  func BenchmarkRSSubCodeRecover(b *testing.B) {
   299  	segmentSize := crypto.SegmentSize
   300  	pieceSize := 4096
   301  	dataPieces := 10
   302  	parityPieces := 30
   303  	data := fastrand.Bytes(pieceSize * dataPieces)
   304  	originalData := make([]byte, len(data))
   305  	copy(originalData, data)
   306  	// Create the erasure coder.
   307  	rsc, err := NewRSSubCode(dataPieces, parityPieces, uint64(segmentSize))
   308  	if err != nil {
   309  		b.Fatal(err)
   310  	}
   311  	// Allocate space for the pieces.
   312  	pieces := make([][]byte, dataPieces)
   313  	for i := range pieces {
   314  		pieces[i] = make([]byte, pieceSize)
   315  	}
   316  	// Write the data to the pieces.
   317  	buf := bytes.NewBuffer(data)
   318  	for i := range pieces {
   319  		if buf.Len() < pieceSize {
   320  			b.Fatal("Buffer is empty")
   321  		}
   322  		pieces[i] = make([]byte, pieceSize)
   323  		copy(pieces[i], buf.Next(pieceSize))
   324  	}
   325  	// Encode the pieces.
   326  	encodedPieces, err := rsc.EncodeShards(pieces)
   327  	if err != nil {
   328  		b.Fatal(err)
   329  	}
   330  	// Check that the parity shards have been created.
   331  	if len(encodedPieces) != rsc.NumPieces() {
   332  		b.Fatalf("encodedPieces should've length %v but was %v", rsc.NumPieces(), len(encodedPieces))
   333  	}
   334  	// Every piece should have pieceSize.
   335  	for _, piece := range encodedPieces {
   336  		if len(piece) != pieceSize {
   337  			b.Fatalf("expecte len(piece) to be %v but was %v", pieceSize, len(piece))
   338  		}
   339  	}
   340  	// Delete all data shards
   341  	for i := range encodedPieces[:dataPieces+1] {
   342  		encodedPieces[i] = nil
   343  	}
   344  
   345  	b.ReportAllocs()
   346  	b.ResetTimer()
   347  	b.SetBytes(int64(len(data)))
   348  	for i := 0; i < b.N; i++ {
   349  		buf.Reset()
   350  		err = rsc.Recover(encodedPieces, uint64(len(data)), buf)
   351  		if err != nil {
   352  			b.Fatal(err)
   353  		}
   354  		if !bytes.Equal(buf.Bytes(), originalData) {
   355  			b.Fatal("decoded bytes don't equal original data")
   356  		}
   357  	}
   358  }