-
Notifications
You must be signed in to change notification settings - Fork 16
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
Introduce AccountFetcher to common-sdk #45
Conversation
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 not defining an interface for AccountFetchOpts, but rather AccountCache<T, AccountFetchOpts>
?
Since it is an interface definition, it can be left to the implementer to decide whether or not to use TTL.
I think refreshAll was used quite infrequently even in AccountFetcher.
So I wonder It is not necessary to define it in the interface.
I would like to have a way to retrieve multiple types of accounts at once, as shown below, since a single RPC call can retrieve consistent data.
However, this may cause some accounts to be updated more recently later, so it would be better to define this as the same function as getMultipleAccountsInMap, etc., rather than a cache layer. WDYT ?
addresses = [[a1, a2, a3], [a4, a5, a6], [a7]]
parsers = [[ParsableWhirlpool, ParsableTickArray, ParsableTickArray], ...]
Thanks @yugure-orca. I've made the following amendments:
I think the ability to fetch multiple types at once is a reasonable responsibility for this class. Of course, since this class depends on the account-request utilities layer, that layer would have to support it as well. Can you elaborate more on the "some accounts may be updated more recently"? I'd imagine if only accounts that exceeds the Also, I think given this PR is more for porting and no new feature depends on the |
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.
Thanks for the update.
Yeah "Fetcher" sounds good to me, too.
Also, I think given this PR is more for porting and no new feature depends on the get multiple types in 1 request just yet, I think that new feature should be left in a separate PR. what do you think?
Yep, agreed. Separate PR is comfortable.🙂
Can you elaborate more on the "some accounts may be updated more recently"?
The consistency aspect is more troublesome than the multiple types aspect.
assuming:
- fetchSize = 10 (not 100)
- addresses = [[A, B, C, D], [E, F], [G], [A, H, I, J], [B, K], [C]]
We need to fetch each sub-array consistently and it means we need to fetch them in a single RPC call.
So we will divide the addresses into 2 getMultipleAccountInfo (gMA) RPC call.
- 1st gMA ([[A, B, C, D], [E, F], [G]].flat())
- 2nd gMA ([[A, H, I, J], [B, K], [C]].flat())
If all of these calls are responded based on the same context slot, it is absolutely consistent.
But if 2nd gMA is responded based on a newer slot than 1st gMA, we need to handle duplicated addresses (A, B, C) correctly.
As for the cache hit decision, whether [A, B, C, D] is a cache hit or not is also a case where all [A, B, C, D] exist in the cache and the results are obtained from the same slot.
// Filter out all unexpired accounts to get the accounts to fetch | ||
const undefinedAccounts = addressStrs.filter((addressStr) => { | ||
const cached = this.cache.get(addressStr); | ||
const elapsed = cached ? now - (cached?.fetchedAt ?? 0) : Number.NEGATIVE_INFINITY; |
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.
super nit: in getAccount
, it is !!cached
. (I think both works well)
gotcha. Yeah that edge case would be an issue even for the current getAccounts calls. I'll leave a comment in the code. An efficient solution is also not exactly clear if we must enforce all gMA calls to be returned on a consistent slot. But the good thing is this use-case isn't present (yet) for the UI, so we still have some time to figure it out. |
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.
Nice this will be great, thanks for doing this.
Just one main comment about forcing refetches - not sure if that's something the fetcher implementation is intended to support.
opts?: SimpleAccountFetchOptions | undefined, | ||
now: number = Date.now() | ||
) { | ||
const addressStrs = AddressUtil.toStrings(addresses); |
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.
nit: since this is in a private method, we can probably use PublicKey[]
as the type for addresses
and skip this transformation and ensure we do the transform at the caller
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 transformation is conditional and the transformation is pretty slow. If we force the transformation back to PublicKey, then the getAccounts methods will have to perform the transformation twice, which can be quite costly
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.
Ah yes gotcha, makes sense since it's just transforming everything to strings
maxAge?: number; | ||
}; | ||
|
||
// SimpleAccountCache is a simple implementation of AccountCache that stores the fetched |
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.
nit: update comments
result.set(addressStr, value); | ||
}); | ||
|
||
// invariant(result.size === addresses.length, "not enough results fetched"); |
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.
nit: should we keep this?
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.
ah yes. remvoed
const addressStr = AddressUtil.toString(address); | ||
|
||
const cached = this.cache.get(addressStr); | ||
const maxAge = opts?.maxAge ?? this.retentionPolicy.get(parser) ?? Number.POSITIVE_INFINITY; |
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 force refetch, I would be tempted to try and pass in maxAge = 0
. I believe this line would end up using the retention policy, which could be confusing. Perhaps change to explicitly checking if maxAge
is undefined 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.
Also the case where retentionPolicy has a zero value. Should that make the fetcher always fetch latest data?
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 good catch. upgraded the getMaxAge fn and added tests
AccountFetcher
to allow easy fetching / caching of on-chain accountsVerification