da4528932a
Types for storage market Modify deals.Provider to implement storagemarket.StorageProvider Inject storagemarket.StorageProvider Storage Provider interfaces Storage Client interfaces Add ValidatePublishedDeal to ClientNodeAdapter Remove FundManager from client Remove Wallet from client Remove StateManager, Events, Wallet from client Rebasing - Copy types.BigInt, use TokenAmount/BigInt for token amounts - Remove auto-imported log package - Move `checkAskSignature` to a client file. - Plumb contexts through fix(storagemarket): use publish cids Switch back to publish message cids to reduce the dependency surface area
173 lines
4.6 KiB
Go
173 lines
4.6 KiB
Go
package deals
|
|
|
|
import (
|
|
"context"
|
|
|
|
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"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/storagemarket"
|
|
)
|
|
|
|
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) {
|
|
|
|
head, err := p.spn.MostRecentStateId(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if head.Height() >= deal.Proposal.ProposalExpiration {
|
|
return nil, xerrors.Errorf("deal proposal already expired")
|
|
}
|
|
|
|
// TODO: check StorageCollateral
|
|
|
|
minPrice := types.BigDiv(types.BigMul(p.ask.Ask.Price, types.NewInt(deal.Proposal.PieceSize)), types.NewInt(1<<30))
|
|
if deal.Proposal.StoragePricePerEpoch.LessThan(minPrice) {
|
|
return nil, xerrors.Errorf("storage price per epoch less than asking price: %s < %s", deal.Proposal.StoragePricePerEpoch, minPrice)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// check market funds
|
|
clientMarketBalance, err := p.spn.GetBalance(ctx, deal.Proposal.Client)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("getting client market balance failed: %w", err)
|
|
}
|
|
|
|
// This doesn't guarantee that the client won't withdraw / lock those funds
|
|
// but it's a decent first filter
|
|
if clientMarketBalance.Available.LessThan(deal.Proposal.TotalStoragePrice()) {
|
|
return nil, xerrors.New("clientMarketBalance.Available too small")
|
|
}
|
|
|
|
waddr, err := p.spn.GetMinerWorker(ctx, deal.Proposal.Provider)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: check StorageCollateral (may be too large (or too small))
|
|
if err := p.spn.EnsureFunds(ctx, waddr, storagemarket.TokenAmount(deal.Proposal.StorageCollateral)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
smDeal := storagemarket.MinerDeal{
|
|
Client: deal.Client,
|
|
Proposal: deal.Proposal,
|
|
ProposalCid: deal.ProposalCid,
|
|
State: deal.State,
|
|
Ref: deal.Ref,
|
|
SectorID: deal.SectorID,
|
|
}
|
|
|
|
dealId, mcid, err := p.spn.PublishDeals(ctx, smDeal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Infof("fetching data for a deal %d", dealId)
|
|
err = p.sendSignedResponse(ctx, &Response{
|
|
State: api.DealAccepted,
|
|
|
|
Proposal: deal.ProposalCid,
|
|
PublishMessage: &mcid,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := p.disconnect(deal); err != nil {
|
|
log.Warnf("closing client connection: %+v", err)
|
|
}
|
|
|
|
ssb := builder.NewSelectorSpecBuilder(ipldfree.NodeBuilder())
|
|
|
|
// this is the selector for "get the whole DAG"
|
|
// TODO: support storage deals with custom payload selectors
|
|
allSelector := ssb.ExploreRecursive(selector.RecursionLimitNone(),
|
|
ssb.ExploreAll(ssb.ExploreRecursiveEdge())).Node()
|
|
|
|
// 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)
|
|
_, err = p.dataTransfer.OpenPullDataChannel(ctx,
|
|
deal.Client,
|
|
&StorageDataTransferVoucher{Proposal: deal.ProposalCid, DealID: uint64(dealId)},
|
|
deal.Ref,
|
|
allSelector,
|
|
)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to open pull data channel: %w", err)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// STAGED
|
|
|
|
func (p *Provider) staged(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
|
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,
|
|
},
|
|
"",
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return func(deal *MinerDeal) {
|
|
deal.SectorID = sectorID
|
|
}, nil
|
|
}
|
|
|
|
// SEALING
|
|
|
|
func (p *Provider) sealing(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
|
// TODO: consider waiting for seal to happen
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func (p *Provider) complete(ctx context.Context, deal MinerDeal) (func(*MinerDeal), error) {
|
|
// TODO: observe sector lifecycle, status, expiration..
|
|
|
|
return nil, nil
|
|
}
|