@across-protocol/sdk 4.3.37 → 4.3.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/arch/svm/BlockUtils.d.ts +3 -1
- package/dist/cjs/arch/svm/BlockUtils.js +3 -2
- package/dist/cjs/arch/svm/BlockUtils.js.map +1 -1
- package/dist/cjs/arch/svm/SpokeUtils.d.ts +9 -8
- package/dist/cjs/arch/svm/SpokeUtils.js +106 -130
- package/dist/cjs/arch/svm/SpokeUtils.js.map +1 -1
- package/dist/cjs/arch/svm/provider.d.ts +1 -1
- package/dist/cjs/arch/svm/provider.js +2 -1
- package/dist/cjs/arch/svm/provider.js.map +1 -1
- package/dist/cjs/arch/svm/utils.d.ts +5 -2
- package/dist/cjs/arch/svm/utils.js +23 -6
- package/dist/cjs/arch/svm/utils.js.map +1 -1
- package/dist/cjs/clients/BaseAbstractClient.d.ts +3 -1
- package/dist/cjs/clients/BaseAbstractClient.js +31 -13
- package/dist/cjs/clients/BaseAbstractClient.js.map +1 -1
- package/dist/cjs/clients/BundleDataClient/BundleDataClient.js +1 -1
- package/dist/cjs/clients/BundleDataClient/BundleDataClient.js.map +1 -1
- package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js +1 -1
- package/dist/cjs/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
- package/dist/cjs/clients/SpokePoolClient/SVMSpokePoolClient.js +5 -5
- package/dist/cjs/clients/SpokePoolClient/SVMSpokePoolClient.js.map +1 -1
- package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.d.ts +1 -1
- package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.js +2 -2
- package/dist/cjs/providers/mocks/MockCachedSolanaRpcFactory.js.map +1 -1
- package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +1 -1
- package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.js +2 -2
- package/dist/cjs/providers/mocks/MockRateLimitedSolanaRpcFactory.js.map +1 -1
- package/dist/cjs/providers/mocks/MockRetrySolanaRpcFactory.d.ts +1 -1
- package/dist/cjs/providers/mocks/MockRetrySolanaRpcFactory.js +2 -2
- package/dist/cjs/providers/mocks/MockRetrySolanaRpcFactory.js.map +1 -1
- package/dist/cjs/providers/solana/baseRpcFactories.d.ts +3 -3
- package/dist/cjs/providers/solana/baseRpcFactories.js +4 -8
- package/dist/cjs/providers/solana/baseRpcFactories.js.map +1 -1
- package/dist/cjs/providers/solana/cachedRpcFactory.js.map +1 -1
- package/dist/cjs/providers/solana/index.d.ts +1 -0
- package/dist/cjs/providers/solana/index.js +1 -0
- package/dist/cjs/providers/solana/index.js.map +1 -1
- package/dist/cjs/providers/solana/quorumFallbackRpcFactory.d.ts +16 -0
- package/dist/cjs/providers/solana/quorumFallbackRpcFactory.js +208 -0
- package/dist/cjs/providers/solana/quorumFallbackRpcFactory.js.map +1 -0
- package/dist/cjs/providers/utils.d.ts +1 -0
- package/dist/cjs/providers/utils.js +5 -1
- package/dist/cjs/providers/utils.js.map +1 -1
- package/dist/cjs/utils/BlockExplorerUtils.js +1 -1
- package/dist/cjs/utils/BlockExplorerUtils.js.map +1 -1
- package/dist/esm/arch/svm/BlockUtils.d.ts +3 -1
- package/dist/esm/arch/svm/BlockUtils.js +3 -2
- package/dist/esm/arch/svm/BlockUtils.js.map +1 -1
- package/dist/esm/arch/svm/SpokeUtils.d.ts +9 -8
- package/dist/esm/arch/svm/SpokeUtils.js +109 -131
- package/dist/esm/arch/svm/SpokeUtils.js.map +1 -1
- package/dist/esm/arch/svm/provider.d.ts +1 -1
- package/dist/esm/arch/svm/provider.js +1 -1
- package/dist/esm/arch/svm/provider.js.map +1 -1
- package/dist/esm/arch/svm/utils.d.ts +11 -2
- package/dist/esm/arch/svm/utils.js +28 -6
- package/dist/esm/arch/svm/utils.js.map +1 -1
- package/dist/esm/clients/BaseAbstractClient.d.ts +3 -1
- package/dist/esm/clients/BaseAbstractClient.js +31 -13
- package/dist/esm/clients/BaseAbstractClient.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/BundleDataClient.js +1 -1
- package/dist/esm/clients/BundleDataClient/BundleDataClient.js.map +1 -1
- package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js +1 -1
- package/dist/esm/clients/BundleDataClient/utils/PoolRebalanceUtils.js.map +1 -1
- package/dist/esm/clients/SpokePoolClient/SVMSpokePoolClient.js +5 -5
- package/dist/esm/clients/SpokePoolClient/SVMSpokePoolClient.js.map +1 -1
- package/dist/esm/pool/poolClient.js +1 -1
- package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.d.ts +1 -1
- package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.js +1 -1
- package/dist/esm/providers/mocks/MockCachedSolanaRpcFactory.js.map +1 -1
- package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +1 -1
- package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.js +1 -1
- package/dist/esm/providers/mocks/MockRateLimitedSolanaRpcFactory.js.map +1 -1
- package/dist/esm/providers/mocks/MockRetrySolanaRpcFactory.d.ts +1 -1
- package/dist/esm/providers/mocks/MockRetrySolanaRpcFactory.js +1 -1
- package/dist/esm/providers/mocks/MockRetrySolanaRpcFactory.js.map +1 -1
- package/dist/esm/providers/solana/baseRpcFactories.d.ts +3 -3
- package/dist/esm/providers/solana/baseRpcFactories.js +4 -8
- package/dist/esm/providers/solana/baseRpcFactories.js.map +1 -1
- package/dist/esm/providers/solana/cachedRpcFactory.js +2 -0
- package/dist/esm/providers/solana/cachedRpcFactory.js.map +1 -1
- package/dist/esm/providers/solana/index.d.ts +1 -0
- package/dist/esm/providers/solana/index.js +1 -0
- package/dist/esm/providers/solana/index.js.map +1 -1
- package/dist/esm/providers/solana/quorumFallbackRpcFactory.d.ts +16 -0
- package/dist/esm/providers/solana/quorumFallbackRpcFactory.js +225 -0
- package/dist/esm/providers/solana/quorumFallbackRpcFactory.js.map +1 -0
- package/dist/esm/providers/utils.d.ts +1 -0
- package/dist/esm/providers/utils.js +3 -0
- package/dist/esm/providers/utils.js.map +1 -1
- package/dist/esm/utils/AddressUtils.js +1 -1
- package/dist/esm/utils/AddressUtils.js.map +1 -1
- package/dist/esm/utils/BlockExplorerUtils.js +1 -1
- package/dist/esm/utils/BlockExplorerUtils.js.map +1 -1
- package/dist/esm/utils/abi/index.d.ts +1 -1
- package/dist/esm/utils/abi/index.js +1 -1
- package/dist/types/arch/svm/BlockUtils.d.ts +3 -1
- package/dist/types/arch/svm/BlockUtils.d.ts.map +1 -1
- package/dist/types/arch/svm/SpokeUtils.d.ts +9 -8
- package/dist/types/arch/svm/SpokeUtils.d.ts.map +1 -1
- package/dist/types/arch/svm/provider.d.ts +1 -1
- package/dist/types/arch/svm/provider.d.ts.map +1 -1
- package/dist/types/arch/svm/utils.d.ts +11 -2
- package/dist/types/arch/svm/utils.d.ts.map +1 -1
- package/dist/types/clients/BaseAbstractClient.d.ts +3 -1
- package/dist/types/clients/BaseAbstractClient.d.ts.map +1 -1
- package/dist/types/clients/BundleDataClient/BundleDataClient.d.ts.map +1 -1
- package/dist/types/clients/BundleDataClient/utils/PoolRebalanceUtils.d.ts.map +1 -1
- package/dist/types/providers/mocks/MockCachedSolanaRpcFactory.d.ts +1 -1
- package/dist/types/providers/mocks/MockCachedSolanaRpcFactory.d.ts.map +1 -1
- package/dist/types/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts +1 -1
- package/dist/types/providers/mocks/MockRateLimitedSolanaRpcFactory.d.ts.map +1 -1
- package/dist/types/providers/mocks/MockRetrySolanaRpcFactory.d.ts +1 -1
- package/dist/types/providers/mocks/MockRetrySolanaRpcFactory.d.ts.map +1 -1
- package/dist/types/providers/solana/baseRpcFactories.d.ts +3 -3
- package/dist/types/providers/solana/baseRpcFactories.d.ts.map +1 -1
- package/dist/types/providers/solana/cachedRpcFactory.d.ts.map +1 -1
- package/dist/types/providers/solana/index.d.ts +1 -0
- package/dist/types/providers/solana/index.d.ts.map +1 -1
- package/dist/types/providers/solana/quorumFallbackRpcFactory.d.ts +17 -0
- package/dist/types/providers/solana/quorumFallbackRpcFactory.d.ts.map +1 -0
- package/dist/types/providers/utils.d.ts +1 -0
- package/dist/types/providers/utils.d.ts.map +1 -1
- package/dist/types/utils/abi/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/arch/svm/BlockUtils.ts +3 -1
- package/src/arch/svm/SpokeUtils.ts +100 -137
- package/src/arch/svm/provider.ts +1 -0
- package/src/arch/svm/utils.ts +30 -4
- package/src/caching/Arweave/ArweaveClient.ts +1 -1
- package/src/clients/BaseAbstractClient.ts +24 -8
- package/src/clients/BundleDataClient/BundleDataClient.ts +1 -0
- package/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts +5 -1
- package/src/clients/SpokePoolClient/SVMSpokePoolClient.ts +5 -5
- package/src/pool/poolClient.ts +1 -1
- package/src/providers/mocks/MockCachedSolanaRpcFactory.ts +1 -1
- package/src/providers/mocks/MockRateLimitedSolanaRpcFactory.ts +1 -1
- package/src/providers/mocks/MockRetrySolanaRpcFactory.ts +1 -1
- package/src/providers/rateLimitedProvider.ts +1 -1
- package/src/providers/solana/baseRpcFactories.ts +3 -3
- package/src/providers/solana/cachedRpcFactory.ts +2 -0
- package/src/providers/solana/index.ts +1 -0
- package/src/providers/solana/quorumFallbackRpcFactory.ts +248 -0
- package/src/providers/utils.ts +4 -0
- package/src/utils/AddressUtils.ts +1 -1
- package/src/utils/BlockExplorerUtils.ts +1 -1
- package/src/utils/abi/index.ts +1 -1
package/src/pool/poolClient.ts
CHANGED
|
@@ -216,7 +216,7 @@ export class PoolEventState {
|
|
|
216
216
|
|
|
217
217
|
// save these events
|
|
218
218
|
this.processEvents(events);
|
|
219
|
-
// only process token receipt events,
|
|
219
|
+
// only process token receipt events, because we just want the l1 token involved with this transfer
|
|
220
220
|
const eventState = hubPool.getEventState(events);
|
|
221
221
|
// event state is keyed by l1token address
|
|
222
222
|
const l1Tokens = Object.keys(eventState);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CachedSolanaRpcFactory } from "
|
|
1
|
+
import { CachedSolanaRpcFactory } from "../solana/cachedRpcFactory";
|
|
2
2
|
import { MockRetrySolanaRpcFactory } from "./MockRetrySolanaRpcFactory";
|
|
3
3
|
|
|
4
4
|
// Creates mocked cached Solana RPC factory by using the mocked retry Solana RPC factory.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RateLimitedSolanaRpcFactory } from "
|
|
1
|
+
import { RateLimitedSolanaRpcFactory } from "../solana/rateLimitedRpcFactory";
|
|
2
2
|
import { MockSolanaRpcFactory } from "./MockSolanaRpcFactory";
|
|
3
3
|
|
|
4
4
|
// Creates mocked rate limited Solana RPC factory by using the mocked Solana RPC factory.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RetrySolanaRpcFactory } from "
|
|
1
|
+
import { RetrySolanaRpcFactory } from "../solana/retryRpcFactory";
|
|
2
2
|
import { MockRateLimitedSolanaRpcFactory } from "./MockRateLimitedSolanaRpcFactory";
|
|
3
3
|
|
|
4
4
|
// Creates mocked retry Solana RPC factory by using the mocked rate limited Solana RPC factory.
|
|
@@ -68,7 +68,7 @@ export class RateLimitedProvider extends ethers.providers.StaticJsonRpcProvider
|
|
|
68
68
|
return result;
|
|
69
69
|
} catch (error) {
|
|
70
70
|
// Log errors as well.
|
|
71
|
-
// For now, to keep logs light, don't log the error itself, just
|
|
71
|
+
// For now, to keep logs light, don't log the error itself, just propagate and let it be handled higher up.
|
|
72
72
|
const elapsedTimeS = (performance.now() - startTime) / 1000;
|
|
73
73
|
this.logger.debug({
|
|
74
74
|
...loggerArgs,
|
|
@@ -2,7 +2,7 @@ import { ClusterUrl, createSolanaRpcFromTransport, RpcTransport } from "@solana/
|
|
|
2
2
|
|
|
3
3
|
// This is abstract base class for creating Solana RPC clients and transports.
|
|
4
4
|
export abstract class SolanaBaseRpcFactory {
|
|
5
|
-
constructor(
|
|
5
|
+
constructor() {}
|
|
6
6
|
|
|
7
7
|
// This method must be implemented by the derived class to create a transport.
|
|
8
8
|
public abstract createTransport(): RpcTransport;
|
|
@@ -18,8 +18,8 @@ export abstract class SolanaBaseRpcFactory {
|
|
|
18
18
|
export abstract class SolanaClusterRpcFactory extends SolanaBaseRpcFactory {
|
|
19
19
|
constructor(
|
|
20
20
|
readonly clusterUrl: ClusterUrl,
|
|
21
|
-
|
|
21
|
+
readonly chainId: number
|
|
22
22
|
) {
|
|
23
|
-
super(
|
|
23
|
+
super();
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -7,6 +7,8 @@ import { CacheType } from "../utils";
|
|
|
7
7
|
import { jsonReplacerWithBigInts, jsonReviverWithBigInts } from "../../utils";
|
|
8
8
|
import { RetrySolanaRpcFactory } from "./retryRpcFactory";
|
|
9
9
|
|
|
10
|
+
// A CachedFactory contains a RetryFactory and provides a caching layer that caches
|
|
11
|
+
// the results of the RetryFactory's RPC calls.
|
|
10
12
|
export class CachedSolanaRpcFactory extends SolanaClusterRpcFactory {
|
|
11
13
|
public readonly getTransactionCachePrefix: string;
|
|
12
14
|
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { RpcFromTransport, RpcResponse, RpcTransport, SolanaRpcApiFromTransport } from "@solana/kit";
|
|
2
|
+
import { CachedSolanaRpcFactory } from "./cachedRpcFactory";
|
|
3
|
+
import { SolanaBaseRpcFactory, SolanaClusterRpcFactory } from "./baseRpcFactories";
|
|
4
|
+
import { isPromiseFulfilled, isPromiseRejected } from "../../utils/TypeGuards";
|
|
5
|
+
import { compareSvmRpcResults, createSendErrorWithMessage } from "../utils";
|
|
6
|
+
import { Logger } from "winston";
|
|
7
|
+
|
|
8
|
+
// This factory stores multiple Cached RPC factories so that users of this factory can specify multiple RPC providers
|
|
9
|
+
// and the factory will fallback through them if any RPC calls fail. This factory also implements quorum logic amongst
|
|
10
|
+
// the RPC providers.
|
|
11
|
+
export class QuorumFallbackSolanaRpcFactory extends SolanaBaseRpcFactory {
|
|
12
|
+
readonly rpcFactories: {
|
|
13
|
+
transport: RpcTransport;
|
|
14
|
+
rpcClient: RpcFromTransport<SolanaRpcApiFromTransport<RpcTransport>, RpcTransport>;
|
|
15
|
+
rpcFactory: CachedSolanaRpcFactory;
|
|
16
|
+
}[] = [];
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
factoryConstructorParams: ConstructorParameters<typeof CachedSolanaRpcFactory>[],
|
|
20
|
+
readonly nodeQuorumThreshold: number,
|
|
21
|
+
readonly logger: Logger
|
|
22
|
+
) {
|
|
23
|
+
super();
|
|
24
|
+
factoryConstructorParams.forEach((params) => {
|
|
25
|
+
const rpcFactory = new CachedSolanaRpcFactory(...params);
|
|
26
|
+
this.rpcFactories.push({
|
|
27
|
+
transport: rpcFactory.createTransport(),
|
|
28
|
+
rpcClient: rpcFactory.createRpcClient(),
|
|
29
|
+
rpcFactory,
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
if (this.nodeQuorumThreshold < 1 || !Number.isInteger(this.nodeQuorumThreshold)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`nodeQuorum,Threshold cannot be < 1 and must be an integer. Currently set to ${this.nodeQuorumThreshold}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public createTransport(): RpcTransport {
|
|
40
|
+
return async <TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> => {
|
|
41
|
+
const { method, params } = args[0].payload as { method: string; params?: unknown[] };
|
|
42
|
+
const quorumThreshold = this._getQuorum(method, params ?? []);
|
|
43
|
+
const requiredFactories = this.rpcFactories.slice(0, quorumThreshold);
|
|
44
|
+
const fallbackFactories = [...this.rpcFactories.slice(quorumThreshold)];
|
|
45
|
+
const errors: [SolanaClusterRpcFactory, string][] = [];
|
|
46
|
+
|
|
47
|
+
const tryWithFallback = <TResponse>(
|
|
48
|
+
factory: {
|
|
49
|
+
transport: RpcTransport;
|
|
50
|
+
rpcClient: RpcFromTransport<SolanaRpcApiFromTransport<RpcTransport>, RpcTransport>;
|
|
51
|
+
rpcFactory: CachedSolanaRpcFactory;
|
|
52
|
+
},
|
|
53
|
+
...args: Parameters<RpcTransport>
|
|
54
|
+
): Promise<[SolanaClusterRpcFactory, RpcResponse<TResponse>]> => {
|
|
55
|
+
return factory
|
|
56
|
+
.transport<TResponse>(...args)
|
|
57
|
+
.then((result): [SolanaClusterRpcFactory, RpcResponse<TResponse>] => [factory.rpcFactory, result])
|
|
58
|
+
.catch((error) => {
|
|
59
|
+
// Append the provider and error to the error array.
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
61
|
+
errors.push([factory.rpcFactory, (error as any)?.stack || error?.toString()]);
|
|
62
|
+
|
|
63
|
+
if (fallbackFactories.length === 0) {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const currentFactory = factory.rpcFactory.clusterUrl;
|
|
68
|
+
const nextFactory = fallbackFactories.shift()!;
|
|
69
|
+
this.logger.debug({
|
|
70
|
+
at: "FallbackSolanaRpcFactory#createTransport::tryWithFallback",
|
|
71
|
+
message: `[${method}] ${currentFactory} failed, falling back to ${nextFactory.rpcFactory.clusterUrl}, new fallback providers length: ${fallbackFactories.length}`,
|
|
72
|
+
method,
|
|
73
|
+
error,
|
|
74
|
+
});
|
|
75
|
+
return tryWithFallback(nextFactory, ...args);
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const results = await Promise.allSettled(
|
|
79
|
+
requiredFactories.map((factory) => {
|
|
80
|
+
return tryWithFallback<TResponse>(factory, ...args);
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const getErrorStrings = () => {
|
|
85
|
+
return errors.map(
|
|
86
|
+
([factory, errorText]) => `Provider ${factory.clusterUrl} failed to call ${method} with error ${errorText}`
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (!results.every(isPromiseFulfilled)) {
|
|
91
|
+
// Format the error so that it's very clear which providers failed and succeeded.
|
|
92
|
+
const errorTexts = getErrorStrings();
|
|
93
|
+
const successfulProviderUrls = results.filter(isPromiseFulfilled).map((result) => result.value[0].clusterUrl);
|
|
94
|
+
throw createSendErrorWithMessage(
|
|
95
|
+
`Not enough providers succeeded on ${method} call. Errors:\n${errorTexts.join("\n")}\n` +
|
|
96
|
+
`Successful Providers:\n${successfulProviderUrls.join("\n")}`,
|
|
97
|
+
results.find(isPromiseRejected)?.reason
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const values = results.map((result) => result.value);
|
|
102
|
+
// Start at element 1 and begin comparing.
|
|
103
|
+
// If _all_ values are equal, we have hit quorum, so return.
|
|
104
|
+
if (values.slice(1).every(([, output]) => compareSvmRpcResults(method, values[0][1], output))) {
|
|
105
|
+
return values[0][1];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const getHighestCountResult = (values: [SolanaClusterRpcFactory, TResponse][]): [TResponse, number] => {
|
|
109
|
+
// Group the results by the count of that result.
|
|
110
|
+
const counts = [...values].reduce(
|
|
111
|
+
(acc, curr) => {
|
|
112
|
+
const [, result] = curr;
|
|
113
|
+
|
|
114
|
+
// Find the first result that matches the return value.
|
|
115
|
+
const existingMatch = acc.find(([existingResult]) => compareSvmRpcResults(method, existingResult, result));
|
|
116
|
+
|
|
117
|
+
// Increment the count if a match is found, else add a new element to the match array with a count of 1.
|
|
118
|
+
if (existingMatch) {
|
|
119
|
+
existingMatch[1]++;
|
|
120
|
+
} else {
|
|
121
|
+
acc.push([result, 1]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Return the same acc object because it was modified in place.
|
|
125
|
+
return acc;
|
|
126
|
+
},
|
|
127
|
+
[[undefined, 0]] as [TResponse, number][] // Initialize with [undefined, 0] as the first element so something is always returned.
|
|
128
|
+
);
|
|
129
|
+
// Sort so the result with the highest count is first.
|
|
130
|
+
counts.sort(([, a], [, b]) => b - a);
|
|
131
|
+
|
|
132
|
+
// Extract the result by grabbing the first element.
|
|
133
|
+
const [mostFrequentResult, count] = counts[0];
|
|
134
|
+
return [mostFrequentResult, count];
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const logQuorumMismatchOrFailureDetails = (
|
|
138
|
+
method: string,
|
|
139
|
+
params: Array<unknown>,
|
|
140
|
+
mismatchedProviders: string[],
|
|
141
|
+
successfulProviders: string[],
|
|
142
|
+
errors: [SolanaClusterRpcFactory, string][],
|
|
143
|
+
quorumResult: TResponse
|
|
144
|
+
) => {
|
|
145
|
+
this.logger.warn({
|
|
146
|
+
at: "FallbackSolanaRpcFactory#createTransport",
|
|
147
|
+
message: `[${method}] Some providers mismatched with the quorum result or failed 🚸`,
|
|
148
|
+
notificationPath: "across-warn",
|
|
149
|
+
method,
|
|
150
|
+
params: JSON.stringify(params),
|
|
151
|
+
quorumResult: METHODS_RETURNING_BIGINT.includes(method) ? Number(quorumResult) : undefined,
|
|
152
|
+
mismatchedProviders,
|
|
153
|
+
successfulProviders,
|
|
154
|
+
erroringProviders: errors.map(
|
|
155
|
+
([factory, errorText]) => `Provider ${factory.clusterUrl} failed with error ${errorText}`
|
|
156
|
+
),
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const throwQuorumError = (mostFrequentResult: TResponse, allValues: [SolanaClusterRpcFactory, TResponse][]) => {
|
|
161
|
+
const errorTexts = getErrorStrings();
|
|
162
|
+
const successfulProviderUrls = values.map(([provider]) => provider.clusterUrl);
|
|
163
|
+
const mismatchedProviders = allValues
|
|
164
|
+
.filter(([, result]) => !compareSvmRpcResults(method, result, mostFrequentResult))
|
|
165
|
+
.map(([factory]) => factory.clusterUrl);
|
|
166
|
+
logQuorumMismatchOrFailureDetails(
|
|
167
|
+
method,
|
|
168
|
+
params ?? [],
|
|
169
|
+
mismatchedProviders,
|
|
170
|
+
successfulProviderUrls,
|
|
171
|
+
errors,
|
|
172
|
+
mostFrequentResult
|
|
173
|
+
);
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Not enough providers agreed to meet quorum.\n" +
|
|
176
|
+
"Providers that errored:\n" +
|
|
177
|
+
`${errorTexts.join("\n")}\n` +
|
|
178
|
+
"Providers that succeeded, but some failed to match:\n" +
|
|
179
|
+
successfulProviderUrls.join("\n")
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// Exit early if there are no fallback providers left.
|
|
184
|
+
if (fallbackFactories.length === 0) {
|
|
185
|
+
const [mostFrequentResult] = getHighestCountResult(values);
|
|
186
|
+
throwQuorumError(mostFrequentResult, values);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Try each fallback provider in parallel.
|
|
190
|
+
const fallbackResults = await Promise.allSettled(
|
|
191
|
+
fallbackFactories.map((factory) => {
|
|
192
|
+
return factory
|
|
193
|
+
.transport<TResponse>(...args)
|
|
194
|
+
.then((result): [SolanaClusterRpcFactory, TResponse] => [factory.rpcFactory, result])
|
|
195
|
+
.catch((err) => {
|
|
196
|
+
errors.push([factory.rpcFactory, err?.stack || err?.toString()]);
|
|
197
|
+
throw new Error("Fallback RPC call failed while trying to reach quorum", err);
|
|
198
|
+
});
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// This filters only the fallbacks that succeeded.
|
|
203
|
+
const fallbackValues = fallbackResults.filter(isPromiseFulfilled).map((promise) => promise.value);
|
|
204
|
+
|
|
205
|
+
const [quorumResult, count] = getHighestCountResult([...values, ...fallbackValues]);
|
|
206
|
+
// If this count is less than we need for quorum, throw the quorum error.
|
|
207
|
+
|
|
208
|
+
if (count < quorumThreshold) {
|
|
209
|
+
throwQuorumError(quorumResult, [...values, ...fallbackValues]);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// If we've achieved quorum, then we should still log the providers that mismatched with the quorum result.
|
|
213
|
+
const mismatchedProviders = [...values, ...fallbackValues]
|
|
214
|
+
.filter(([, result]) => !compareSvmRpcResults(method, result, quorumResult))
|
|
215
|
+
.map(([factory]) => factory.clusterUrl);
|
|
216
|
+
const successfulProviderUrls = [...values, ...fallbackValues].map(([provider]) => provider.clusterUrl);
|
|
217
|
+
if (mismatchedProviders.length > 0 || errors.length > 0) {
|
|
218
|
+
logQuorumMismatchOrFailureDetails(
|
|
219
|
+
method,
|
|
220
|
+
params ?? [],
|
|
221
|
+
mismatchedProviders,
|
|
222
|
+
successfulProviderUrls,
|
|
223
|
+
errors,
|
|
224
|
+
quorumResult
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return quorumResult;
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_getQuorum(method: string, _params: Array<unknown>): number {
|
|
233
|
+
// Only use quorum if this is a historical query that doesn't depend on the current block number.
|
|
234
|
+
|
|
235
|
+
switch (method) {
|
|
236
|
+
case "getBlock":
|
|
237
|
+
case "getBlockTime":
|
|
238
|
+
return this.nodeQuorumThreshold;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// All other calls should use quorum 1 to avoid errors due to sync differences.
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// These methods return a bigint and their results are loggable because they are succinct and can further assist
|
|
247
|
+
// quorum debugging.
|
|
248
|
+
const METHODS_RETURNING_BIGINT = ["getBlockTime", "getSlot"];
|
package/src/providers/utils.ts
CHANGED
|
@@ -170,6 +170,10 @@ export function compareRpcResults(method: string, rpcResultA: unknown, rpcResult
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
export function compareSvmRpcResults(_method: string, rpcResultA: unknown, rpcResultB: unknown): boolean {
|
|
174
|
+
return isEqual(rpcResultA, rpcResultB);
|
|
175
|
+
}
|
|
176
|
+
|
|
173
177
|
export enum CacheType {
|
|
174
178
|
NONE, // Do not cache
|
|
175
179
|
WITH_TTL, // Cache with TTL
|
|
@@ -154,7 +154,7 @@ export abstract class Address {
|
|
|
154
154
|
return (this.evmAddress ??= parseRawAddress());
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
// Converts the address to a hex string. This method should be
|
|
157
|
+
// Converts the address to a hex string. This method should be overridden by subclasses to obtain more meaningful
|
|
158
158
|
// address representations for the target chain ID.
|
|
159
159
|
toNative(): string {
|
|
160
160
|
return this.toBytes32();
|
|
@@ -73,7 +73,7 @@ function _createBlockExplorerLinkMarkdown(hex: string, chainId = 1): string | nu
|
|
|
73
73
|
] as [number, string][]) {
|
|
74
74
|
// If the hex string is the correct length, return the link.
|
|
75
75
|
if (hex.length === length) {
|
|
76
|
-
return `<${constructURL(explorerDomain, [route, hex])}
|
|
76
|
+
return `<${constructURL(explorerDomain, [route, hex])} | ${shortURLString}>`;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
return null;
|
package/src/utils/abi/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ export function getABIDir(): string {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
* @notice Retrieve an ABI
|
|
21
|
+
* @notice Retrieve an ABI description from the set of known contracts.
|
|
22
22
|
* @param contractName Name of the contract ABI to retrieve.
|
|
23
23
|
* @returns Contract ABI as an ethers ContractInterface type.
|
|
24
24
|
*/
|