[CO-324] Accounts per-field endpoints#3210
Conversation
Ideally, we want the /.../account/addresses to be paginated, otherwise there's no real value in isolating it as such. Also, for now, the implementation is rather heavy and requires the full account to be retrieved from the DB. On the long-term, we could imagine fetching only the addresses from the DB and streaming the response.
This is not 'ideal' yet because we are sorting and filtering on a plain list. Instead, we could have these operation at a DB-level, but this won't probably be achieved with acid-state. Perhaps if one day we switch to SQL
There was no integration tests for any accounts endpoints :s ... We should work on that in the future as I didn't take care of it in this commit / task, but solely add a few one for the newly added endpoints
| IndexToQueryParam Wallet (V1 Core.Timestamp) = "created_at" | ||
|
|
||
| IndexToQueryParam WalletAddress (V1 Core.Address) = "id" | ||
| IndexToQueryParam WalletAddress (V1 Core.Address) = "address" |
There was a problem hiding this comment.
man, that does feel really weird. i agree that this is an improvement, but this is confusing.
| newtype AccountBalance = AccountBalance | ||
| { acbAmount :: V1 Core.Coin | ||
| } deriving (Show, Ord, Eq, Generic) | ||
|
|
There was a problem hiding this comment.
This is really open to debate. This will preserve a similar structure than the one used for account. For instance, retrieving the account balance will return a JSON with:
{
"status": "success",
"data": {
"amount": 14
},
"metadata": "..."
}Another option would be to remove one level of nesting and put the value directly under data:
{
"status": "success",
"data": 14,
"metadata": "..."
}I am really open to discuss this.
There was a problem hiding this comment.
What is the argument for the flattened one? I have no idea how this would affect usage in various client libraries, maybe there is some specific argument for either one. I wouldn't instinctively argue against the nested approach though.
There was a problem hiding this comment.
It's a matter of perspective. I believe that the first approach allows you to re-use parser and structures defined in client code by keeping things roughly the same.
The second approach is more "correct" if we think of the request as a question, and data the answer to that question.
-
"What is this account's balance?"
-
"14"
Though, I agree, it's really a matter of preferences here. I just wanted to query your opinion(s).
There was a problem hiding this comment.
Could one enforce the "keeping things roughly the same" part? When first seeing this PR I came to think about lenses and GraphQL…
There was a problem hiding this comment.
I always thought about data as a container of, well, data, so it would feel unnatural to me to go from data :: Object to data :: Value, if you see what you mean. I suspect I lean towards symmetry and consistency, and as such having data always index a JSON object seems like the best approach, to me, but of course, that's just my opinion 😉
There was a problem hiding this comment.
I agree that consistency and convention are important (especially without types), and right now, as far as JS is concerned, we've got a type like { data :: Object }. Deviating from that would a potential source of runtime errors.
Also, this rename the balance endpoint to '/api/v1/wallets/{id}/accounts/{ix}/amount' to reflect the actual name of
the field in the Account representation.
adinapoli-iohk
left a comment
There was a problem hiding this comment.
Two minor quibbles , but I think the "meat" is there -- tres bien ![]()
| :> Summary "Retrieve only account's addressees." | ||
| :> WalletRequestParams | ||
| :> FilterBy '[V1 Core.Address] WalletAddress | ||
| :> SortBy '[V1 Core.Address] WalletAddress |
There was a problem hiding this comment.
Does sorting on address really make sense? They are just a bunch of base58-encoded strings after all, so I'm wondering whether or not we should allow sorting, as it buys virtually nothing 😉
There was a problem hiding this comment.
It allows pagination based on an indexed part of the datatype, which makes for super efficient querying.
There was a problem hiding this comment.
It allows pagination based on an indexed part of the datatype, which makes for super efficient querying.
@parsonsmatt @KtorZ Isn't what FilterBy buys you already? Unless I am mistaken, note that the type-level sorting & filtering has no effect on the (already-defined) IxSet indices. Once those are defined (as in instance Indexable ...) which one you use for filter & sort (or the lack thereof) really doesn't undermine querying efficiency 😉
Imho keeping SortBy here is a mistake, as it would encourage exchanges to sort by addresses, which is an operation terribly wasteful, as there is no meaningful ordering for addresses, as the lexicographical order doesn't really help here. My 2 cents anyway, very happy to be proved wrong! 😀
There was a problem hiding this comment.
In a way, pagination without ordering doesn't make much sense to me, does it? Do we even have guarantees with the acid-state db that we will yield the same elements from two successive get queries? Or is it completely non-deterministic on non-indexed items?
Though, I'd rather follow your intuition on that @adinapoli-iohk. Note that the PR has been merged in a feature-branch and not yet to develop, so there's still time to do some minor adjusments!
There was a problem hiding this comment.
Haven't followed the details here but I agree that for pagination it is important to have an indexed attribute available to sort on. Not sure how much control we need to give the user over this, since as @adinapoli-iohk points out, sorting on base58 strings isn't particularly meaningful. Moreover, in the new wallet we clearly distinguish between the primary key and the rest of the data structure and don't require Ord instances for the whole type; in this case, the natural way to sort by "address" would be to sort on its key (which is basically just the pair of integers telling it how that address is derived from the root). So I think it's important that "sort by address" doesn't necessarily mean "sort lexicographically", but rather "give me some kind of pagination I can depend on" -- so that we are free to switch over the sorting to the primary key once we move to the new wallet, rather than being forced to still support this lexicographical sort.
There was a problem hiding this comment.
In a way, pagination without ordering doesn't make much sense to me, does it?
Indeed, but I think we should be fine in this case, as in the current implementation we simply call IxSet.toList which, AFAIK, uses descending ordering, which is a reasonable default, I believe.
My main gripe was that, if we leave that SortBy as-it-is, we are really exposing sort_by=address[DES|ASC] to the API, which means exchanges could enforce an explicit sorting on the addresses, which, considering the amount of addresses an exchanges has, I would prefer to not pay the explicit price, if possible 😀
Am I being mental? 😛
There was a problem hiding this comment.
I usually say: If users can do something, eventually they will. I wonder if we shouldn't indeed remove that sorting for now, until we have something more meaningful we can actually sort on.
There was a problem hiding this comment.
If users can do something, eventually they will.
Aye, that is usually my experience -- that's why I have flagged this up 😉
There was a problem hiding this comment.
NOTE: Sorting was removed on the main story branch
| $readAccounts | ||
| ``` | ||
|
|
||
| Partial Representations |
There was a problem hiding this comment.
Good lad for adding documentation! 😻
|
|
||
| -- | Datatype wrapping balance for per-field endpoint | ||
| newtype AccountBalance = AccountBalance | ||
| { acbAmount :: V1 Core.Coin |
There was a problem hiding this comment.
Bikeshedding -- acbAmount or acbBalance? 🚴
I suspect the latter reads better in the output JSON -- what do you think?
There was a problem hiding this comment.
Weak vote for acbBalance but I think both are fine
There was a problem hiding this comment.
🙃 ... I went for acbBalance at first, because it reads better. Then I notice the fireld was actually named amount in the Account representation. Therefore, I changed for acbAmount as well for consistency.
| (expectExactlyAddresses [addr]) | ||
| , PaginationTest (Just 2) (Just 5) (filterByAddress addr) NoSorts | ||
| (expectExactlyAddresses []) | ||
| ] |
| IndexToQueryParam Wallet (V1 Core.Timestamp) = "created_at" | ||
|
|
||
| IndexToQueryParam WalletAddress (V1 Core.Address) = "id" | ||
| IndexToQueryParam WalletAddress (V1 Core.Address) = "address" |
There was a problem hiding this comment.
man, that does feel really weird. i agree that this is an improvement, but this is confusing.
| :> Summary "Retrieve only account's addressees." | ||
| :> WalletRequestParams | ||
| :> FilterBy '[V1 Core.Address] WalletAddress | ||
| :> SortBy '[V1 Core.Address] WalletAddress |
There was a problem hiding this comment.
It allows pagination based on an indexed part of the datatype, which makes for super efficient querying.
| -> m (WalletResponse AccountAddresses) | ||
| getAccountAddresses wId accIdx pagination filters sorts = do | ||
| resp <- respondWith pagination filters sorts (getAddresses <$> getAccount wId accIdx) | ||
| return resp { wrData = AccountAddresses . wrData $ resp } |
There was a problem hiding this comment.
is WalletResponse a functor/foldable? it totally should be!
| readAddresses = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Address]) | ||
| readFees = decodeUtf8 $ encodePretty $ genExample @(WalletResponse EstimatedFees) | ||
| readNodeInfo = decodeUtf8 $ encodePretty $ genExample @(WalletResponse NodeInfo) | ||
| readTransactions = decodeUtf8 $ encodePretty $ genExample @(WalletResponse [Transaction]) |
There was a problem hiding this comment.
rather than large whitespace changes like this i would prefer an indentation to make the start point equal for each line. minor quibble :)
There was a problem hiding this comment.
And I do agree with you!
|
|
||
| -- | Datatype wrapping balance for per-field endpoint | ||
| newtype AccountBalance = AccountBalance | ||
| { acbAmount :: V1 Core.Coin |
There was a problem hiding this comment.
Weak vote for acbBalance but I think both are fine
|
|
||
| instance Arbitrary AccountAddresses where | ||
| arbitrary = | ||
| AccountAddresses <$> arbitrary |
There was a problem hiding this comment.
with deriving strategies, this is deriving newtype (Arbitrary) 😄
There was a problem hiding this comment.
I am always (without any good reasons) afraid of deriving newtypes automagically :p ...
…endpoints [CO-324] Accounts per-field endpoints
…endpoints [CO-324] Accounts per-field endpoints
…endpoints [CO-324] Accounts per-field endpoints
…hk/KtorZ/CO-324/per-field-endpoints [CO-324] Accounts per-field endpoints

Description
There are needs for exchanges to retrieve only the balance of a given account. A call to `/api/v1/wallets/{walletId}/accounts/{accountId} already contains this field but is unpractical for exchanges since they usually have accounts with MANY addresses, resulting in a huge response from the API.
Linked issue
CO-324
Type of change
Developer checklist
Testing checklist
QA Steps
Screenshots (if available)