-
Notifications
You must be signed in to change notification settings - Fork 462
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
Feat/deal protocol #108
Feat/deal protocol #108
Conversation
b8d65f6
to
a31d265
Compare
Oh, and the cborutil stuff i wrote before it was a simple request response, its technically not needed now and i'll probably remove it. |
node/deal_protocol.go
Outdated
} | ||
|
||
vals, err := abi.ToValues([]interface{}{ | ||
big.NewInt(0).SetUint64(propose.Deal.Ask), |
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.
ToValues
should probably be smart enough to handle ints... maybe
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 should be able to handle uint32 and uint64 explicitly, but not uint to avoid accidentally introducing dependencies on build/architecture
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.
I mean that it should be able to convert normal ints into the big int 'Integer' type.
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.
not sure then, because that could lead to ambiguous cases if we support regular ints at some point
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.
I am missing the entry point for starting the actual process, how would this whole process get started if I had a deal I want to propose?
node/deal_protocol.go
Outdated
pdata, err := abi.EncodeValues(vals) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to encode abi values") | ||
} |
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.
you can use abi.ToEncodedValues()
instead of calling ToValues
and EncodeValues
individually
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.
That wasnt merged when I wrote this.
node/deal_protocol.go
Outdated
return &storage, nil | ||
} | ||
|
||
func (sm *StorageMarket) GetAsk(id uint64) (*core.Ask, error) { |
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.
while I understand the convenience of the current implementation, this at least needs a TODO to switch over to message sending instead of relying on being able to execute the code of the actor outside the VM
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.
This would be good c5 exercise to give some exposure to the vm, even if they are not going to be working on it. If you put a TODO you can add a c5-tagged issue to make the change.
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.
I think we always want to be able to directly access contract state for performance reasons. Things that interact with the market are going to want to do lots of interesting queries, and we don't want each request to have to spin up a whole vm just to get it.
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.
Hmmm this could get pretty complicated once the actors are written in a different language, also this means we need to make actors thread safe, as currently I am pretty sure this could get into race conditions depending on how things are wired up.
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.
What? race conditions? You take a hash of the actors memory, load it up in some way, and get a race condition how?
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.
I guess it is synced on bestblock to avoid any races
node/deal_protocol.go
Outdated
return ask, nil | ||
} | ||
|
||
func (sm *StorageMarket) GetBid(id uint64) (*core.Bid, error) { |
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.
Same as GetAsk
core/storage_market.go
Outdated
@@ -46,6 +45,9 @@ type Bid struct { | |||
//Coding ??? | |||
Owner types.Address | |||
ID uint64 | |||
|
|||
// Used indicates whether or not this bid is in use by a deal | |||
Used bool |
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.
We could store a reference to the deal here, which could make certain lookups cheaper
if err := sm.fetchData(context.TODO(), propose.Deal.DataRef); err != nil { | ||
return nil, errors.Wrap(err, "fetching data failed") | ||
} | ||
|
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.
Can you add a dummy method to store the data & mark it for sealing, so it's clear where & how to plug that part in?
node/deal_protocol.go
Outdated
) | ||
|
||
const MakeDealProtocolID = protocol.ID("/fil/deal/mk/1.0.0") | ||
const CheckDealProtocolID = protocol.ID("/fil/deal/chk/1.0.0") |
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.
Is there any benefit on the libp2p side to making it two distinct protocols? Otherwise I am having a hard time seeing the benefit of splitting them into two.
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.
Well, doing things this way makes the protocols themselves cleaner. Switching to a single protocol means we need to add extra fields to the 'request' object to tell what type of request it is (propose vs check)
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.
We should keep them separate -- they are two different things and I don't see a lot of benefit from combination...
a31d265
to
7ac5b2b
Compare
node/deal_protocol.go
Outdated
cbor.RegisterCborType(DealPropose{}) | ||
cbor.RegisterCborType(DealResponse{}) | ||
cbor.RegisterCborType(DealQuery{}) | ||
} |
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.
[I initially inadvertently first left this comment at the draft spec (https://github.com/filecoin-project/specs/pull/63). However, it was hidden there and also didn't make perfect sense outside the context of the API here.]
I think it would be a good idea to fully standardize the naming so the hypothetical verbosity is at least consistent and meaningful. However, the obvious way to do that makes the reuse of DealResponse somewhat problematic -- since we would then want (Storage)DealProposalResponse and Check(Storage)DealResponse.
If, instead, we decide that it does make sense to reuse the same type, it might make sense to name it in a way which conveys its shared function. For example, I could see using (Storage)DealStatus in both cases.
In this case, I think I prefer the current design with just changed naming (to …Status). However, I acknowledge that this potentially adds a layer of complexity to the message naming language. I think I'm comfortable with that, as long as we remain thoughtful and consistent about naming choices.
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.
To expand a little:
- generally I think we should name responses that are specific to a request as {...}Response, so FooRequest should have FooResponse.
- if a response is not scoped to a specific request then as @clkunzang says yes I think DealStatus is much more clear
- however having said that, right now it's clear that we can re-use the status but we are going to find it annoying if we want to evolve the responses for the different requests in different ways, for example if we want to enable a batch query and then the response should have a set of statuses. i think it makes sense to have separate responses for separate requests, even if they both just embed a dealstatus for now (so ProposeDealResponse and CheckDealResponse just embed DealStatus for now). in general seems like the safer and more future proof thing to do.
- BTW I think DealProposal as a noun might be more clear than DealPropose
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.
Regarding DealProposal
vs DealPropose
. There are two separate issues here. I'll illustrate by referring to DealProposal
as the noun form, and DealPropose
as the verb form.
If we want to use the verb form, then we should put the verb first and use ProposeDeal
.
Independently, we should decide if we have a global preference for noun or verb form. I have a mild preference for verb form on linguistic grounds; and a slightly stronger preference on the basis that forcing verb choice will help document the API's intention more clearly. Whatever we choose, we should be consistent.
Based on the above, my concrete suggestion would be:
ProposeDeal
->ProposeDealResponse
(embedsDealStatus
).CheckDeal
->CheckDealResponse
(embedsDealStatus
).
Then we should make sure our naming all eventually conforms with this pattern (or amend/extend it). Ideally, our naming consistency would extend to reusing verbs wherever appropriate. So if we have another pair of messages following an initiate/poll pattern, we could use <Initiate><Noun>
, <Initiate><Noun>Response
, Check<Noun>
, and Check<Noun>Response
-- embedding <Noun>Status
as a default implementation, but only if that makes sense.
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.
I'm not sure naming the types after the action makes a lot of sense to me. The operations are the verb, the object is the concrete thing.
Thats kinda like calling my wallet the SpendMoney.
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.
You're right. So noun forms for structs, verb forms for handlers, and try to have the correspondences be as consistent as possible -- so ProposeDeal
and DealProposal
, etc.
Does that make sense?
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.
Yeah, that makes the most sense to me
node/deal_protocol.go
Outdated
} | ||
} | ||
|
||
func (sm *StorageMarket) CheckDeal(id [32]byte) (*DealResponse, error) { |
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.
Would be easier to understand if either Check or Query and were consistent with it. Right now we have a DealQuery which executes CheckDeal. Let's pick Check or Query and stick with it?
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.
This is one of those "implement it both ways and hope someone has an opinion on which way is right" sort of things.
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.
I guess i'll pick Query?
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.
Or just say what it means: GetStatus? If not that then just to be obtuse I pick Check.
node/deal_protocol.go
Outdated
cbor.RegisterCborType(DealPropose{}) | ||
cbor.RegisterCborType(DealResponse{}) | ||
cbor.RegisterCborType(DealQuery{}) | ||
} |
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.
To expand a little:
- generally I think we should name responses that are specific to a request as {...}Response, so FooRequest should have FooResponse.
- if a response is not scoped to a specific request then as @clkunzang says yes I think DealStatus is much more clear
- however having said that, right now it's clear that we can re-use the status but we are going to find it annoying if we want to evolve the responses for the different requests in different ways, for example if we want to enable a batch query and then the response should have a set of statuses. i think it makes sense to have separate responses for separate requests, even if they both just embed a dealstatus for now (so ProposeDealResponse and CheckDealResponse just embed DealStatus for now). in general seems like the safer and more future proof thing to do.
- BTW I think DealProposal as a noun might be more clear than DealPropose
node/deal_protocol.go
Outdated
) | ||
|
||
const MakeDealProtocolID = protocol.ID("/fil/deal/mk/1.0.0") | ||
const CheckDealProtocolID = protocol.ID("/fil/deal/chk/1.0.0") |
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.
We should keep them separate -- they are two different things and I don't see a lot of benefit from combination...
node/deal_protocol.go
Outdated
// Rejected, the deal was rejected for some reason | ||
Rejected | ||
|
||
// Accepted, the deal was accepted but hasnt yet started |
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.
Can we define what "started" means -- does it mean a transfer is in progress? Below it says "started, the deal has started and the transfer is in progress" -- are those two different things?
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.
I guess capitalizing the 'S' on started in this comment might make it more clear, but yes. Started means we're actively fetching the data. Accepted means we intend to accept this deal, but we're either busy, or havent gotten to it yet.
I think its a valuable distinction, but maybe they should be smashed into one?
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.
Could be valuable I guess. AttemptingTransfer? Don't feel strongly.
node/deal_protocol.go
Outdated
Started | ||
|
||
// Failed, the deal has failed for some reason | ||
Failed |
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.
Will we be able to distinguish why it failed at some point?
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.
Thats what the message is for
node/deal_protocol.go
Outdated
cbor.RegisterCborType(DealQuery{}) | ||
} | ||
|
||
type DealPropose struct { |
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.
Probably worth a comment that says these are made by customers, not miners.
node/deal_protocol.go
Outdated
if ok { | ||
// TODO: maybe return current negotiation status instead? | ||
return &DealResponse{ | ||
Message: "deal negotiation already in progress", |
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.
This is maybe an argument to separate the query response from propose deal response: because here we respond with rejected, but if they query they get an in progress status. If we just have one return type it's less clear that they have different semantics.
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.
I'm actually thinking it should go the opposite direction. I think this should return whatever the current state of the existing negotiation is, but keep the message as is. It makes these calls a bit more idempotent.
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.
that sounds good
node/deal_protocol.go
Outdated
return &storage, nil | ||
} | ||
|
||
func (sm *StorageMarket) GetAsk(id uint64) (*core.Ask, error) { |
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.
This would be good c5 exercise to give some exposure to the vm, even if they are not going to be working on it. If you put a TODO you can add a c5-tagged issue to make the change.
cf8173a
to
066f3fe
Compare
b2b3d7d
to
27d06c0
Compare
69da689
to
e6c9244
Compare
node/deal_protocol.go
Outdated
inet "gx/ipfs/QmXfkENeeBvh3zYA51MaSdGUdBjhQ99cP5WQe8zgr6wchG/go-libp2p-net" | ||
"gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol" | ||
"gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid" | ||
|
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.
intentional spacing?
node/deal_protocol.go
Outdated
// creator of the proposal to select a bid and an ask and turn that into a | ||
// deal, add a reference to the data they want stored to the deal, then add | ||
// their signature over the deal. | ||
type DealPropose struct { |
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.
OK, we're going with nouns and not a separate response for the separate protocol.
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.
Yeah... Having separate types felt a bit weird to me. It seemed to encourage treating the response as logically different things, when they are really the same.
The ProposeDeal operation could really not return anything, and just require a followup Query call, and have the same effect.
Also, in the future, we're going to want the miner to be able to manually review proposals, or plug the decision making into a separate process. Given that, we might not even be able to say "Accepted" as a response from the ProposeDeal call.
node/deal_protocol.go
Outdated
if err != nil { | ||
s.Reset() | ||
// TODO: metrics, more structured logging. This is fairly useful information | ||
log.Infof("incoming deal proposal failed: %s", err) |
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.
incoming query deal failed
|
||
ask, ok := stor.Orderbook.Asks[id] | ||
if !ok { | ||
return nil, fmt.Errorf("no such ask") |
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.
Is it a little bit funny to call this an actor when how it's used is dissimilar to "regular" actors that can be instantiated in called in the vm? For example when reading the code as it were an actor code my immediate reaction is hey the return signature is wrong. I admit I don't know what else to call it. Ask: tell me whether you think the name actor really works here.
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.
(And to be clear if there is an ask along with an approval I'm happy to take an answer after merge or in slack or whatever)
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.
yeah... I really struggled with the naming here. Any ideas?
c4261ae
ab99d7e
to
665823a
Compare
665823a
to
575da6b
Compare
Fixes #28. |
The previous design was pretty poor. No nice way of resumption, expected a channel to stay open during the entire transfer, had poorly defined states and no story for multiple transfers saturating bandwidth.
This should be a little better. I'm torn on whether it should be a single libp2p protocol, or two as implemented here. But, probably good to get some feedback before thinking about it too much more.