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 }