bindings.go 17.7 KB
Newer Older
1
package go_sectorbuilder
2
3

import (
4
5
	"bytes"
	"sort"
6
7
8
	"time"
	"unsafe"

9
	"github.com/filecoin-project/go-sectorbuilder/sealed_sector_health"
10
	"github.com/filecoin-project/go-sectorbuilder/sealing_state"
11

12
13
14
15
	logging "github.com/ipfs/go-log"
	"github.com/pkg/errors"
)

16
17
18
// #cgo LDFLAGS: ${SRCDIR}/libsector_builder_ffi.a
// #cgo pkg-config: ${SRCDIR}/sector_builder_ffi.pc
// #include "./sector_builder_ffi.h"
19
20
21
22
23
24
25
26
27
28
29
import "C"

var log = logging.Logger("libsectorbuilder") // nolint: deadcode

func elapsed(what string) func() {
	start := time.Now()
	return func() {
		log.Debugf("%s took %v\n", what, time.Since(start))
	}
}

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// SortedSectorInfo is a slice of SectorInfo sorted (lexicographically,
// ascending) by replica commitment (CommR).
type SortedSectorInfo struct {
	f []SectorInfo
}

// NewSortedSectorInfo returns a SortedSectorInfo
func NewSortedSectorInfo(sectorInfo ...SectorInfo) SortedSectorInfo {
	fn := func(i, j int) bool {
		return bytes.Compare(sectorInfo[i].CommR[:], sectorInfo[j].CommR[:]) == -1
	}

	sort.Slice(sectorInfo[:], fn)

	return SortedSectorInfo{
		f: sectorInfo,
	}
}

// Values returns the sorted SectorInfo as a slice
func (s *SortedSectorInfo) Values() []SectorInfo {
	return s.f
}

type SectorInfo struct {
	SectorID uint64
56
	CommR    [CommitmentBytesLen]byte
57
58
}

59
60
61
// CommitmentBytesLen is the number of bytes in a CommR, CommD, CommP, and CommRStar.
const CommitmentBytesLen = 32

62
63
64
65
66
67
68
// StagedSectorMetadata is a sector into which we write user piece-data before
// sealing. Note: SectorID is unique across all staged and sealed sectors for a
// storage miner actor.
type StagedSectorMetadata struct {
	SectorID uint64
}

Sidney Keese's avatar
Sidney Keese committed
69
70
71
72
73
74
75
76
// SealedSectorMetadata represents a sector in the builder that has been sealed.
type SealedSectorMetadata struct {
	SectorID  uint64
	CommD     [CommitmentBytesLen]byte
	CommR     [CommitmentBytesLen]byte
	CommRStar [CommitmentBytesLen]byte
	Proof     []byte
	Pieces    []PieceMetadata
77
	Health    sealed_sector_health.Health
Sidney Keese's avatar
Sidney Keese committed
78
79
}

80
81
82
// SectorSealingStatus communicates how far along in the sealing process a
// sector has progressed.
type SectorSealingStatus struct {
83
84
85
86
87
88
89
90
	SectorID     uint64
	State        sealing_state.State
	SealErrorMsg string                   // will be nil unless State == Failed
	CommD        [CommitmentBytesLen]byte // will be empty unless State == Sealed
	CommR        [CommitmentBytesLen]byte // will be empty unless State == Sealed
	CommRStar    [CommitmentBytesLen]byte // will be empty unless State == Sealed
	Proof        []byte                   // will be empty unless State == Sealed
	Pieces       []PieceMetadata          // will be empty unless State == Sealed
91
92
93
94
95
96
97
}

// PieceMetadata represents a piece stored by the sector builder.
type PieceMetadata struct {
	Key            string
	Size           uint64
	InclusionProof []byte
98
	CommP          [CommitmentBytesLen]byte
99
100
101
102
103
104
}

