2019-10-21 18:12:11 +00:00
|
|
|
package deals
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
2019-10-28 23:51:50 +00:00
|
|
|
ipldfree "github.com/ipld/go-ipld-prime/impl/free"
|
|
|
|
"github.com/ipld/go-ipld-prime/traversal/selector"
|
|
|
|
"github.com/ipld/go-ipld-prime/traversal/selector/builder"
|
|
|
|
|
2019-10-21 18:12:11 +00:00
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
2019-11-04 19:57:54 +00:00
|
|
|
"github.com/filecoin-project/lotus/storagemarket"
|
2019-10-21 18:12:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type providerHandlerFunc func(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error)
|
|
|
|
|
|
|
|
func (p *Provider) handle(ctx context.Context, deal MinerDeal, cb providerHandlerFunc, next api.DealState) {
|
|
|
|
go func() {
|
|
|
|
mut, err := cb(ctx, deal)
|
|
|
|
|
|
|
|
if err == nil && next == api.DealNoUpdate {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case p.updated <- minerDealUpdate{
|
|
|
|
newState: next,
|
|
|
|
id: deal.ProposalCid,
|
|
|
|
err: err,
|
|
|
|
mut: mut,
|
|
|
|
}:
|
|
|
|
case <-p.stop:
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ACCEPTED
|
|
|
|
func (p *Provider) accept(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
head, err := p.spn.MostRecentStateId(ctx)
|
2019-10-23 17:39:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if head.Height() >= deal.Proposal.ProposalExpiration {
|
|
|
|
return nil, xerrors.Errorf("deal proposal already expired")
|
|
|
|
}
|
|
|
|
|
2019-10-25 12:44:34 +00:00
|
|
|
// TODO: check StorageCollateral
|
|
|
|
|
2019-10-29 12:02:24 +00:00
|
|
|
minPrice := types.BigDiv(types.BigMul(p.ask.Ask.Price, types.NewInt(deal.Proposal.PieceSize)), types.NewInt(1<<30))
|
2019-10-29 10:01:18 +00:00
|
|
|
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
|
|
|
|
return nil, xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice)
|
2019-10-25 12:44:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if deal.Proposal.PieceSize < p.ask.Ask.MinPieceSize {
|
|
|
|
return nil, xerrors.Errorf("piece size less than minimum required size: %d < %d", deal.Proposal.PieceSize, p.ask.Ask.MinPieceSize)
|
|
|
|
}
|
2019-10-21 18:12:11 +00:00
|
|
|
|
|
|
|
// check market funds
|
2019-11-04 19:57:54 +00:00
|
|
|
clientMarketBalance, err := p.spn.GetBalance(ctx, deal.Proposal.Client)
|
2019-10-21 18:12:11 +00:00
|
|
|
if err != nil {
|
2019-10-23 12:59:57 +00:00
|
|
|
return nil, xerrors.Errorf("getting client market balance failed: %w", err)
|
2019-10-21 18:12:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This doesn't guarantee that the client won't withdraw / lock those funds
|
|
|
|
// but it's a decent first filter
|
2019-10-29 10:01:18 +00:00
|
|
|
if clientMarketBalance.Available.LessThan(deal.Proposal.TotalStoragePrice()) {
|
2019-10-21 18:12:11 +00:00
|
|
|
return nil, xerrors.New("clientMarketBalance.Available too small")
|
|
|
|
}
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
waddr, err := p.spn.GetMinerWorker(ctx, deal.Proposal.Provider)
|
2019-10-23 17:39:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-08 17:15:38 +00:00
|
|
|
// TODO: check StorageCollateral (may be too large (or too small))
|
2019-11-04 19:57:54 +00:00
|
|
|
if err := p.spn.EnsureFunds(ctx, waddr, storagemarket.TokenAmount(deal.Proposal.StorageCollateral)); err != nil {
|
2019-11-08 17:15:38 +00:00
|
|
|
return nil, err
|
2019-10-21 18:12:11 +00:00
|
|
|
}
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
smDeal := storagemarket.MinerDeal{
|
|
|
|
Client: deal.Client,
|
|
|
|
Proposal: deal.Proposal,
|
|
|
|
ProposalCid: deal.ProposalCid,
|
|
|
|
State: deal.State,
|
|
|
|
Ref: deal.Ref,
|
|
|
|
SectorID: deal.SectorID,
|
2019-10-23 17:39:14 +00:00
|
|
|
}
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
dealId, mcid, err := p.spn.PublishDeals(ctx, smDeal)
|
2019-10-21 18:12:11 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
log.Infof("fetching data for a deal %d", dealId)
|
|
|
|
err = p.sendSignedResponse(ctx, &Response{
|
2019-11-07 12:57:00 +00:00
|
|
|
State: api.DealAccepted,
|
|
|
|
|
2019-11-04 19:57:54 +00:00
|
|
|
Proposal: deal.ProposalCid,
|
|
|
|
PublishMessage: &mcid,
|
2019-10-21 18:12:11 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-07 12:57:00 +00:00
|
|
|
if err := p.disconnect(deal); err != nil {
|
|
|
|
log.Warnf("closing client connection: %+v", err)
|
|
|
|
}
|
|
|
|
|
2019-10-28 23:51:50 +00:00
|
|
|
ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder())
|
|
|
|
|
|
|
|
// this is the selector for "get the whole DAG"
|
2019-10-29 00:57:12 +00:00
|
|
|
// TODO: support storage deals with custom payload selectors
|
2019-10-28 23:51:50 +00:00
|
|
|
allSelector := ssb.ExploreRecursive(selector.RecursionLimitNone(),
|
|
|
|
ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node()
|
|
|
|
|
2019-10-29 00:57:12 +00:00
|
|
|
// initiate a pull data transfer. This will complete asynchronously and the
|
|
|
|
// completion of the data transfer will trigger a change in deal state
|
|
|
|
// (see onDataTransferEvent)
|
2019-10-31 03:00:02 +00:00
|
|
|
_, err = p.dataTransfer.OpenPullDataChannel(ctx,
|
|
|
|
deal.Client,
|
2019-11-04 19:57:54 +00:00
|
|
|
&StorageDataTransferVoucher{Proposal: deal.ProposalCid, DealID: uint64(dealId)},
|
2019-10-28 23:51:50 +00:00
|
|
|
deal.Ref,
|
|
|
|
allSelector,
|
|
|
|
)
|
2019-12-05 05:14:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to open pull data channel: %w", err)
|
|
|
|
}
|
2019-10-28 23:51:50 +00:00
|
|
|
|
2019-12-01 20:07:42 +00:00
|
|
|
return nil, nil
|
2019-10-21 18:12:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// STAGED
|
|
|
|
|
|
|
|
func (p *Provider) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
2019-11-04 19:57:54 +00:00
|
|
|
sectorID, err := p.spn.OnDealComplete(
|
|
|
|
ctx,
|
|
|
|
storagemarket.MinerDeal{
|
|
|
|
Client: deal.Client,
|
|
|
|
Proposal: deal.Proposal,
|
|
|
|
ProposalCid: deal.ProposalCid,
|
|
|
|
State: deal.State,
|
|
|
|
Ref: deal.Ref,
|
|
|
|
DealID: deal.DealID,
|
|
|
|
},
|
|
|
|
"",
|
|
|
|
)
|
2019-10-23 18:04:07 +00:00
|
|
|
|
2019-11-05 18:40:51 +00:00
|
|
|
if err != nil {
|
2019-11-04 19:57:54 +00:00
|
|
|
return nil, err
|
2019-11-05 18:40:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return func(deal *MinerDeal) {
|
|
|
|
deal.SectorID = sectorID
|
|
|
|
}, nil
|
2019-10-21 18:12:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SEALING
|
|
|
|
|
|
|
|
func (p *Provider) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
2019-11-07 18:22:59 +00:00
|
|
|
// TODO: consider waiting for seal to happen
|
2019-10-21 18:12:11 +00:00
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Provider) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
2019-11-07 14:09:11 +00:00
|
|
|
// TODO: observe sector lifecycle, status, expiration..
|
2019-10-21 18:12:11 +00:00
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|