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

Merge development into main #46

Merged
merged 11 commits into from
Aug 23, 2024
12 changes: 12 additions & 0 deletions tests/rpc-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,16 @@ describe("RPCHandler", () => {
}, 10000);
}
});

it("Should return the first available RPC", async () => {
const module = await import("../types/rpc-handler");
const rpcHandler = new module.RPCHandler({
...testConfig,
networkId: "1",
rpcTimeout: 1000,
});

const provider = await rpcHandler.getFirstAvailableRpcProvider();
expect(provider).not.toBeNull();
});
});
28 changes: 28 additions & 0 deletions types/rpc-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { StorageService } from "./storage-service";

const NO_RPCS_AVAILABLE = "No RPCs available";

function shuffleArray(array: object[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

export class RPCHandler implements HandlerInterface {
private static _instance: RPCHandler | null = null;
private _provider: JsonRpcProvider | null = null;
Expand Down Expand Up @@ -55,6 +62,27 @@ export class RPCHandler implements HandlerInterface {
this.testRpcPerformance.bind(this);
}

/**
* Loops through all RPCs for a given network id and returns a provider with the first successful network.
*/
public async getFirstAvailableRpcProvider() {
const rpcList = [...networkRpcs[this._networkId].rpcs];
shuffleArray(rpcList);
for (const rpc of rpcList) {
try {
const result = await RPCService.makeRpcRequest(rpc.url, this._rpcTimeout, { "Content-Type": "application/json" });
if (result.success) {
return new JsonRpcProvider({ url: rpc.url, skipFetchSetup: true }, Number(this._networkId));
} else {
console.error(`Failed to reach endpoint ${rpc.url}. ${result.error}`);
}
} catch (err) {
console.error(`Failed to reach endpoint ${rpc.url}. ${err}`);
}
}
return null;
}

public async getFastestRpcProvider(): Promise<JsonRpcProvider> {
let fastest = await this.testRpcPerformance();

Expand Down
23 changes: 1 addition & 22 deletions types/rpc-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,8 @@ export class RPCService {
throw new Error(rpcUrl);
}
}
const promises = runtimeRpcs.map((rpcUrl) => requestEndpoint(rpcUrl));
async function getFirstSuccessfulRequest(requests: string[]) {
if (requests.length === 0) {
throw new Error("All requests failed.");
}
const promisesToResolve = requests.map((rpcUrl) => requestEndpoint(rpcUrl));

try {
return await Promise.race(promisesToResolve);
} catch (err) {
console.error(`Failed to reach endpoint. ${err}`);
if (err instanceof Error && requests.includes(err.message)) {
return getFirstSuccessfulRequest(requests.filter((request) => request !== err.message));
}
return getFirstSuccessfulRequest(requests.slice(1));
}
}
const fastest = await getFirstSuccessfulRequest(runtimeRpcs);

if (fastest.success) {
latencies[`${networkId}__${fastest.rpcUrl}`] = fastest.duration;
}

const promises = runtimeRpcs.map((rpcUrl) => requestEndpoint(rpcUrl));
const allResults = await Promise.allSettled(promises);

allResults.forEach((result) => {
Expand Down
Loading