bindings_test.go 14.3 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
182
183
	candidates, err := sb.GenerateCandidates(ptr, sectorInfo, [32]byte{}, []uint64{})
	require.NoError(t, err)

184
	// generate a PoSt
185
	proofs, err := sb.GeneratePoSt(ptr, sectorInfo, [32]byte{}, candidates)
186
187
188
	require.NoError(t, err)

	// verify the PoSt
189
	isValid, err = sb.VerifyPoSt(1024, sectorInfo, [32]byte{}, proofs, candidates, proverID)
190
	require.NoError(t, err)
191
192
	require.True(t, isValid)

193
	sealedSectors, err = sb.GetAllSealedSectorsWithHealth(ptr)
194
	require.NoError(t, err)
195
196
197
198
199
200
201
	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
202

203
204
205
	// 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")
206
	require.NoError(t, err)
207
208
209
	require.Equal(t, pieceBytes, unsealedPieceBytes)
}

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
370
371
372
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)
}

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
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)
	}
}

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

403
	tick := time.Tick(1 * time.Second)
404
405
406
407

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

417
418
			lastState = sealingStatus.State

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

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

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

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

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

445
	return file
446
447
448
449
450
451
452
453
}

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

	return dir
}
454
455
456
457
458
459
460
461
462
463

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
	}
}