-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow custom decisioning for a provider to decide retrieval deals. #269
Changes from 3 commits
5eb0d68
f613a20
fb5c434
03600a4
370a838
867afdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,10 @@ import ( | |
"github.com/filecoin-project/go-fil-markets/shared" | ||
) | ||
|
||
type provider struct { | ||
type RetrievalProviderOption func(p *Provider) | ||
type DealDecider func(ctx context.Context, state retrievalmarket.ProviderDealState) (bool, string, error) | ||
|
||
type Provider struct { | ||
bs blockstore.Blockstore | ||
node retrievalmarket.RetrievalProviderNode | ||
network rmnet.RetrievalMarketNetwork | ||
|
@@ -40,9 +43,11 @@ type provider struct { | |
dealStreams map[retrievalmarket.ProviderDealIdentifier]rmnet.RetrievalDealStream | ||
blockReaders map[retrievalmarket.ProviderDealIdentifier]blockio.BlockReader | ||
stateMachines fsm.Group | ||
dealDecider DealDecider | ||
} | ||
|
||
var _ retrievalmarket.RetrievalProvider = &provider{} | ||
var _ retrievalmarket.RetrievalProvider = new(Provider) | ||
var _ providerstates.ProviderDealEnvironment = new(Provider) | ||
|
||
// DefaultPricePerByte is the charge per byte retrieved if the miner does | ||
// not specifically set it | ||
|
@@ -56,10 +61,13 @@ var DefaultPaymentInterval = uint64(1 << 20) | |
// set to to 1Mb if the miner does not explicitly set it otherwise | ||
var DefaultPaymentIntervalIncrease = uint64(1 << 20) | ||
|
||
// NewProvider returns a new retrieval provider | ||
func NewProvider(minerAddress address.Address, node retrievalmarket.RetrievalProviderNode, network rmnet.RetrievalMarketNetwork, pieceStore piecestore.PieceStore, bs blockstore.Blockstore, ds datastore.Batching) (retrievalmarket.RetrievalProvider, error) { | ||
// NewProvider returns a new retrieval Provider | ||
func NewProvider(minerAddress address.Address, node retrievalmarket.RetrievalProviderNode, | ||
network rmnet.RetrievalMarketNetwork, pieceStore piecestore.PieceStore, | ||
bs blockstore.Blockstore, ds datastore.Batching, opts ...RetrievalProviderOption, | ||
) (retrievalmarket.RetrievalProvider, error) { | ||
|
||
p := &provider{ | ||
p := &Provider{ | ||
bs: bs, | ||
node: node, | ||
network: network, | ||
|
@@ -79,40 +87,48 @@ func NewProvider(minerAddress address.Address, node retrievalmarket.RetrievalPro | |
StateEntryFuncs: providerstates.ProviderStateEntryFuncs, | ||
Notifier: p.notifySubscribers, | ||
}) | ||
p.stateMachines = statemachines | ||
if err != nil { | ||
return nil, err | ||
} | ||
p.Configure(opts...) | ||
p.stateMachines = statemachines | ||
return p, nil | ||
} | ||
|
||
func (p *Provider) RunDealDecisioningLogic(ctx context.Context, state retrievalmarket.ProviderDealState) (bool, string, error) { | ||
if p.dealDecider == nil { | ||
return true, "", nil | ||
} | ||
return p.dealDecider(ctx, state) | ||
} | ||
|
||
// Stop stops handling incoming requests | ||
func (p *provider) Stop() error { | ||
func (p *Provider) Stop() error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this had to be exported similarly to storagemarket, so a decider func is possible |
||
return p.network.StopHandlingRequests() | ||
} | ||
|
||
// Start begins listening for deals on the given host | ||
func (p *provider) Start() error { | ||
func (p *Provider) Start() error { | ||
return p.network.SetDelegate(p) | ||
} | ||
|
||
// V0 | ||
// SetPricePerByte sets the price per byte a miner charges for retrievals | ||
func (p *provider) SetPricePerByte(price abi.TokenAmount) { | ||
func (p *Provider) SetPricePerByte(price abi.TokenAmount) { | ||
p.pricePerByte = price | ||
} | ||
|
||
// SetPaymentInterval sets the maximum number of bytes a a provider will send before | ||
// SetPaymentInterval sets the maximum number of bytes a a Provider will send before | ||
// requesting further payment, and the rate at which that value increases | ||
func (p *provider) SetPaymentInterval(paymentInterval uint64, paymentIntervalIncrease uint64) { | ||
func (p *Provider) SetPaymentInterval(paymentInterval uint64, paymentIntervalIncrease uint64) { | ||
p.paymentInterval = paymentInterval | ||
p.paymentIntervalIncrease = paymentIntervalIncrease | ||
} | ||
|
||
// unsubscribeAt returns a function that removes an item from the subscribers list by comparing | ||
// their reflect.ValueOf before pulling the item out of the slice. Does not preserve order. | ||
// Subsequent, repeated calls to the func with the same Subscriber are a no-op. | ||
func (p *provider) unsubscribeAt(sub retrievalmarket.ProviderSubscriber) retrievalmarket.Unsubscribe { | ||
func (p *Provider) unsubscribeAt(sub retrievalmarket.ProviderSubscriber) retrievalmarket.Unsubscribe { | ||
return func() { | ||
p.subscribersLk.Lock() | ||
defer p.subscribersLk.Unlock() | ||
|
@@ -127,7 +143,7 @@ func (p *provider) unsubscribeAt(sub retrievalmarket.ProviderSubscriber) retriev | |
} | ||
} | ||
|
||
func (p *provider) notifySubscribers(eventName fsm.EventName, state fsm.StateType) { | ||
func (p *Provider) notifySubscribers(eventName fsm.EventName, state fsm.StateType) { | ||
p.subscribersLk.RLock() | ||
defer p.subscribersLk.RUnlock() | ||
evt := eventName.(retrievalmarket.ProviderEvent) | ||
|
@@ -138,7 +154,7 @@ func (p *provider) notifySubscribers(eventName fsm.EventName, state fsm.StateTyp | |
} | ||
|
||
// SubscribeToEvents listens for events that happen related to client retrievals | ||
func (p *provider) SubscribeToEvents(subscriber retrievalmarket.ProviderSubscriber) retrievalmarket.Unsubscribe { | ||
func (p *Provider) SubscribeToEvents(subscriber retrievalmarket.ProviderSubscriber) retrievalmarket.Unsubscribe { | ||
p.subscribersLk.Lock() | ||
p.subscribers = append(p.subscribers, subscriber) | ||
p.subscribersLk.Unlock() | ||
|
@@ -147,15 +163,15 @@ func (p *provider) SubscribeToEvents(subscriber retrievalmarket.ProviderSubscrib | |
} | ||
|
||
// V1 | ||
func (p *provider) SetPricePerUnseal(price abi.TokenAmount) { | ||
func (p *Provider) SetPricePerUnseal(price abi.TokenAmount) { | ||
panic("not implemented") | ||
} | ||
|
||
func (p *provider) ListDeals() map[retrievalmarket.ProviderDealID]retrievalmarket.ProviderDealState { | ||
func (p *Provider) ListDeals() map[retrievalmarket.ProviderDealID]retrievalmarket.ProviderDealState { | ||
panic("not implemented") | ||
} | ||
|
||
func (p *provider) HandleQueryStream(stream rmnet.RetrievalQueryStream) { | ||
func (p *Provider) HandleQueryStream(stream rmnet.RetrievalQueryStream) { | ||
defer stream.Close() | ||
query, err := stream.ReadQuery() | ||
if err != nil { | ||
|
@@ -212,7 +228,7 @@ func (p *provider) HandleQueryStream(stream rmnet.RetrievalQueryStream) { | |
} | ||
} | ||
|
||
func (p *provider) HandleDealStream(stream rmnet.RetrievalDealStream) { | ||
func (p *Provider) HandleDealStream(stream rmnet.RetrievalDealStream) { | ||
// read deal proposal (or fail) | ||
err := p.newProviderDeal(stream) | ||
if err != nil { | ||
|
@@ -221,7 +237,7 @@ func (p *provider) HandleDealStream(stream rmnet.RetrievalDealStream) { | |
} | ||
} | ||
|
||
func (p *provider) newProviderDeal(stream rmnet.RetrievalDealStream) error { | ||
func (p *Provider) newProviderDeal(stream rmnet.RetrievalDealStream) error { | ||
dealProposal, err := stream.ReadDealProposal() | ||
if err != nil { | ||
return err | ||
|
@@ -264,15 +280,15 @@ func (p *provider) newProviderDeal(stream rmnet.RetrievalDealStream) error { | |
return nil | ||
} | ||
|
||
func (p *provider) Node() retrievalmarket.RetrievalProviderNode { | ||
func (p *Provider) Node() retrievalmarket.RetrievalProviderNode { | ||
return p.node | ||
} | ||
|
||
func (p *provider) DealStream(id retrievalmarket.ProviderDealIdentifier) rmnet.RetrievalDealStream { | ||
func (p *Provider) DealStream(id retrievalmarket.ProviderDealIdentifier) rmnet.RetrievalDealStream { | ||
return p.dealStreams[id] | ||
} | ||
|
||
func (p *provider) CheckDealParams(pricePerByte abi.TokenAmount, paymentInterval uint64, paymentIntervalIncrease uint64) error { | ||
func (p *Provider) CheckDealParams(pricePerByte abi.TokenAmount, paymentInterval uint64, paymentIntervalIncrease uint64) error { | ||
if pricePerByte.LessThan(p.pricePerByte) { | ||
return errors.New("Price per byte too low") | ||
} | ||
|
@@ -285,15 +301,15 @@ func (p *provider) CheckDealParams(pricePerByte abi.TokenAmount, paymentInterval | |
return nil | ||
} | ||
|
||
func (p *provider) NextBlock(ctx context.Context, id retrievalmarket.ProviderDealIdentifier) (retrievalmarket.Block, bool, error) { | ||
func (p *Provider) NextBlock(ctx context.Context, id retrievalmarket.ProviderDealIdentifier) (retrievalmarket.Block, bool, error) { | ||
br, ok := p.blockReaders[id] | ||
if !ok { | ||
return retrievalmarket.Block{}, false, errors.New("Could not read block") | ||
} | ||
return br.ReadBlock(ctx) | ||
} | ||
|
||
func (p *provider) GetPieceSize(c cid.Cid) (uint64, error) { | ||
func (p *Provider) GetPieceSize(c cid.Cid) (uint64, error) { | ||
pieceInfo, err := getPieceInfoFromCid(p.pieceStore, c, cid.Undef) | ||
if err != nil { | ||
return 0, err | ||
|
@@ -304,6 +320,18 @@ func (p *provider) GetPieceSize(c cid.Cid) (uint64, error) { | |
return pieceInfo.Deals[0].Length, nil | ||
} | ||
|
||
func (p *Provider) Configure(opts ...RetrievalProviderOption) { | ||
for _, opt := range opts { | ||
opt(p) | ||
} | ||
} | ||
|
||
func DealDeciderOpt(dd DealDecider) RetrievalProviderOption { | ||
return func(provider *Provider) { | ||
provider.dealDecider = dd | ||
} | ||
} | ||
|
||
func getPieceInfoFromCid(pieceStore piecestore.PieceStore, payloadCID, pieceCID cid.Cid) (piecestore.PieceInfo, error) { | ||
cidInfo, err := pieceStore.GetCIDInfo(payloadCID) | ||
if err != nil { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package providerstates | |
|
||
import ( | ||
"context" | ||
"errors" | ||
|
||
"github.com/filecoin-project/go-statemachine/fsm" | ||
"github.com/filecoin-project/specs-actors/actors/abi" | ||
|
@@ -20,6 +21,7 @@ type ProviderDealEnvironment interface { | |
DealStream(id rm.ProviderDealIdentifier) rmnet.RetrievalDealStream | ||
NextBlock(context.Context, rm.ProviderDealIdentifier) (rm.Block, bool, error) | ||
CheckDealParams(pricePerByte abi.TokenAmount, paymentInterval uint64, paymentIntervalIncrease uint64) error | ||
RunDealDecisioningLogic(ctx context.Context, state rm.ProviderDealState) (bool, string, error) | ||
} | ||
|
||
// ReceiveDeal receives and evaluates a deal proposal | ||
|
@@ -36,21 +38,33 @@ func ReceiveDeal(ctx fsm.Context, environment ProviderDealEnvironment, deal rm.P | |
} | ||
|
||
// check that the deal parameters match our required parameters (or reject) | ||
err = environment.CheckDealParams(dealProposal.PricePerByte, dealProposal.PaymentInterval, dealProposal.PaymentIntervalIncrease) | ||
err = environment.CheckDealParams(dealProposal.PricePerByte, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was conflicted here between moving all decisioning to |
||
dealProposal.PaymentInterval, | ||
dealProposal.PaymentIntervalIncrease) | ||
if err != nil { | ||
return ctx.Trigger(rm.ProviderEventDealRejected, err) | ||
} | ||
return ctx.Trigger(rm.ProviderEventDealReceived) | ||
} | ||
|
||
err = environment.DealStream(deal.Identifier()).WriteDealResponse(rm.DealResponse{ | ||
// DecideOnDeal | ||
func DecideOnDeal(ctx fsm.Context, env ProviderDealEnvironment, state rm.ProviderDealState) error { | ||
accepted, reason, err := env.RunDealDecisioningLogic(ctx.Context(), state) | ||
if err != nil { | ||
return ctx.Trigger(rm.ProviderEventDecisioningError, err) | ||
} | ||
if !accepted { | ||
return ctx.Trigger(rm.ProviderEventDealRejected, errors.New(reason)) | ||
} | ||
err = env.DealStream(state.Identifier()).WriteDealResponse(rm.DealResponse{ | ||
Status: rm.DealStatusAccepted, | ||
ID: deal.ID, | ||
ID: state.ID, | ||
}) | ||
if err != nil { | ||
return ctx.Trigger(rm.ProviderEventWriteResponseFailed, err) | ||
} | ||
|
||
return ctx.Trigger(rm.ProviderEventDealAccepted, dealProposal) | ||
|
||
return ctx.Trigger(rm.ProviderEventDealAccepted, state.DealProposal) | ||
} | ||
|
||
// SendBlocks sends blocks to the client until funds are needed | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did you decide to make this a public struct? I just want to make sure it's neccessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's similar to StorageMarket -- if you make it private you can't operate on (p Provider) to set a config option. StorageProviderOption and RetrievalProviderOption both take a (Storage|Retrieval)Provider.