bindings_test.go 14.2 KB
Newer Older
1
2
3
4
5
package go_sectorbuilder_test

import (
	"bytes"
	"crypto/rand"
6
	"encoding/hex"
7
	"errors"
8
	"fmt"
9
	"github.com/stretchr/testify/assert"
10
11
	"io"
	"io/ioutil"
12
	"math/big"
13
	"os"
14
	"path/filepath"
15
16
17
18
19
	"testing"
	"time"
	"unsafe"

	sb "github.com/filecoin-project/go-sectorbuilder"
20
	"github.com/filecoin-project/go-sectorbuilder/sealed_sector_health"
21
	"github.com/filecoin-project/go-sectorbuilder/sealing_state"
22
23
24
25
26

	"github.com/stretchr/testify/require"
)

func TestSectorBuilderLifecycle(t *testing.T) {
27
28
	ticketA := sb.SealTicket{
		BlockHeight: 0,
29
		TicketBytes: [32]byte{5, 4, 2},
30
31
32
33
34
35
36
	}

	ticketB := sb.SealTicket{
		BlockHeight: 10,
		TicketBytes: [32]byte{1, 2, 3},
	}

37
38
39
40
41
42
43
44
45
46
	seedA := sb.SealSeed{
		BlockHeight: 50,
		TicketBytes: [32]byte{7, 4, 2},
	}

	seedB := sb.SealSeed{
		BlockHeight: 60,
		TicketBytes: [32]byte{9, 10, 11},
	}

47
48
	proverID := [32]byte{6, 7, 8}

49
	metadataDir := requireTempDirPath(t)
50
	defer os.RemoveAll(metadataDir)
51
52

	sealedSectorDir := requireTempDirPath(t)
53
	defer os.RemoveAll(sealedSectorDir)
54
55

	stagedSectorDir := requireTempDirPath(t)
56
	defer os.RemoveAll(stagedSectorDir)
57

58
	sectorCacheRootDir := requireTempDirPath(t)
59
	defer os.RemoveAll(sectorCacheRootDir)
60

61
	ptr, err := sb.InitSectorBuilder(1024, 2, 0, metadataDir, proverID, sealedSectorDir, stagedSectorDir, sectorCacheRootDir, 1, 2)
62
63
64
	require.NoError(t, err)
	defer sb.DestroySectorBuilder(ptr)

65
66
67
68
69
	// verify that we've not yet sealed a sector
	sealedSectors, err := sb.GetAllSealedSectorsWithHealth(ptr)
	require.NoError(t, err)
	require.Equal(t, 0, len(sealedSectors), "expected to see zero sealed sectors")

70
71
72
73
74
75
76
	// compute the max user-bytes that can fit into a staged sector such that
	// bit-padding ("preprocessing") expands the file to $SECTOR_SIZE
	maxPieceSize := sb.GetMaxUserBytesPerStagedSector(1024)

	// create a piece which consumes all available space in a new, staged
	// sector
	pieceBytes := make([]byte, maxPieceSize)
77
78
79
	read, err := io.ReadFull(rand.Reader, pieceBytes)
	require.Equal(t, uint64(read), maxPieceSize)

80
	require.NoError(t, err)
81
82
83
84
	pieceFileA := requireTempFile(t, bytes.NewReader(pieceBytes), maxPieceSize)

	require.NoError(t, err)
	pieceFileB := requireTempFile(t, bytes.NewReader(pieceBytes), maxPieceSize)
85
86

	// generate piece commitment
87
	commP, err := sb.GeneratePieceCommitmentFromFile(pieceFileA, maxPieceSize)
88
89
	require.NoError(t, err)

90
91
92
93
94
	publicPieceInfoA := []sb.PublicPieceInfo{{
		Size:  maxPieceSize,
		CommP: commP,
	}}

95
96
97
	preComputedCommD, err := sb.GenerateDataCommitment(1024, publicPieceInfoA)
	require.NoError(t, err)

98
	// seek to the beginning
99
	_, err = pieceFileA.Seek(0, 0)
100
101
	require.NoError(t, err)

102
103
	// write a piece to a staged sector, reducing remaining space to 0
	sectorIDA, err := sb.AddPieceFromFile(ptr, "snoqualmie", maxPieceSize, pieceFileA)
104
105
	require.NoError(t, err)

Sidney Keese's avatar
Sidney Keese committed
106
	stagedSectors, err := sb.GetAllStagedSectors(ptr)
107
	require.NoError(t, err)
Sidney Keese's avatar
Sidney Keese committed
108
109
110
111
	require.Equal(t, 1, len(stagedSectors))
	stagedSector := stagedSectors[0]
	require.Equal(t, uint64(1), stagedSector.SectorID)

112
	// block until the sector is ready for us to begin sealing
113
	statusA, err := pollForSectorSealingStatus(ptr, sectorIDA, sealing_state.FullyPacked, time.Minute)
114
115
	require.NoError(t, err)

116
	// pre-commit sector to a ticket (in a non-blocking fashion)
117
	go func() {
118
		out, err := sb.SealPreCommit(ptr, statusA.SectorID, ticketA)
119
		require.NoError(t, err)
120
121
		require.Equal(t, sectorIDA, out.SectorID)
		require.Equal(t, ticketA.TicketBytes, out.Ticket.TicketBytes)
122
		require.True(t, bytes.Equal(preComputedCommD[:], out.CommD[:]))
123
124
125
126
127
128
	}()

	// write a second piece to a staged sector, reducing remaining space to 0
	sectorIDB, err := sb.AddPieceFromFile(ptr, "duvall", maxPieceSize, pieceFileB)
	require.NoError(t, err)

129
	// pre-commit second sector to a ticket too
130
	go func() {
131
		_, err := sb.SealPreCommit(ptr, sectorIDB, ticketB)
132
133
134
		require.NoError(t, err)
	}()

135
136
	// block until both sectors have successfully pre-committed
	statusA, err = pollForSectorSealingStatus(ptr, sectorIDA, sealing_state.PreCommitted, 30*time.Minute)
137
138
	require.NoError(t, err)

139
	statusB, err := pollForSectorSealingStatus(ptr, sectorIDB, sealing_state.PreCommitted, 30*time.Minute)
140
	require.NoError(t, err)
141

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
	// commit both sectors concurrently
	go func() {
		out, err := sb.SealCommit(ptr, sectorIDA, seedA)
		require.NoError(t, err)
		require.Equal(t, sectorIDA, out.SectorID)
		require.Equal(t, ticketA.TicketBytes, out.Ticket.TicketBytes)
		require.Equal(t, seedA.TicketBytes, out.Seed.TicketBytes)
	}()

	go func() {
		out, err := sb.SealCommit(ptr, sectorIDB, seedB)
		require.NoError(t, err)
		require.Equal(t, sectorIDB, out.SectorID)
	}()

	// block until both sectors have finished sealing (successfully)
	statusA, err = pollForSectorSealingStatus(ptr, sectorIDA, sealing_state.Committed, 30*time.Minute)
159
160
	require.NoError(t, err)

161
162
163
164
165
166
167
168
169
170
	statusB, err = pollForSectorSealingStatus(ptr, sectorIDB, sealing_state.Committed, 30*time.Minute)
	require.NoError(t, err)

	// verify that we used the tickets and seeds we'd intended to use
	require.Equal(t, ticketA.TicketBytes, statusA.Ticket.TicketBytes)
	require.Equal(t, ticketB.TicketBytes, statusB.Ticket.TicketBytes)
	require.Equal(t, seedA.TicketBytes, statusA.Seed.TicketBytes)
	require.Equal(t, seedB.TicketBytes, statusB.Seed.TicketBytes)

	// verify the seal proof
171
	isValid, err := sb.VerifySeal(1024, statusA.CommR, statusA.CommD, proverID, ticketA.TicketBytes, seedA.TicketBytes, sectorIDA, statusA.Proof)
172
173
174
	require.NoError(t, err)
	require.True(t, isValid)

175
176
	// enforces sort ordering of SectorInfo tuples
	sectorInfo := sb.NewSortedSectorInfo(sb.SectorInfo{
177
178
		SectorID: statusA.SectorID,
		CommR:    statusA.CommR,
179
180
	})

181
	// generate a PoSt
182
	proofs, err := sb.GeneratePoSt(ptr, sectorInfo, [32]byte{}, []sb.Candidate{})
183
184
185
	require.NoError(t, err)

	// verify the PoSt
186
	isValid, err = sb.VerifyPoSt(1024, sectorInfo, [32]byte{}, proofs, []sb.Candidate{}, proverID)
187
	require.NoError(t, err)
188
189
	require.True(t, isValid)

190
	sealedSectors, err = sb.GetAllSealedSectorsWithHealth(ptr)
191
	require.NoError(t, err)
192
193
194
195
196
197
198
	require.Equal(t, 2, len(sealedSectors), "expected to see two sealed sectors")
	for _, sealedSector := range sealedSectors {
		require.Equal(t, sealed_sector_health.Ok, sealedSector.Health)
	}

	// both sealed sectors contain the same data, so either will suffice
	require.Equal(t, commP, sealedSectors[0].CommD)
Sidney Keese's avatar
Sidney Keese committed
199

200
201
202
	// unseal the sector and retrieve the client's piece, verifying that the
	// retrieved bytes match what we originally wrote to the staged sector
	unsealedPieceBytes, err := sb.ReadPieceFromSealedSector(ptr, "snoqualmie")
203
	require.NoError(t, err)
204
205
206
	require.Equal(t, pieceBytes, unsealedPieceBytes)
}

