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
294 lines
7.2 KiB
Go
294 lines
7.2 KiB
Go
package deals
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipfs/go-datastore"
|
|
"github.com/ipfs/go-datastore/namespace"
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
inet "github.com/libp2p/go-libp2p-core/network"
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/go-cbor-util"
|
|
"github.com/filecoin-project/go-data-transfer"
|
|
"github.com/filecoin-project/go-statestore"
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/actors"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
|
"github.com/filecoin-project/lotus/storagemarket"
|
|
)
|
|
|
|
var ProviderDsPrefix = "/deals/provider"
|
|
|
|
type MinerDeal struct {
|
|
Client peer.ID
|
|
Proposal actors.StorageDealProposal
|
|
ProposalCid cid.Cid
|
|
State api.DealState
|
|
|
|
Ref cid.Cid
|
|
|
|
DealID uint64
|
|
SectorID uint64 // Set when State >= DealStaged
|
|
|
|
s inet.Stream
|
|
}
|
|
|
|
type Provider struct {
|
|
pricePerByteBlock types.BigInt // how much we want for storing one byte for one block
|
|
minPieceSize uint64
|
|
|
|
ask *types.SignedStorageAsk
|
|
askLk sync.Mutex
|
|
|
|
spn storagemarket.StorageProviderNode
|
|
|
|
// TODO: This will go away once storage market module + CAR
|
|
// is implemented
|
|
dag dtypes.StagingDAG
|
|
|
|
// dataTransfer is the manager of data transfers used by this storage provider
|
|
dataTransfer dtypes.ProviderDataTransfer
|
|
|
|
deals *statestore.StateStore
|
|
ds dtypes.MetadataDS
|
|
|
|
conns map[cid.Cid]inet.Stream
|
|
|
|
actor address.Address
|
|
|
|
incoming chan MinerDeal
|
|
updated chan minerDealUpdate
|
|
stop chan struct{}
|
|
stopped chan struct{}
|
|
}
|
|
|
|
type minerDealUpdate struct {
|
|
newState api.DealState
|
|
id cid.Cid
|
|
err error
|
|
mut func(*MinerDeal)
|
|
}
|
|
|
|
var (
|
|
// ErrDataTransferFailed means a data transfer for a deal failed
|
|
ErrDataTransferFailed = errors.New("deal data transfer failed")
|
|
)
|
|
|
|
func NewProvider(ds dtypes.MetadataDS, dag dtypes.StagingDAG, dataTransfer dtypes.ProviderDataTransfer, spn storagemarket.StorageProviderNode) (storagemarket.StorageProvider, error) {
|
|
addr, err := ds.Get(datastore.NewKey("miner-address"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
minerAddress, err := address.NewFromBytes(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
h := &Provider{
|
|
dag: dag,
|
|
dataTransfer: dataTransfer,
|
|
spn: spn,
|
|
|
|
pricePerByteBlock: types.NewInt(3), // TODO: allow setting
|
|
minPieceSize: 256, // TODO: allow setting (BUT KEEP MIN 256! (because of how we fill sectors up))
|
|
|
|
conns: map[cid.Cid]inet.Stream{},
|
|
|
|
incoming: make(chan MinerDeal),
|
|
updated: make(chan minerDealUpdate),
|
|
stop: make(chan struct{}),
|
|
stopped: make(chan struct{}),
|
|
|
|
actor: minerAddress,
|
|
|
|
deals: statestore.New(namespace.Wrap(ds, datastore.NewKey(ProviderDsPrefix))),
|
|
ds: ds,
|
|
}
|
|
|
|
if err := h.tryLoadAsk(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if h.ask == nil {
|
|
// TODO: we should be fine with this state, and just say it means 'not actively accepting deals'
|
|
// for now... lets just set a price
|
|
if err := h.SetPrice(types.NewInt(500_000_000), 1000000); err != nil {
|
|
return nil, xerrors.Errorf("failed setting a default price: %w", err)
|
|
}
|
|
}
|
|
|
|
// register a data transfer event handler -- this will move deals from
|
|
// accepted to staged
|
|
h.dataTransfer.SubscribeToEvents(h.onDataTransferEvent)
|
|
|
|
return h, nil
|
|
}
|
|
|
|
func (p *Provider) Run(ctx context.Context, host host.Host) {
|
|
// TODO: restore state
|
|
|
|
host.SetStreamHandler(storagemarket.DealProtocolID, p.HandleStream)
|
|
host.SetStreamHandler(storagemarket.AskProtocolID, p.HandleAskStream)
|
|
|
|
go func() {
|
|
defer log.Warn("quitting deal provider loop")
|
|
defer close(p.stopped)
|
|
|
|
for {
|
|
select {
|
|
case deal := <-p.incoming: // DealAccepted
|
|
p.onIncoming(deal)
|
|
case update := <-p.updated: // DealStaged
|
|
p.onUpdated(ctx, update)
|
|
case <-p.stop:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (p *Provider) onIncoming(deal MinerDeal) {
|
|
log.Info("incoming deal")
|
|
|
|
p.conns[deal.ProposalCid] = deal.s
|
|
|
|
if err := p.deals.Begin(deal.ProposalCid, &deal); err != nil {
|
|
// This can happen when client re-sends proposal
|
|
p.failDeal(context.TODO(), deal.ProposalCid, err)
|
|
log.Errorf("deal tracking failed: %s", err)
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
p.updated <- minerDealUpdate{
|
|
newState: api.DealAccepted,
|
|
id: deal.ProposalCid,
|
|
err: nil,
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (p *Provider) onUpdated(ctx context.Context, update minerDealUpdate) {
|
|
log.Infof("Deal %s updated state to %s", update.id, api.DealStates[update.newState])
|
|
if update.err != nil {
|
|
log.Errorf("deal %s (newSt: %d) failed: %+v", update.id, update.newState, update.err)
|
|
p.failDeal(ctx, update.id, update.err)
|
|
return
|
|
}
|
|
var deal MinerDeal
|
|
err := p.deals.Mutate(update.id, func(d *MinerDeal) error {
|
|
d.State = update.newState
|
|
if update.mut != nil {
|
|
update.mut(d)
|
|
}
|
|
deal = *d
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
p.failDeal(ctx, update.id, err)
|
|
return
|
|
}
|
|
|
|
switch update.newState {
|
|
case api.DealAccepted:
|
|
p.handle(ctx, deal, p.accept, api.DealNoUpdate)
|
|
case api.DealStaged:
|
|
p.handle(ctx, deal, p.staged, api.DealSealing)
|
|
case api.DealSealing:
|
|
p.handle(ctx, deal, p.sealing, api.DealComplete)
|
|
case api.DealComplete:
|
|
p.handle(ctx, deal, p.complete, api.DealNoUpdate)
|
|
}
|
|
}
|
|
|
|
// onDataTransferEvent is the function called when an event occurs in a data
|
|
// transfer -- it reads the voucher to verify this even occurred in a storage
|
|
// market deal, then, based on the data transfer event that occurred, it generates
|
|
// and update message for the deal -- either moving to staged for a completion
|
|
// event or moving to error if a data transfer error occurs
|
|
func (p *Provider) onDataTransferEvent(event datatransfer.Event, channelState datatransfer.ChannelState) {
|
|
voucher, ok := channelState.Voucher().(*StorageDataTransferVoucher)
|
|
// if this event is for a transfer not related to storage, ignore
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// data transfer events for opening and progress do not affect deal state
|
|
var next api.DealState
|
|
var err error
|
|
var mut func(*MinerDeal)
|
|
switch event.Code {
|
|
case datatransfer.Complete:
|
|
next = api.DealStaged
|
|
mut = func(deal *MinerDeal) {
|
|
deal.DealID = voucher.DealID
|
|
}
|
|
case datatransfer.Error:
|
|
next = api.DealFailed
|
|
err = ErrDataTransferFailed
|
|
default:
|
|
// the only events we care about are complete and error
|
|
return
|
|
}
|
|
|
|
select {
|
|
case p.updated <- minerDealUpdate{
|
|
newState: next,
|
|
id: voucher.Proposal,
|
|
err: err,
|
|
mut: mut,
|
|
}:
|
|
case <-p.stop:
|
|
}
|
|
}
|
|
|
|
func (p *Provider) newDeal(s inet.Stream, proposal Proposal) (MinerDeal, error) {
|
|
proposalNd, err := cborutil.AsIpld(proposal.DealProposal)
|
|
if err != nil {
|
|
return MinerDeal{}, err
|
|
}
|
|
|
|
return MinerDeal{
|
|
Client: s.Conn().RemotePeer(),
|
|
Proposal: *proposal.DealProposal,
|
|
ProposalCid: proposalNd.Cid(),
|
|
State: api.DealUnknown,
|
|
|
|
Ref: proposal.Piece,
|
|
|
|
s: s,
|
|
}, nil
|
|
}
|
|
|
|
func (p *Provider) HandleStream(s inet.Stream) {
|
|
log.Info("Handling storage deal proposal!")
|
|
|
|
proposal, err := p.readProposal(s)
|
|
if err != nil {
|
|
log.Error(err)
|
|
s.Close()
|
|
return
|
|
}
|
|
|
|
deal, err := p.newDeal(s, proposal)
|
|
if err != nil {
|
|
log.Errorf("%+v", err)
|
|
s.Close()
|
|
return
|
|
}
|
|
|
|
p.incoming <- deal
|
|
}
|
|
|
|
func (p *Provider) Stop() {
|
|
close(p.stop)
|
|
<-p.stopped
|
|
}
|