// VerifySeal returns true if the sealing operation from which its inputs were
// derived was valid, and false if not.
func VerifySeal(
	sectorSize uint64,
105
106
107
	commR [CommitmentBytesLen]byte,
	commD [CommitmentBytesLen]byte,
	commRStar [CommitmentBytesLen]byte,
108
	proverID [31]byte,
laser's avatar
laser committed
109
	sectorID uint64,
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
	proof []byte,
) (bool, error) {
	defer elapsed("VerifySeal")()

	commDCBytes := C.CBytes(commD[:])
	defer C.free(commDCBytes)

	commRCBytes := C.CBytes(commR[:])
	defer C.free(commRCBytes)

	commRStarCBytes := C.CBytes(commRStar[:])
	defer C.free(commRStarCBytes)

	proofCBytes := C.CBytes(proof[:])
	defer C.free(proofCBytes)

	proverIDCBytes := C.CBytes(proverID[:])
	defer C.free(proverIDCBytes)

	// a mutable pointer to a VerifySealResponse C-struct
130
	resPtr := C.sector_builder_ffi_verify_seal(
131
		C.uint64_t(sectorSize),
132
133
134
		(*[CommitmentBytesLen]C.uint8_t)(commRCBytes),
		(*[CommitmentBytesLen]C.uint8_t)(commDCBytes),
		(*[CommitmentBytesLen]C.uint8_t)(commRStarCBytes),
135
		(*[31]C.uint8_t)(proverIDCBytes),
laser's avatar
laser committed
136
		C.uint64_t(sectorID),
137
138
		(*C.uint8_t)(proofCBytes),
		C.size_t(len(proof)),
139
	)
140
141
142
143
144
145
146
147
148
149
150
151
152
	defer C.sector_builder_ffi_destroy_verify_seal_response(resPtr)

	if resPtr.status_code != 0 {
		return false, errors.New(C.GoString(resPtr.error_msg))
	}

	return bool(resPtr.is_valid), nil
}

// VerifyPoSt returns true if the PoSt-generation operation from which its
// inputs were derived was valid, and false if not.
func VerifyPoSt(
	sectorSize uint64,
153
	sectorInfo SortedSectorInfo,
laser's avatar
laser committed
154
155
	challengeSeed [32]byte,
	proof []byte,
156
157
158
159
	faults []uint64,
) (bool, error) {
	defer elapsed("VerifyPoSt")()

160
161
162
163
164
165
166
167
	// CommRs and sector ids must be provided to C.verify_post in the same order
	// that they were provided to the C.generate_post
	sortedCommRs := make([][CommitmentBytesLen]byte, len(sectorInfo.Values()))
	sortedSectorIds := make([]uint64, len(sectorInfo.Values()))
	for idx, v := range sectorInfo.Values() {
		sortedCommRs[idx] = v.CommR
		sortedSectorIds[idx] = v.SectorID
	}
168
169

	// flattening the byte slice makes it easier to copy into the C heap
170
171
	flattened := make([]byte, CommitmentBytesLen*len(sortedCommRs))
	for idx, commR := range sortedCommRs {
172
		copy(flattened[(CommitmentBytesLen*idx):(CommitmentBytesLen*(1+idx))], commR[:])
173
174
175
176
177
178
179
180
181
	}

	// copy bytes from Go to C heap
	flattenedCommRsCBytes := C.CBytes(flattened)
	defer C.free(flattenedCommRsCBytes)

	challengeSeedCBytes := C.CBytes(challengeSeed[:])
	defer C.free(challengeSeedCBytes)

laser's avatar
laser committed
182
183
	proofCBytes := C.CBytes(proof)
	defer C.free(proofCBytes)
184
185

	// allocate fixed-length array of uint64s in C heap
laser's avatar
laser committed
186
187
188
	sectorIdsPtr, sectorIdsSize := cUint64s(sortedSectorIds)
	defer C.free(unsafe.Pointer(sectorIdsPtr))

189
190
191
192
	faultsPtr, faultsSize := cUint64s(faults)
	defer C.free(unsafe.Pointer(faultsPtr))

	// a mutable pointer to a VerifyPoStResponse C-struct
193
	resPtr := C.sector_builder_ffi_verify_post(
194
		C.uint64_t(sectorSize),
195
		(*[CommitmentBytesLen]C.uint8_t)(challengeSeedCBytes),
laser's avatar
laser committed
196
197
		sectorIdsPtr,
		sectorIdsSize,
198
199
		faultsPtr,
		faultsSize,
laser's avatar
laser committed
200
201
202
203
		(*C.uint8_t)(flattenedCommRsCBytes),
		C.size_t(len(flattened)),
		(*C.uint8_t)(proofCBytes),
		C.size_t(len(proof)),
204
	)
205
206
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
	defer C.sector_builder_ffi_destroy_verify_post_response(resPtr)

	if resPtr.status_code != 0 {
		return false, errors.New(C.GoString(resPtr.error_msg))
	}

	return bool(resPtr.is_valid), nil
}

