Skip to content
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(solana): cached solana provider #905

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Reinis-FRP
Copy link
Contributor

This implements similar cached provider logic as src/providers/cachedProvider.ts, but for Solana.

Also adds missing tests for the rate limited Solana provider

Fixes: https://linear.app/uma/issue/ACX-3741/solana-cached-provider

Signed-off-by: Reinis Martinsons <[email protected]>
Signed-off-by: Reinis Martinsons <[email protected]>
Signed-off-by: Reinis Martinsons <[email protected]>
Copy link

linear bot commented Feb 27, 2025

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently this is used in tests only

@@ -13,7 +13,7 @@ export class RateLimitedSolanaRpcFactory extends SolanaClusterRpcFactory {
private queue: QueueObject<SolanaRateLimitTask>;

// Holds the underlying transport that the rate limiter wraps.
private readonly defaultTransport: RpcTransport;
protected defaultTransport: RpcTransport;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had to change these for being able to use in mocks

*/
export function jsonReplacerWithBigInts(_key: string, value: unknown): unknown {
if (typeof value === "bigint") {
return { type: "BigInt", value: value.toString() };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative would be to encode this as decimal strings followed by n character which would take bit less space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I favour storing them as strings 👍. Ultimately I wonder if we'll just use BigInt everywhere, in which case we could unconditionally convert strings as numbers back to BigInt.

@Reinis-FRP Reinis-FRP marked this pull request as ready for review February 27, 2025 16:46
Comment on lines +73 to +79
const getSignatureStatusesResponse = await this.rateLimitedRpcClient
.getSignatureStatuses([params[0]], {
searchTransactionHistory: true,
})
.send();

const getTransactionResponse = await this.rateLimitedTransport<TResponse>(...args);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we run getSignatureStatuses and this.rateLimitedTransport in parallel using Promise.all(...) to reduce sequential waits?

// Cache the transaction only if it is finalized.
if (getSignatureStatusesResponse.value[0]?.confirmationStatus === "finalized") {
const redisKey = this.buildRedisKey(method, params);
await this.redisClient?.set(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is no need to await here since it won't affect the returned value

const { method, params } = args[0].payload as { method: string; params?: unknown[] };

// Only handles getTransaction right now.
if (method === "getTransaction") {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You could use an early return if (method !== "getTransaction") return this.rateLimitedTransport<TResponse>(...args);
So we don't need the else at the end and improve readability

Comment on lines +45 to +60
if (cacheType !== CacheType.NONE) {
const redisKey = this.buildRedisKey(method, params);

// Attempt to pull the result from the cache.
const redisResult = await this.redisClient?.get<string>(redisKey);

// If cache has the result, parse the json and return it.
if (redisResult) {
return JSON.parse(redisResult, jsonReviverWithBigInts);
}

// Cache does not have the result. Query it directly and cache it if finalized.
return this.requestAndCacheFinalized<TResponse>(...args);
}

return this.rateLimitedTransport<TResponse>(...args);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor refactor that Nick figured out with the EVM implementation:

Suggested change
if (cacheType !== CacheType.NONE) {
const redisKey = this.buildRedisKey(method, params);
// Attempt to pull the result from the cache.
const redisResult = await this.redisClient?.get<string>(redisKey);
// If cache has the result, parse the json and return it.
if (redisResult) {
return JSON.parse(redisResult, jsonReviverWithBigInts);
}
// Cache does not have the result. Query it directly and cache it if finalized.
return this.requestAndCacheFinalized<TResponse>(...args);
}
return this.rateLimitedTransport<TResponse>(...args);
if (cacheType === CacheType.NONE) {
return this.rateLimitedTransport<TResponse>(...args);
}
const redisKey = this.buildRedisKey(method, params);
// Attempt to pull the result from the cache.
const redisResult = await this.redisClient?.get<string>(redisKey);
// If cache has the result, parse the json and return it.
if (redisResult) {
return JSON.parse(redisResult, jsonReviverWithBigInts);
}
// Cache does not have the result. Query it directly and cache it if finalized.
return this.requestAndCacheFinalized<TResponse>(...args);

Comment on lines +108 to +114
typeof value === "object" &&
value !== null &&
"type" in value &&
"value" in value &&
value.type === "BigInt" &&
typeof value.value === "string" &&
/^-?\d+$/.test(value.value) // Ensure it's a valid BigInt value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooc, could superstruct help here ?

Copy link
Contributor

@pxrl pxrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a couple of minor comments from me - this looks pretty good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants