-
Notifications
You must be signed in to change notification settings - Fork 179
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
[v2] disperser client payments api #928
Conversation
1ae3e3c
to
8ea4f03
Compare
0b944cf
to
9982bf1
Compare
api/clients/disperser_client_v2.go
Outdated
@@ -60,7 +60,7 @@ var _ DisperserClientV2 = &disperserClientV2{} | |||
// | |||
// // Subsequent calls will use the existing connection | |||
// status2, blobKey2, err := client.DisperseBlob(ctx, data, blobHeader) | |||
func NewDisperserClientV2(config *DisperserClientV2Config, signer corev2.BlobRequestSigner, prover encoding.Prover) (*disperserClientV2, error) { | |||
func NewDisperserClientV2(config *DisperserClientV2Config, signer corev2.BlobRequestSigner, prover encoding.Prover, accountant Accountant) (*disperserClientV2, 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.
It's quite difficult to construct a new Accountant. Does it make sense to provide a helper method that creates an accountant from the GetPaymentState
result?
Let's also add a way to call GetPaymentState
in this client
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.
added a helper function PopulateAccountant
that should run after creating NewDisperserClientV2
by constructing accountant from GetPaymentState
What do you think about allowing the user of DisperserClientV2 to provide their own accountant, or simply passing in &accountant{}
to NewDisperserClientV2
and then explicitly calling PopulateAccountant
to fill in the accountant state? (5004cf9)
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.
Instead of directly mutating the client, why don't we make PopulateAccountant
a helper method that returns Accountant
by calling GetPaymentState
so that the caller can pass that in to NewDisperserClientV2
?
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.
The reason is GetPaymentState
is a function of disperserClientV2
. If we write a standalone PopulateAccountant
, then we would need it to be able to initOnceGrpcConnection
, pass in a signer for creating the request, and a disperser_rpc.DisperserClient
that makes the actual request. I figured it is cleaner to just use disperserClientV2
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.
Are there any use cases where a use of disperser_client_v2 would want to pass in its own accountant? If not, then how about making populateAccountant() private and calling it from within the constructor? Feels like disperser_client and accountant are too intertwined. Are there any use cases for using an accountant without a disperser? If not, let's just hide the populateAccountant and make the disperser_client more RAII-like.
0f1ddcc
to
5004cf9
Compare
@@ -109,15 +121,11 @@ func (c *disperserClientV2) DisperseBlob( | |||
return nil, [32]byte{}, api.NewErrorInternal("uninitialized signer for authenticated dispersal") | |||
} | |||
|
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.
check if c.accountant
is nil?
we can instruct how to initialize accountant in the error message
api/clients/disperser_client_v2.go
Outdated
// conn and client are initialized lazily | ||
}, nil | ||
} | ||
|
||
// PopulateAccountant populates the accountant with the payment state from the disperser. | ||
// This function is required to be called before using the accountant. Perhaps rename to Start()? |
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 function isn't required if a valid accountant is passed in the constructor, right?
If you want to require this method, we can remove accountant as an argument from the constructor.
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.
Oh yeah, will update the comment
api/clients/accountant.go
Outdated
@@ -160,6 +159,37 @@ func (a *accountant) GetRelativeBinRecord(index uint32) *BinRecord { | |||
return &a.binRecords[relativeIndex] | |||
} | |||
|
|||
func (a *accountant) SetPaymentState(paymentState *disperser_rpc.GetPaymentStateReply) { | |||
a.minNumSymbols = uint32(paymentState.PaymentGlobalParams.MinNumSymbols) |
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.
these will panic if any of the attributes are nil?
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.
yes.... added a check for all of them!
It would've been great if protobuf let me set fields to be required, but proto3 removed "required" keyword:(
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.
If you use getters (i.e. GetXXX
), you could chain them!
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.
lgtm, lets address the lint issue
api/clients/disperser_client_v2.go
Outdated
@@ -60,7 +60,7 @@ var _ DisperserClientV2 = &disperserClientV2{} | |||
// | |||
// // Subsequent calls will use the existing connection | |||
// status2, blobKey2, err := client.DisperseBlob(ctx, data, blobHeader) | |||
func NewDisperserClientV2(config *DisperserClientV2Config, signer corev2.BlobRequestSigner, prover encoding.Prover) (*disperserClientV2, error) { | |||
func NewDisperserClientV2(config *DisperserClientV2Config, signer corev2.BlobRequestSigner, prover encoding.Prover, accountant Accountant) (*disperserClientV2, 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.
Are there any use cases where a use of disperser_client_v2 would want to pass in its own accountant? If not, then how about making populateAccountant() private and calling it from within the constructor? Feels like disperser_client and accountant are too intertwined. Are there any use cases for using an accountant without a disperser? If not, let's just hide the populateAccountant and make the disperser_client more RAII-like.
api/clients/accountant.go
Outdated
@@ -160,6 +159,59 @@ func (a *accountant) GetRelativeBinRecord(index uint32) *BinRecord { | |||
return &a.binRecords[relativeIndex] | |||
} | |||
|
|||
func (a *accountant) SetPaymentState(paymentState *disperser_rpc.GetPaymentStateReply) 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 feels too coupled to me. A Public setter that is not part of the interface and it set by some response from another client. As discussed in other threads, could we not combine accountant and disperser_client more smoothly? Does accountant really need to be an independent 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.
We wanted to keep accounting flow separate from the client, but arguably we can combine them into a bigger struct. What do you think @ian-shim ?
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 this would improve the UX quite a bit. You cannot disperse a blob without payment metadata and you probably don't need accountant functionality other than for the purpose of dispersing a blob, so coupling them more explicitly makes sense imo
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.
Makes sense! I would also not try to future-proof this design too much (to a case where there are multiple dispersers and you need some super complex accountant that manages multiple dispersers). We should try to make the simple case simple, true to golang's philosophy, and people who need complex integrations can take on the complexity hit around our simple API.
Another possibility is to have 2 constructors: one where we inject the accountant, and the other that initializes the accountant by calling the disperser?
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.
Hmm all one needs to do is to call the current constructor and then call PopulateAccountant
, so adding a separate constructor doesn't seem that beneficial to me. I won't mind adding it, probably in the follow-up PR that focus on integrations adaptions (maybe something like NewDisperserClientV2AutoPay
?
@@ -108,16 +123,15 @@ func (c *disperserClientV2) DisperseBlob( | |||
if c.signer == nil { | |||
return nil, [32]byte{}, api.NewErrorInternal("uninitialized signer for authenticated dispersal") | |||
} | |||
if c.accountant == nil { | |||
return nil, [32]byte{}, api.NewErrorInternal("uninitialized accountant for paid dispersal; make sure to call PopulateAccountant after creating the client") |
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.
how about we make the call to PopulateAccountant as part of the initOnce function? This way its hidden from the user.
Is there ever a need to recall PopulateAccountant? For eg if disperser went down, or whatever other weird case? To reset the accountant basically? If so, should it be called ResetAccountantFromServer
or something like that?
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.
In this case, maybe we also move signer check to the initOnce?
There's some cases a user might recall populate accountant, I don't think it hurts to be able to reset. Can update the function name for sure
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 I'll leave this as a future refactoring
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'll give a stamp on this in case its blocking progress on other work, but I would have liked to get @ian-shim's take on #928 (comment). Would really like to find a better interface/decoupling between these. But we can do so later (hopefully...)
Imma merge:) linking PR for any follow-ups |
Why are these changes needed?
properly construct payment header in the dispersal request
For easy initialization of DisperserClient, pass in an empty accountant
&accountant{}
and then callclient.PopulateAccountant(ctx)
to populate the accountant by getting payment information both on-chain and off-chain for the local signer.For more customized initialization, the user can explicitly construct their accountant and pass in as the argument
Checks