// GetMaxUserBytesPerStagedSector returns the number of user bytes that will fit
// into a staged sector. Due to bit-padding, the number of user bytes that will
// fit into the staged sector will be less than number of bytes in sectorSize.
func GetMaxUserBytesPerStagedSector(sectorSize uint64) uint64 {
	defer elapsed("GetMaxUserBytesPerStagedSector")()

	return uint64(C.sector_builder_ffi_get_max_user_bytes_per_staged_sector(C.uint64_t(sectorSize)))
}

// InitSectorBuilder allocates and returns a pointer to a sector builder.
func InitSectorBuilder(
	sectorSize uint64,
	poRepProofPartitions uint8,
	poStProofPartitions uint8,
	lastUsedSectorID uint64,
	metadataDir string,
	proverID [31]byte,
	sealedSectorDir string,
	stagedSectorDir string,
	maxNumOpenStagedSectors uint8,
) (unsafe.Pointer, error) {
	defer elapsed("InitSectorBuilder")()

	cMetadataDir := C.CString(metadataDir)
	defer C.free(unsafe.Pointer(cMetadataDir))

	proverIDCBytes := C.CBytes(proverID[:])
	defer C.free(proverIDCBytes)

	cStagedSectorDir := C.CString(stagedSectorDir)
	defer C.free(unsafe.Pointer(cStagedSectorDir))

	cSealedSectorDir := C.CString(sealedSectorDir)
	defer C.free(unsafe.Pointer(cSealedSectorDir))

laser's avatar
laser committed
249
	class, err := cSectorClass(sectorSize, poRepProofPartitions)
250
251
252
253
	if err != nil {
		return nil, errors.Wrap(err, "failed to get sector class")
	}

254
	resPtr := C.sector_builder_ffi_init_sector_builder(
255
256
257
258
259
260
261
		class,
		C.uint64_t(lastUsedSectorID),
		cMetadataDir,
		(*[31]C.uint8_t)(proverIDCBytes),
		cSealedSectorDir,
		cStagedSectorDir,
		C.uint8_t(maxNumOpenStagedSectors),
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
	defer C.sector_builder_ffi_destroy_init_sector_builder_response(resPtr)

	if resPtr.status_code != 0 {
		return nil, errors.New(C.GoString(resPtr.error_msg))
	}

	return unsafe.Pointer(resPtr.sector_builder), nil
}

// DestroySectorBuilder deallocates the sector builder associated with the
// provided pointer. This function will panic if the provided pointer is null
// or if the sector builder has been previously deallocated.
func DestroySectorBuilder(sectorBuilderPtr unsafe.Pointer) {
	defer elapsed("DestroySectorBuilder")()

	C.sector_builder_ffi_destroy_sector_builder((*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr))
}

// AddPiece writes the given piece into an unsealed sector and returns the id
// of that sector.
func AddPiece(
	sectorBuilderPtr unsafe.Pointer,
	pieceKey string,
	pieceSize uint64,
	piecePath string,
) (sectorID uint64, retErr error) {
	defer elapsed("AddPiece")()

	cPieceKey := C.CString(pieceKey)
	defer C.free(unsafe.Pointer(cPieceKey))

	cPiecePath := C.CString(piecePath)
	defer C.free(unsafe.Pointer(cPiecePath))

297
298
299
300
301
302
303
304
305
306
	// TODO: The UTC time, in seconds, at which the sector builder can safely
	// delete the piece. This allows for co-location of pieces with similar time
	// constraints, and allows the sector builder to remove sectors containing
	// pieces whose deals have expired.
	//
	// This value is currently ignored by the sector builder.
	//
	// https://github.com/filecoin-project/rust-fil-sector-builder/issues/32
	pieceExpiryUtcSeconds := 0

307
	resPtr := C.sector_builder_ffi_add_piece(
308
309
310
311
		(*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr),
		cPieceKey,
		C.uint64_t(pieceSize),
		cPiecePath,
312
		C.uint64_t(pieceExpiryUtcSeconds),
313
	)
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
	defer C.sector_builder_ffi_destroy_add_piece_response(resPtr)

	if resPtr.status_code != 0 {
		return 0, errors.New(C.GoString(resPtr.error_msg))
	}

	return uint64(resPtr.sector_id), nil
}

// ReadPieceFromSealedSector produces a byte buffer containing the piece
// associated with the provided key. If the key is not associated with any piece
// yet sealed into a sector, an error will be returned.
func ReadPieceFromSealedSector(sectorBuilderPtr unsafe.Pointer, pieceKey string) ([]byte, error) {
	defer elapsed("ReadPieceFromSealedSector")()

	cPieceKey := C.CString(pieceKey)
	defer C.free(unsafe.Pointer(cPieceKey))

332
333
334
335
	resPtr := C.sector_builder_ffi_read_piece_from_sealed_sector(
		(*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr),
		cPieceKey,
	)
336
337
338
339
340
341
342
343
344
345
346
347
348
	defer C.sector_builder_ffi_destroy_read_piece_from_sealed_sector_response(resPtr)

	if resPtr.status_code != 0 {
		return nil, errors.New(C.GoString(resPtr.error_msg))
	}

	return goBytes(resPtr.data_ptr, resPtr.data_len), nil
}

// SealAllStagedSectors schedules sealing of all staged sectors.
func SealAllStagedSectors(sectorBuilderPtr unsafe.Pointer) error {
	defer elapsed("SealAllStagedSectors")()

349
	resPtr := C.sector_builder_ffi_seal_all_staged_sectors((*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr))
350
351
352
353
354
355
356
357
358
359
360
361
362
	defer C.sector_builder_ffi_destroy_seal_all_staged_sectors_response(resPtr)

	if resPtr.status_code != 0 {
		return errors.New(C.GoString(resPtr.error_msg))
	}

	return nil
}

// GetAllStagedSectors returns a slice of all staged sector metadata for the sector builder.
func GetAllStagedSectors(sectorBuilderPtr unsafe.Pointer) ([]StagedSectorMetadata, error) {
	defer elapsed("GetAllStagedSectors")()

363
	resPtr := C.sector_builder_ffi_get_staged_sectors((*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr))
364
365
366
367
368
369
	defer C.sector_builder_ffi_destroy_get_staged_sectors_response(resPtr)

	if resPtr.status_code != 0 {
		return nil, errors.New(C.GoString(resPtr.error_msg))
	}

370
	meta, err := goStagedSectorMetadata(resPtr.sectors_ptr, resPtr.sectors_len)
371
372
373
374
375
376
377
	if err != nil {
		return nil, err
	}

	return meta, nil
}

378
379
380
// GetAllSealedSectors returns a slice of all sealed sector metadata, excluding
// sector health.
func GetAllSealedSectors(sectorBuilderPtr unsafe.Pointer) ([]SealedSectorMetadata, error) {
Sidney Keese's avatar
Sidney Keese committed
381
382
	defer elapsed("GetAllSealedSectors")()

383
384
	return getAllSealedSectors(sectorBuilderPtr, false)
}
Sidney Keese's avatar
Sidney Keese committed
385

386
387
388
389
390
// GetAllSealedSectorsWithHealth returns a slice of all sealed sector metadata
// for the sector builder, including sector health info (which can be expensive
// to compute).
func GetAllSealedSectorsWithHealth(sectorBuilderPtr unsafe.Pointer) ([]SealedSectorMetadata, error) {
	defer elapsed("GetAllSealedSectorsWithHealth")()
Sidney Keese's avatar
Sidney Keese committed
391

392
	return getAllSealedSectors(sectorBuilderPtr, true)
Sidney Keese's avatar
Sidney Keese committed
393
394
}

395
396
397
// GetSectorSealingStatusByID produces sector sealing status (staged, sealing in
// progress, sealed, failed) for the provided sector id. If no sector
// corresponding to the provided id exists, this function returns an error.
398
399
400
func GetSectorSealingStatusByID(sectorBuilderPtr unsafe.Pointer, sectorID uint64) (SectorSealingStatus, error) {
	defer elapsed("GetSectorSealingStatusByID")()

401
402
403
404
	resPtr := C.sector_builder_ffi_get_seal_status(
		(*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr),
		C.uint64_t(sectorID),
	)
405
406
407
408
409
410
411
	defer C.sector_builder_ffi_destroy_get_seal_status_response(resPtr)

	if resPtr.status_code != 0 {
		return SectorSealingStatus{}, errors.New(C.GoString(resPtr.error_msg))
	}

	if resPtr.seal_status_code == C.Failed {
412
		return SectorSealingStatus{SectorID: sectorID, State: sealing_state.Failed, SealErrorMsg: C.GoString(resPtr.seal_error_msg)}, nil
413
	} else if resPtr.seal_status_code == C.Pending {
414
		return SectorSealingStatus{SectorID: sectorID, State: sealing_state.Pending}, nil
415
	} else if resPtr.seal_status_code == C.Sealing {
416
		return SectorSealingStatus{SectorID: sectorID, State: sealing_state.Sealing}, nil
417
	} else if resPtr.seal_status_code == C.Sealed {
418
419
		commRSlice := goBytes(&resPtr.comm_r[0], CommitmentBytesLen)
		var commR [CommitmentBytesLen]byte
420
421
		copy(commR[:], commRSlice)

422
423
		commDSlice := goBytes(&resPtr.comm_d[0], CommitmentBytesLen)
		var commD [CommitmentBytesLen]byte
424
425
		copy(commD[:], commDSlice)

426
427
		commRStarSlice := goBytes(&resPtr.comm_r_star[0], CommitmentBytesLen)
		var commRStar [CommitmentBytesLen]byte
428
429
430
431
432
433
434
435
436
437
		copy(commRStar[:], commRStarSlice)

		proof := goBytes(resPtr.proof_ptr, resPtr.proof_len)

		ps, err := goPieceMetadata(resPtr.pieces_ptr, resPtr.pieces_len)
		if err != nil {
			return SectorSealingStatus{}, errors.Wrap(err, "failed to marshal from string to cid")
		}

		return SectorSealingStatus{
438
439
440
441
442
443
444
			SectorID:  sectorID,
			State:     sealing_state.Sealed,
			CommD:     commD,
			CommR:     commR,
			CommRStar: commRStar,
			Proof:     proof,
			Pieces:    ps,
445
446
447
448
449
450
451
452
453
454
		}, nil
	} else {
		// unknown
		return SectorSealingStatus{}, errors.New("unexpected seal status")
	}
}

// GeneratePoSt produces a proof-of-spacetime for the provided replica commitments.
func GeneratePoSt(
	sectorBuilderPtr unsafe.Pointer,
455
	sectorInfo SortedSectorInfo,
456
	challengeSeed [CommitmentBytesLen]byte,
laser's avatar
laser committed
457
458
	faults []uint64,
) ([]byte, error) {
459
460
	defer elapsed("GeneratePoSt")()

461
462
463
464
465
466
467
	// CommRs and sector ids must be provided to C.verify_post in the same order
	// that they were provided to the C.generate_post
	sortedCommRs := make([][CommitmentBytesLen]byte, len(sectorInfo.Values()))
	for idx, v := range sectorInfo.Values() {
		sortedCommRs[idx] = v.CommR
	}

468
	// flattening the byte slice makes it easier to copy into the C heap
469
470
	flattened := make([]byte, CommitmentBytesLen*len(sortedCommRs))
	for idx, commR := range sortedCommRs {
471
		copy(flattened[(CommitmentBytesLen*idx):(CommitmentBytesLen*(1+idx))], commR[:])
472
473
474
475
476
477
478
479
	}

	// copy the Go byte slice into C memory
	cflattened := C.CBytes(flattened)
	defer C.free(cflattened)

	challengeSeedPtr := unsafe.Pointer(&(challengeSeed)[0])

laser's avatar
laser committed
480
481
482
	faultsPtr, faultsSize := cUint64s(faults)
	defer C.free(unsafe.Pointer(faultsPtr))

483
	// a mutable pointer to a GeneratePoStResponse C-struct
484
485
486
487
488
	resPtr := C.sector_builder_ffi_generate_post(
		(*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr),
		(*C.uint8_t)(cflattened),
		C.size_t(len(flattened)),
		(*[CommitmentBytesLen]C.uint8_t)(challengeSeedPtr),
laser's avatar
laser committed
489
490
		faultsPtr,
		faultsSize,
491
	)
492
493
494
	defer C.sector_builder_ffi_destroy_generate_post_response(resPtr)

	if resPtr.status_code != 0 {
laser's avatar
laser committed
495
		return nil, errors.New(C.GoString(resPtr.error_msg))
496
497
	}

laser's avatar
laser committed
498
	return goBytes(resPtr.proof_ptr, resPtr.proof_len), nil
499
}
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548

// VerifyPieceInclusionProof returns true if the piece inclusion proof is valid
// with the given arguments.
func VerifyPieceInclusionProof(sectorSize uint64, pieceSize uint64, commP [CommitmentBytesLen]byte, commD [CommitmentBytesLen]byte, proof []byte) (bool, error) {
	commDCBytes := C.CBytes(commD[:])
	defer C.free(commDCBytes)

	commPCBytes := C.CBytes(commP[:])
	defer C.free(commPCBytes)

	pieceInclusionProofCBytes := C.CBytes(proof)
	defer C.free(pieceInclusionProofCBytes)

	resPtr := C.sector_builder_ffi_verify_piece_inclusion_proof(
		(*[CommitmentBytesLen]C.uint8_t)(commDCBytes),
		(*[CommitmentBytesLen]C.uint8_t)(commPCBytes),
		(*C.uint8_t)(pieceInclusionProofCBytes),
		C.size_t(len(proof)),
		C.uint64_t(pieceSize),
		C.uint64_t(sectorSize),
	)
	defer C.sector_builder_ffi_destroy_verify_piece_inclusion_proof_response(resPtr)

	if resPtr.status_code != 0 {
		return false, errors.New(C.GoString(resPtr.error_msg))
	}

	return bool(resPtr.is_valid), nil
}

// GeneratePieceCommitment produces a piece commitment for the provided data
// stored at a given piece path.
func GeneratePieceCommitment(piecePath string, pieceSize uint64) (commP [CommitmentBytesLen]byte, err error) {
	cPiecePath := C.CString(piecePath)
	defer C.free(unsafe.Pointer(cPiecePath))

	resPtr := C.sector_builder_ffi_generate_piece_commitment(cPiecePath, C.uint64_t(pieceSize))
	defer C.sector_builder_ffi_destroy_generate_piece_commitment_response(resPtr)

	if resPtr.status_code != 0 {
		return [CommitmentBytesLen]byte{}, errors.New(C.GoString(resPtr.error_msg))
	}

	commPSlice := goBytes(&resPtr.comm_p[0], CommitmentBytesLen)
	var commitment [CommitmentBytesLen]byte
	copy(commitment[:], commPSlice)

	return commitment, nil
}
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564

func getAllSealedSectors(sectorBuilderPtr unsafe.Pointer, performHealthchecks bool) ([]SealedSectorMetadata, error) {
	resPtr := C.sector_builder_ffi_get_sealed_sectors((*C.sector_builder_ffi_SectorBuilder)(sectorBuilderPtr), C.bool(performHealthchecks))
	defer C.sector_builder_ffi_destroy_get_sealed_sectors_response(resPtr)

	if resPtr.status_code != 0 {
		return nil, errors.New(C.GoString(resPtr.error_msg))
	}

	meta, err := goSealedSectorMetadata(resPtr.sectors_ptr, resPtr.sectors_len)
	if err != nil {
		return nil, err
	}

	return meta, nil
}