Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,29 @@ export const getOriginHeader = () => {
return 'impact-graph-' + SERVICE_NAME || 'unnamed';
};

// Get fallback RPC URLs for networks that have multiple providers configured
export function getFallbackProviderUrls(networkId: number): string[] {
const fallbackUrls: string[] = [];

switch (networkId) {
case NETWORK_IDS.XDAI:
// Add all available Gnosis/xDai RPC endpoints
if (process.env.XDAI_NODE_HTTP_URL) {
fallbackUrls.push(process.env.XDAI_NODE_HTTP_URL);
}
if (process.env.XDAI_NODE_HTTP_URL_QUICKNODE) {
fallbackUrls.push(process.env.XDAI_NODE_HTTP_URL_QUICKNODE);
}
// Public fallback endpoints
fallbackUrls.push('https://rpc.gnosischain.com');
fallbackUrls.push('https://rpc.ankr.com/gnosis');
break;
// Add more networks with fallback support here as needed
}

return fallbackUrls;
}

export function getProvider(networkId: number) {
let url;
let options;
Expand All @@ -474,7 +497,10 @@ export function getProvider(networkId: number) {
url = process.env.ETC_NODE_HTTP_URL as string;
break;
case NETWORK_IDS.XDAI:
url = process.env.XDAI_NODE_HTTP_URL as string;
url =
process.env.XDAI_NODE_HTTP_URL ||
process.env.XDAI_NODE_HTTP_URL_QUICKNODE ||
'https://rpc.gnosischain.com';
break;

case NETWORK_IDS.BSC:
Expand Down Expand Up @@ -561,6 +587,67 @@ export function getProvider(networkId: number) {
);
}

// Retry an RPC call with automatic fallback to alternate providers
export async function retryWithFallbackProvider<T>(
networkId: number,
rpcCall: (provider: ethers.providers.JsonRpcProvider) => Promise<T>,
maxRetries = 2,
): Promise<T> {
const fallbackUrls = getFallbackProviderUrls(networkId);

// If no fallbacks are configured, just use the primary provider
if (fallbackUrls.length === 0) {
const provider = getProvider(networkId);
return await rpcCall(provider);
}

let lastError: Error | undefined;

// Try each provider URL in sequence
for (let i = 0; i < fallbackUrls.length && i < maxRetries; i++) {
try {
const provider = new ethers.providers.JsonRpcProvider({
url: fallbackUrls[i],
headers: {
Origin: getOriginHeader(),
},
});

logger.debug(`Trying RPC provider ${i + 1}/${fallbackUrls.length}`, {
networkId,
url: fallbackUrls[i],
});

const result = await rpcCall(provider);

// If successful and not the first provider, log for monitoring
if (i > 0) {
logger.info(`Successfully used fallback RPC provider #${i + 1}`, {
networkId,
url: fallbackUrls[i],
});
}

return result;
} catch (error: any) {
lastError = error;
logger.warn(`RPC provider ${i + 1}/${fallbackUrls.length} failed`, {
networkId,
url: fallbackUrls[i],
error: error.message,
status: error.status,
});

// If this is the last attempt, throw the error
if (i === fallbackUrls.length - 1 || i === maxRetries - 1) {
throw error;
}
}
}

throw lastError || new Error('All RPC providers failed');
}

export function getBlockExplorerApiUrl(networkId: number): string {
let apiUrl;
// After the migration to Etherscan V2, we can use the Etherscan API key for all networks
Expand Down
9 changes: 6 additions & 3 deletions src/services/chains/evm/transactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getBlockExplorerApiUrl,
getNetworkNativeToken,
getProvider,
retryWithFallbackProvider,
NETWORK_IDS,
} from '../../../provider';
import { logger } from '../../../utils/logger';
Expand All @@ -34,13 +35,15 @@ export async function getEvmTransactionInfoFromNetwork(
): Promise<NetworkTransactionInfo> {
const { networkId, nonce } = input;

const provider = getProvider(networkId);
logger.debug(
'NODE RPC request count - getTransactionInfoFromNetwork provider.getTransactionCount txHash:',
input.txHash,
);
const userTransactionsCount = await provider.getTransactionCount(
input.fromAddress,

// Use retry mechanism for getTransactionCount to handle RPC failures
const userTransactionsCount = await retryWithFallbackProvider(
networkId,
async provider => await provider.getTransactionCount(input.fromAddress),
);
if (typeof nonce === 'number' && userTransactionsCount <= nonce) {
logger.debug('getTransactionDetail check nonce', {
Expand Down