2021-07-19 13:51:46 +00:00
|
|
|
package dagstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-07-28 14:39:23 +00:00
|
|
|
"fmt"
|
2021-07-19 13:51:46 +00:00
|
|
|
"io"
|
2021-07-28 14:39:23 +00:00
|
|
|
"os"
|
|
|
|
"strconv"
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-27 16:24:48 +00:00
|
|
|
"github.com/filecoin-project/dagstore/throttle"
|
2021-07-19 13:51:46 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/go-fil-markets/piecestore"
|
|
|
|
"github.com/filecoin-project/go-fil-markets/retrievalmarket"
|
2021-07-26 22:32:51 +00:00
|
|
|
"github.com/filecoin-project/go-fil-markets/shared"
|
2021-07-19 13:51:46 +00:00
|
|
|
)
|
|
|
|
|
2021-07-28 14:39:23 +00:00
|
|
|
// MaxConcurrentStorageCalls caps the amount of concurrent calls to the
|
|
|
|
// storage, so that we don't spam it during heavy processes like bulk migration.
|
|
|
|
var MaxConcurrentStorageCalls = func() int {
|
|
|
|
// TODO replace env with config.toml attribute.
|
|
|
|
v, ok := os.LookupEnv("LOTUS_DAGSTORE_MOUNT_CONCURRENCY")
|
|
|
|
if ok {
|
|
|
|
concurrency, err := strconv.Atoi(v)
|
|
|
|
if err == nil {
|
|
|
|
return concurrency
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 100
|
|
|
|
}()
|
2021-07-27 16:24:48 +00:00
|
|
|
|
2021-08-03 11:22:40 +00:00
|
|
|
type MinerAPI interface {
|
2021-07-19 13:51:46 +00:00
|
|
|
FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error)
|
2021-07-21 11:31:20 +00:00
|
|
|
GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error)
|
2021-07-30 07:32:42 +00:00
|
|
|
IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error)
|
2021-07-21 12:04:07 +00:00
|
|
|
Start(ctx context.Context) error
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
type minerAPI struct {
|
2021-07-19 13:51:46 +00:00
|
|
|
pieceStore piecestore.PieceStore
|
|
|
|
rm retrievalmarket.RetrievalProviderNode
|
2021-07-27 16:24:48 +00:00
|
|
|
throttle throttle.Throttler
|
|
|
|
readyMgr *shared.ReadyManager
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
var _ MinerAPI = (*minerAPI)(nil)
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-08-03 11:22:40 +00:00
|
|
|
func NewMinerAPI(store piecestore.PieceStore, rm retrievalmarket.RetrievalProviderNode) MinerAPI {
|
2021-08-03 11:23:36 +00:00
|
|
|
return &minerAPI{
|
2021-07-19 13:51:46 +00:00
|
|
|
pieceStore: store,
|
|
|
|
rm: rm,
|
2021-07-28 14:39:23 +00:00
|
|
|
throttle: throttle.Fixed(MaxConcurrentStorageCalls),
|
2021-07-21 12:04:07 +00:00
|
|
|
readyMgr: shared.NewReadyManager(),
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
func (m *minerAPI) Start(_ context.Context) error {
|
2021-07-28 16:08:04 +00:00
|
|
|
return m.readyMgr.FireReady(nil)
|
2021-07-21 09:53:02 +00:00
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
func (m *minerAPI) IsUnsealed(ctx context.Context, pieceCid cid.Cid) (bool, error) {
|
2021-07-30 07:32:42 +00:00
|
|
|
err := m.readyMgr.AwaitReady()
|
|
|
|
if err != nil {
|
|
|
|
return false, xerrors.Errorf("failed while waiting for accessor to start: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var pieceInfo piecestore.PieceInfo
|
|
|
|
err = m.throttle.Do(ctx, func(ctx context.Context) (err error) {
|
|
|
|
pieceInfo, err = m.pieceStore.GetPieceInfo(pieceCid)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return false, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(pieceInfo.Deals) == 0 {
|
|
|
|
return false, xerrors.Errorf("no storage deals found for piece %s", pieceCid)
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if we have an unsealed deal for the given piece in any of the unsealed sectors.
|
|
|
|
for _, deal := range pieceInfo.Deals {
|
|
|
|
deal := deal
|
|
|
|
|
|
|
|
var isUnsealed bool
|
|
|
|
// Throttle this path to avoid flooding the storage subsystem.
|
|
|
|
err := m.throttle.Do(ctx, func(ctx context.Context) (err error) {
|
|
|
|
isUnsealed, err = m.rm.IsUnsealed(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to check if sector %d for deal %d was unsealed: %w", deal.SectorID, deal.DealID, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("failed to check/retrieve unsealed sector: %s", err)
|
|
|
|
continue // move on to the next match.
|
|
|
|
}
|
|
|
|
|
|
|
|
if isUnsealed {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// we don't have an unsealed sector containing the piece
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
func (m *minerAPI) FetchUnsealedPiece(ctx context.Context, pieceCid cid.Cid) (io.ReadCloser, error) {
|
2021-07-21 12:04:07 +00:00
|
|
|
err := m.readyMgr.AwaitReady()
|
2021-07-21 11:31:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-28 14:39:23 +00:00
|
|
|
// Throttle this path to avoid flooding the storage subsystem.
|
|
|
|
var pieceInfo piecestore.PieceInfo
|
|
|
|
err = m.throttle.Do(ctx, func(ctx context.Context) (err error) {
|
|
|
|
pieceInfo, err = m.pieceStore.GetPieceInfo(pieceCid)
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
|
2021-07-20 09:04:47 +00:00
|
|
|
if len(pieceInfo.Deals) == 0 {
|
2021-07-28 14:39:23 +00:00
|
|
|
return nil, xerrors.Errorf("no storage deals found for piece %s", pieceCid)
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// prefer an unsealed sector containing the piece if one exists
|
|
|
|
for _, deal := range pieceInfo.Deals {
|
2021-07-28 14:39:23 +00:00
|
|
|
deal := deal
|
|
|
|
|
|
|
|
// Throttle this path to avoid flooding the storage subsystem.
|
|
|
|
var reader io.ReadCloser
|
|
|
|
err := m.throttle.Do(ctx, func(ctx context.Context) (err error) {
|
|
|
|
isUnsealed, err := m.rm.IsUnsealed(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded())
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to check if sector %d for deal %d was unsealed: %w", deal.SectorID, deal.DealID, err)
|
|
|
|
}
|
|
|
|
if !isUnsealed {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// Because we know we have an unsealed copy, this UnsealSector call will actually not perform any unsealing.
|
|
|
|
reader, err = m.rm.UnsealSector(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded())
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
if err != nil {
|
2021-07-28 14:39:23 +00:00
|
|
|
log.Warnf("failed to check/retrieve unsealed sector: %s", err)
|
|
|
|
continue // move on to the next match.
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
2021-07-28 14:39:23 +00:00
|
|
|
|
|
|
|
if reader != nil {
|
|
|
|
// we were able to obtain a reader for an already unsealed piece
|
|
|
|
return reader, nil
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
lastErr := xerrors.New("no sectors found to unseal from")
|
|
|
|
// if there is no unsealed sector containing the piece, just read the piece from the first sector we are able to unseal.
|
|
|
|
for _, deal := range pieceInfo.Deals {
|
2021-07-20 09:04:47 +00:00
|
|
|
// Note that if the deal data is not already unsealed, unsealing may
|
|
|
|
// block for a long time with the current PoRep
|
2021-07-28 14:39:23 +00:00
|
|
|
//
|
|
|
|
// This path is unthrottled.
|
2021-07-19 13:51:46 +00:00
|
|
|
reader, err := m.rm.UnsealSector(ctx, deal.SectorID, deal.Offset.Unpadded(), deal.Length.Unpadded())
|
2021-07-20 09:04:47 +00:00
|
|
|
if err != nil {
|
|
|
|
lastErr = xerrors.Errorf("failed to unseal deal %d: %w", deal.DealID, err)
|
|
|
|
log.Warn(lastErr.Error())
|
|
|
|
continue
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
2021-07-20 09:04:47 +00:00
|
|
|
|
|
|
|
// Successfully fetched the deal data so return a reader over the data
|
|
|
|
return reader, nil
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
2021-07-20 09:04:47 +00:00
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
return nil, lastErr
|
|
|
|
}
|
|
|
|
|
2021-08-03 11:23:36 +00:00
|
|
|
func (m *minerAPI) GetUnpaddedCARSize(ctx context.Context, pieceCid cid.Cid) (uint64, error) {
|
2021-07-21 12:04:07 +00:00
|
|
|
err := m.readyMgr.AwaitReady()
|
2021-07-21 11:31:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
pieceInfo, err := m.pieceStore.GetPieceInfo(pieceCid)
|
|
|
|
if err != nil {
|
|
|
|
return 0, xerrors.Errorf("failed to fetch pieceInfo for piece %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(pieceInfo.Deals) == 0 {
|
|
|
|
return 0, xerrors.Errorf("no storage deals found for piece %s", pieceCid)
|
|
|
|
}
|
|
|
|
|
|
|
|
len := pieceInfo.Deals[0].Length
|
|
|
|
|
|
|
|
return uint64(len), nil
|
|
|
|
}
|