207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
func TestStandaloneSealing(t *testing.T) {
	sectorSize := uint64(1024)
	poRepProofPartitions := uint8(2)

	ticket := sb.SealTicket{
		BlockHeight: 0,
		TicketBytes: [32]byte{5, 4, 2},
	}

	seed := sb.SealSeed{
		BlockHeight: 50,
		TicketBytes: [32]byte{7, 4, 2},
	}

	proverID := [32]byte{6, 7, 8}

	// initialize a sector builder
	metadataDir := requireTempDirPath(t)
	defer os.RemoveAll(metadataDir)

	sealedSectorsDir := requireTempDirPath(t)
	defer os.RemoveAll(sealedSectorsDir)

	stagedSectorsDir := requireTempDirPath(t)
	defer os.RemoveAll(stagedSectorsDir)

	sectorCacheRootDir := requireTempDirPath(t)
	defer os.RemoveAll(sectorCacheRootDir)

	ptr, err := sb.InitSectorBuilder(1024, 2, 0, metadataDir, proverID, sealedSectorsDir, stagedSectorsDir, sectorCacheRootDir, 1, 1)
	require.NoError(t, err)
	defer sb.DestroySectorBuilder(ptr)

	sectorID, err := sb.AcquireSectorId(ptr)
	require.NoError(t, err)

	sectorCacheDirPath := requireTempDirPath(t)
	defer os.RemoveAll(sectorCacheDirPath)

	stagedSectorFile := requireTempFile(t, bytes.NewReader([]byte{}), 0)
	defer stagedSectorFile.Close()

	sealedSectorFile := requireTempFile(t, bytes.NewReader([]byte{}), 0)
	defer sealedSectorFile.Close()

	unsealOutputFile := requireTempFile(t, bytes.NewReader([]byte{}), 0)
	defer unsealOutputFile.Close()

	// some rando bytes
	someBytes := make([]byte, 1016)
	_, err = io.ReadFull(rand.Reader, someBytes)
	require.NoError(t, err)

	// write first piece
	require.NoError(t, err)
	pieceFileA := requireTempFile(t, bytes.NewReader(someBytes[0:127]), 127)

	commPA, err := sb.GeneratePieceCommitmentFromFile(pieceFileA, 127)
	require.NoError(t, err)

	// seek back to head (generating piece commitment moves offset)
	_, err = pieceFileA.Seek(0, 0)
	require.NoError(t, err)

	// write the first piece using the alignment-free function
	n, commP, err := sb.StandaloneWriteWithoutAlignment(pieceFileA, 127, stagedSectorFile)
	require.NoError(t, err)
	require.Equal(t, int(n), 127)
	require.Equal(t, commP, commPA)

	// write second piece + alignment
	require.NoError(t, err)
	pieceFileB := requireTempFile(t, bytes.NewReader(someBytes[0:508]), 508)

	commPB, err := sb.GeneratePieceCommitmentFromFile(pieceFileB, 508)
	require.NoError(t, err)

	// seek back to head
	_, err = pieceFileB.Seek(0, 0)
	require.NoError(t, err)

	// second piece relies on the alignment-computing version
	left, tot, commP, err := sb.StandaloneWriteWithAlignment(pieceFileB, 508, stagedSectorFile, []uint64{127})
	require.NoError(t, err)
	require.Equal(t, int(left), 381)
	require.Equal(t, int(tot), 889)
	require.Equal(t, commP, commPB)

	publicPieces := []sb.PublicPieceInfo{{
		Size:  127,
		CommP: commPA,
	}, {
		Size:  508,
		CommP: commPB,
	}}

	privatePieces := make([]sb.PieceMetadata, len(publicPieces))
	for i, v := range publicPieces {
		privatePieces[i] = sb.PieceMetadata{
			Key:   hex.EncodeToString(v.CommP[:]),
			Size:  v.Size,
			CommP: v.CommP,
		}
	}

	// pre-commit the sector
	output, err := sb.StandaloneSealPreCommit(sectorSize, poRepProofPartitions, sectorCacheDirPath, stagedSectorFile.Name(), sealedSectorFile.Name(), sectorID, proverID, ticket.TicketBytes, publicPieces)
	require.NoError(t, err)

	// commit the sector
	proof, err := sb.StandaloneSealCommit(sectorSize, poRepProofPartitions, sectorCacheDirPath, sectorID, proverID, ticket.TicketBytes, seed.TicketBytes, publicPieces, output)
	require.NoError(t, err)

	// verify the 'ole proofy
	isValid, err := sb.VerifySeal(sectorSize, output.CommR, output.CommD, proverID, ticket.TicketBytes, seed.TicketBytes, sectorID, proof)
	require.NoError(t, err)
	require.True(t, isValid, "proof wasn't valid")

	// unseal and verify that things went as we planned
	require.NoError(t, sb.StandaloneUnseal(sectorSize, poRepProofPartitions, sealedSectorFile.Name(), unsealOutputFile.Name(), sectorID, proverID, ticket.TicketBytes, output.CommD))
	contents, err := ioutil.ReadFile(unsealOutputFile.Name())
	require.NoError(t, err)

	// unsealed sector includes a bunch of alignment NUL-bytes
	alignment := make([]byte, 381)

	// verify that we unsealed what we expected to unseal
	require.Equal(t, someBytes[0:127], contents[0:127])
	require.Equal(t, alignment, contents[127:508])
	require.Equal(t, someBytes[0:508], contents[508:1016])

	// verify that the sector builder owns no sealed sectors
	var sealedSectorPaths []string
	require.NoError(t, filepath.Walk(sealedSectorsDir, visit(&sealedSectorPaths)))
	assert.Equal(t, 1, len(sealedSectorPaths), sealedSectorPaths)

	// no sector cache dirs, either
	var sectorCacheDirPaths []string
	require.NoError(t, filepath.Walk(sectorCacheRootDir, visit(&sectorCacheDirPaths)))
	assert.Equal(t, 1, len(sectorCacheDirPaths), sectorCacheDirPaths)

	// import the sealed sector
	err = sb.ImportSealedSector(ptr, sectorID, sectorCacheDirPath, sealedSectorFile.Name(), ticket, seed, output.CommR, output.CommD, output.CommC, output.CommRLast, proof, privatePieces)
	require.NoError(t, err)

	// it should now have a sealed sector!
	var sealedSectorPathsB []string
	require.NoError(t, filepath.Walk(sealedSectorsDir, visit(&sealedSectorPathsB)))
	assert.Equal(t, 2, len(sealedSectorPathsB), sealedSectorPathsB)

	// it should now have a cache dir, woo!
	var sectorCacheDirPathsB []string
	require.NoError(t, filepath.Walk(sectorCacheRootDir, visit(&sectorCacheDirPathsB)))
	assert.Equal(t, 2, len(sectorCacheDirPathsB), sectorCacheDirPathsB)

	// verify that it shows up in sealed sector list
	metadata, err := sb.GetAllSealedSectorsWithHealth(ptr)
	require.NoError(t, err)
	require.Equal(t, 1, len(metadata))
	require.Equal(t, output.CommD, metadata[0].CommD)
	require.Equal(t, output.CommR, metadata[0].CommR)
}

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
func TestJsonMarshalSymmetry(t *testing.T) {
	for i := 0; i < 100; i++ {
		xs := make([]sb.SectorInfo, 10)
		for j := 0; j < 10; j++ {
			var x sb.SectorInfo
			_, err := io.ReadFull(rand.Reader, x.CommR[:])
			require.NoError(t, err)

			n, err := rand.Int(rand.Reader, big.NewInt(500))
			require.NoError(t, err)
			x.SectorID = n.Uint64()
			xs[j] = x
		}
		toSerialize := sb.NewSortedSectorInfo(xs...)

		serialized, err := toSerialize.MarshalJSON()
		require.NoError(t, err)

		var fromSerialized sb.SortedSectorInfo
		err = fromSerialized.UnmarshalJSON(serialized)
		require.NoError(t, err)

		require.Equal(t, toSerialize, fromSerialized)
	}
}

