Skip to content

Commit 094557d

Browse files
committed
refactor(solana): Improve RPC error detection
RPC errors must also be evaluated at the caching/aggregator layer to avoid rotating to another provider in the event that the first provider fails. Some failures cannot be resolved and need to be aborted immediately.
1 parent ce288ec commit 094557d

File tree

4 files changed

+39
-31
lines changed

4 files changed

+39
-31
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@across-protocol/sdk",
33
"author": "UMA Team",
4-
"version": "4.3.69",
4+
"version": "4.3.70",
55
"license": "AGPL-3.0",
66
"homepage": "https://docs.across.to/reference/sdk",
77
"files": [

src/providers/solana/quorumFallbackRpcFactory.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { Logger } from "winston";
12
import { RpcFromTransport, RpcResponse, RpcTransport, SolanaRpcApiFromTransport } from "@solana/kit";
2-
import { CachedSolanaRpcFactory } from "./cachedRpcFactory";
3-
import { SolanaBaseRpcFactory, SolanaClusterRpcFactory } from "./baseRpcFactories";
43
import { isPromiseFulfilled, isPromiseRejected } from "../../utils/TypeGuards";
54
import { compareSvmRpcResults, createSendErrorWithMessage } from "../utils";
6-
import { Logger } from "winston";
5+
import { CachedSolanaRpcFactory } from "./cachedRpcFactory";
6+
import { SolanaBaseRpcFactory, SolanaClusterRpcFactory } from "./baseRpcFactories";
7+
import { shouldFailImmediate } from "./utils";
78

89
// This factory stores multiple Cached RPC factories so that users of this factory can specify multiple RPC providers
910
// and the factory will fallback through them if any RPC calls fail. This factory also implements quorum logic amongst
@@ -64,6 +65,11 @@ export class QuorumFallbackSolanaRpcFactory extends SolanaBaseRpcFactory {
6465
throw error;
6566
}
6667

68+
// If one RPC provider reverted, others likely will too. Skip them.
69+
if (quorumThreshold === 1 && shouldFailImmediate(method, error)) {
70+
throw error;
71+
}
72+
6773
const currentFactory = factory.rpcFactory.clusterUrl;
6874
const nextFactory = fallbackFactories.shift()!;
6975
this.logger.debug({

src/providers/solana/retryRpcFactory.ts

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import { Logger } from "winston";
12
import { RpcTransport, SOLANA_ERROR__RPC__TRANSPORT_HTTP_ERROR } from "@solana/kit";
23
import { getThrowSolanaErrorResponseTransformer } from "@solana/rpc-transformers";
4+
import { isSolanaError } from "../../arch/svm";
5+
import { delay } from "../../utils";
36
import { SolanaClusterRpcFactory } from "./baseRpcFactories";
47
import { RateLimitedSolanaRpcFactory } from "./rateLimitedRpcFactory";
5-
import { isSolanaError, SVM_SLOT_SKIPPED, SVM_LONG_TERM_STORAGE_SLOT_SKIPPED } from "../../arch/svm";
6-
import { delay } from "../../utils";
7-
import { Logger } from "winston";
8+
import { shouldFailImmediate } from "./utils";
89

910
// This factory adds retry logic on top of the RateLimitedSolanaRpcFactory.
1011
// It follows the same composition pattern as other factories in this module.
@@ -67,7 +68,7 @@ export class RetrySolanaRpcFactory extends SolanaClusterRpcFactory {
6768
getThrowSolanaErrorResponseTransformer()(response, { methodName: method, params });
6869
return response;
6970
} catch (error) {
70-
if (retryAttempt++ >= this.retries || this.shouldFailImmediate(method, error)) {
71+
if (retryAttempt++ >= this.retries || shouldFailImmediate(method, error)) {
7172
throw error;
7273
}
7374

@@ -82,29 +83,6 @@ export class RetrySolanaRpcFactory extends SolanaClusterRpcFactory {
8283
}
8384
}
8485

85-
/**
86-
* Determine whether a Solana RPC error indicates an unrecoverable error that should not be retried.
87-
* @param method RPC method name
88-
* @param error Error object from the RPC call
89-
* @returns True if the request should be aborted immediately, otherwise false
90-
*/
91-
private shouldFailImmediate(method: string, error: unknown): boolean {
92-
if (!isSolanaError(error)) {
93-
return false;
94-
}
95-
96-
// JSON-RPC errors: https://www.quicknode.com/docs/solana/error-references
97-
const { __code: code } = error.context;
98-
switch (method) {
99-
case "getBlock":
100-
case "getBlockTime":
101-
// No block at the requested slot. This may not be correct for blocks > 1 year old.
102-
return [SVM_SLOT_SKIPPED, SVM_LONG_TERM_STORAGE_SLOT_SKIPPED].includes(code);
103-
default:
104-
return false;
105-
}
106-
}
107-
10886
/**
10987
* Identify whether an error thrown was the result of an RPC provider 429 response.
11088
* @param error Error object from the RPC query.

src/providers/solana/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { RpcTransport } from "@solana/rpc-spec";
2+
import { isSolanaError, SVM_SLOT_SKIPPED, SVM_LONG_TERM_STORAGE_SLOT_SKIPPED } from "../../arch/svm";
23

34
/**
45
* This is the type we pass to define a Solana RPC request "task".
@@ -12,3 +13,26 @@ export interface SolanaRateLimitTask {
1213
resolve: (result: unknown) => void;
1314
reject: (err: unknown) => void;
1415
}
16+
17+
/**
18+
* Determine whether a Solana RPC error indicates an unrecoverable error that should not be retried.
19+
* @param method RPC method name.
20+
* @param error Error object from the RPC call.
21+
* @returns True if the request should be aborted immediately, otherwise false.
22+
*/
23+
export function shouldFailImmediate(method: string, error: unknown): boolean {
24+
if (!isSolanaError(error)) {
25+
return false;
26+
}
27+
28+
// JSON-RPC errors: https://www.quicknode.com/docs/solana/error-references
29+
const { __code: code } = error.context;
30+
switch (method) {
31+
case "getBlock":
32+
case "getBlockTime":
33+
// No block at the requested slot. This may not be correct for blocks > 1 year old.
34+
return [SVM_SLOT_SKIPPED, SVM_LONG_TERM_STORAGE_SLOT_SKIPPED].includes(code);
35+
default:
36+
return false;
37+
}
38+
}

0 commit comments

Comments
 (0)