396
func pollForSectorSealingStatus(ptr unsafe.Pointer, sectorID uint64, targetState sealing_state.State, timeout time.Duration) (status sb.SectorSealingStatus, retErr error) {
397
	timeoutCh := time.After(timeout)
398
	lastState := sealing_state.Unknown
399

400
	tick := time.Tick(1 * time.Second)
401
402
403
404

	for {
		select {
		case <-timeoutCh:
405
			retErr = fmt.Errorf("timed out waiting for sector hit desired state (last state: %s)", lastState)
406
407
408
409
410
411
412
413
			return
		case <-tick:
			sealingStatus, err := sb.GetSectorSealingStatusByID(ptr, sectorID)
			if err != nil {
				retErr = err
				return
			}

414
415
			lastState = sealingStatus.State

416
			if sealingStatus.State == targetState {
417
418
				status = sealingStatus
				return
419
420
421
			} else if sealingStatus.State == sealing_state.Failed {
				retErr = errors.New(sealingStatus.SealErrorMsg)
				return
422
423
424
425
426
			}
		}
	}
}

427
func requireTempFile(t *testing.T, fileContentsReader io.Reader, size uint64) *os.File {
428
429
430
	file, err := ioutil.TempFile("", "")
	require.NoError(t, err)

431
432
433
	written, err := io.Copy(file, fileContentsReader)
	require.NoError(t, err)
	// check that we wrote everything
434
	require.Equal(t, int(size), int(written))
435
436
437
438
439

	require.NoError(t, file.Sync())

	// seek to the beginning
	_, err = file.Seek(0, 0)
440
441
	require.NoError(t, err)

442
	return file
443
444
445
446
447
448
449
450
}

func requireTempDirPath(t *testing.T) string {
	dir, err := ioutil.TempDir("", "")
	require.NoError(t, err)

	return dir
}
451
452
453
454
455
456
457
458
459
460

func visit(paths *[]string) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if err != nil {
			panic(err)
		}
		*paths = append(*paths, path)
		return nil
	}
}