Skip to content

Commit

Permalink
Merge pull request #31 from ubiquity/development
Browse files Browse the repository at this point in the history
Merge development into main
  • Loading branch information
gentlementlegen authored Jul 31, 2024
2 parents 464ca7b + e50fa28 commit 9a20758
Show file tree
Hide file tree
Showing 39 changed files with 3,784 additions and 2,432 deletions.
4 changes: 2 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "lib", "dist"],
"ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "lib", "dist", "dynamic.ts"],
"useGitignore": true,
"language": "en",
"words": ["dataurl", "devpool", "outdir", "servedir"],
"dictionaries": ["typescript", "node", "software-terms"],
"import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"],
"ignoreRegExpList": ["[0-9a-fA-F]{6}"],
"ignoreWords": ["Rpcs", "ethersproject", "publicnode", "WXDAI", "XDAI", "chainlist", "Knip", "LOCALSTORAGE"]
"ignoreWords": ["Rpcs", "ethersproject", "publicnode", "WXDAI", "XDAI", "chainlist", "Knip", "LOCALSTORAGE", "sonarjs", "cronos", "Cronos", "gochain"]
}
10 changes: 10 additions & 0 deletions .github/cspell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { exec } = require("child_process");

exec('cspell "**/*"', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
if (stdout) console.log(`stdout: ${stdout}`);
if (stderr) console.error(`stderr: ${stderr}`);
});
40 changes: 0 additions & 40 deletions .github/workflows/bump-version.yml

This file was deleted.

36 changes: 25 additions & 11 deletions .github/workflows/jest-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,33 @@ jobs:
with:
fetch-depth: 0

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install dependencies
run: yarn install

- name: Build & Run test suite
- name: Build
run: yarn build

- name: Start Anvil
run: yarn test:anvil &

- name: Wait for Anvil
run: |
yarn test | tee ./coverage.txt && exit ${PIPESTATUS[0]}
- name: Jest Coverage Comment
# Ensures this step is run even on previous step failure (e.g. test failed)
for i in {1..30}
do
if curl -s http://localhost:8545; then
break
fi
sleep 1
done || exit 1
- name: Jest With Coverage
run: yarn test

- name: Add Jest Report to Summary
if: always()
uses: MishaKav/jest-coverage-comment@main
with:
coverage-summary-path: coverage/coverage-summary.json
junitxml-path: junit.xml
junitxml-title: JUnit
coverage-path: ./coverage.txt
run: echo "$(cat test-dashboard.md)" >> $GITHUB_STEP_SUMMARY
2 changes: 1 addition & 1 deletion .github/workflows/knip-reporter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
jobs:
knip-reporter:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion != 'success' }}
steps:
- uses: actions/download-artifact@v4
with:
Expand All @@ -24,7 +25,6 @@ jobs:
trim: true

- name: Report knip results to pull request
if: ${{ github.event.workflow_run.conclusion != 'success' }}
uses: gitcoindev/knip-reporter@main
with:
verbose: true
Expand Down
11 changes: 9 additions & 2 deletions .github/workflows/publish-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ jobs:
publish-package:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
release-type: node
target-branch: main

- name: Checkout repository code
uses: actions/checkout@v4
with:
Expand All @@ -32,6 +38,7 @@ jobs:
yarn pack
- name: Publish
run: npm publish --access public
run: yarn publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
if: ${{ steps.release.outputs.release_created }}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ node_modules
dist

coverage
junit.xml
junit.xml
dynamic.ts
test-dashboard.md
27 changes: 14 additions & 13 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
nodeLinker: node-modules

supportedArchitectures:
os:
- "current"
- "darwin"
- "linux"
- "win32"
cpu:
- "current"
- "x64"
- "x86"
- "arm"
- "ia32"
- current
- x64
- x86
- arm
- ia32
libc:
- "current"
- "glibc"
- "musl"
- current
- glibc
- musl
os:
- current
- darwin
- linux
- win32
85 changes: 59 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# `@ubiquity-dao/rpc-handler`

This packages leverages [Chainlist's](https://github.com/DefiLlama/chainlist) network RPC list to return the lowest latency provider from the list for any given network ID.

## Features

- Returns the lowest latency provider for a given network ID
- Drops bad endpoints from the list and creates a runtime/local storage cache
- Can re-test the cached RPCs by calling `handler.getFastestRpcProvider()`
- Can be used in both the browser and Node.js
- Fully configurable and extendable
- Only uses endpoints which Chainlist report as tracking _no_ data (see [`extraRpcs.js`](https://github.com/DefiLlama/chainlist/blob/main/constants/extraRpcs.js))
## Why use this package?

- **No more slow RPCs**: No more slow RPCs, no more failed requests, no more headaches
- **Fastest RPCs**: Returns the fastest RPC for a given network ID
- **Fallback mechanism**: Retries failed method calls on the next fastest provider
- **Retries failed method calls**: Retries failed method calls on the next fastest provider
- **No more searching for RPCs**: No need to for an RPC URL again, just pass the network ID
- **Fully configurable**: Configure the number of retries, retry delay, log tier and more
- **Browser and Node.js support**: Can be used in both the browser and Node.js
- **Local storage cache**: Stores the fastest RPCs for a given network ID in the browser's local storage
- **Drop-in replacement**: No changes required to your existing codebase, just replace your current provider with the one returned by `RPCHandler.getFastestRpcProvider()`
- **No tracking or analytics**: No additional tracking, data collection or analytics performed by this package beyond the specific public endpoints that support those features

## Installation

Expand All @@ -26,13 +28,23 @@ import { RPCHandler, HandlerConstructorConfig } from "@ubiquity-dao/rpc-handler/

export function useHandler(networkId: number) {
const config: HandlerConstructorConfig = {
networkId: 1,
rpcTimeout: 1500,
autoStorage: false,
cacheRefreshCycles: 10,
networkName: null, // the name will be deduced from the networkId, unless using a custom network
networkRpcs: null, // same as networkName, but for injecting additional RPCs
runtimeRpcs: null, // same as networkRpcs, although these are considered error-free
networkId: 100, // your chosen networkId
networkName: null, // will default using the networkRpcs
networkRpcs: null, // e.g "https://mainnet.infura.io/..."
runtimeRpcs: null, // e.g "<networkId>__https://mainnet.infura.io/..." > "1__https://mainnet.infura.io/..."
autoStorage: true, // browser only, will store in localStorage
cacheRefreshCycles: 10, // bad RPCs are excluded if they fail, this is how many cycles before they're re-tested
rpcTimeout: 1500, // when the RPCs are tested they are raced, this is the max time to allow for a response
tracking: "yes", // accepted values: "yes" | "limited" | "none". This is the data tracking status of the RPC, not this package.
proxySettings: {
retryCount: 3, // how many times we'll loop the list of RPCs retrying the request before failing
retryDelay: 100, // (ms) how long we'll wait before moving to the next RPC, best to keep this low
logTier: "ok", // |"info"|"error"|"debug"|"fatal"|"verbose"; set to "none" for no logs, null will default to "error", "verbose" will log all
logger: null, // null will default to PrettyLogs
strictLogs: true, // true, only the specified logTier will be logged and false all wll be logged.
moduleName?: "[UBQ RPC Handler]", // Can be omitted. this is the prefix for the logs.
disabled?: false, // Can be omitted. this will disable the proxy, requiring you to handle retry logic etc yourself.
}
};
// No RPCs are tested at this point
return new RPCHandler(config);
Expand All @@ -51,22 +63,43 @@ app.provider = await handler.getFastestRpcProvider();

- The RPCs are not tested on instantiation, but are tested on each call to `handler.getFastestRpcProvider()` or `handler.testRpcPerformance()`

- See the full [config](src\handler.ts) object (optionally passed in the constructor) for more options
- See the full [config](types/handler.ts) object (optionally passed in the constructor) for more options

- Local storage is not enabled by default, but can be enabled by passing `autoStorage: true` in the config object
- LocalStorage is not enabled by default, but can be enabled by passing `autoStorage: true` in the config object

- The `cacheRefreshCycles` is the number of roundtrips made before clearing the cache and re-testing all RPCs again
- Use the returned `JsonRpcProvider` object as you would normally, internally, any call you pass through it will be retried on the next fastest provider if it fails. It should only ever really throw due to user error or a network issue.

## Testing

- Tests have a specific build in order to run `yarn test` will produce this build and run the tests.
- After testing, re-build using `yarn build` for the original build
1. Build the package:

```bash
yarn test
yarn build
```

- This below will only work after `yarn build` has been run and will fail under test conditions
2. In terminal A run the following command to start a local Anvil instance:

```bash
npx tsx tests/script-test.ts
```
yarn test:anvil
```

3. In terminal B run the following command to run the tests:

```bash
yarn test
```

## Say goodbye to slow RPCs

This packages leverages [Chainlist's](https://github.com/DefiLlama/chainlist) network RPC list to return the lowest latency provider from the list for any given network ID. Creating a runtime/local storage cache of the fastest RPCs, it can be used in both the browser and Node.js.

By default, it performs as an abstraction layer for the Web3 developer by having built-in failed method call retries,
bad endpoint exclusion and a cache of the fastest RPCs for a given network ID. It serves as a drop-in replacement for any `JsonRpcProvider` and can be extended to handle custom RPCs, chains and more.

By routing requests through this package, it ensures the lowest latency for your users, while also providing a fallback mechanism for when the fastest provider fails. As even the best of nodes can fail, it retries failed method calls on the next fastest provider and so on until it succeeds or until your custom breakpoints are reached. With the ability to configure the number of retries, retry delay, log tier, breakpoints and more, it can be tailored to your specific needs.

There is no additional tracking, data collection or analytics performed by this package beyond the specific public endpoints that support those features. These will be made filterable in the future. Calls are made directly to the RPCs and the only data stored is the `Record` of RPCs for a given network ID, which can be stored in the browser's local storage for faster retrieval.

No changes are required to your existing codebase, simply replace your current provider with the one returned by `RPCHandler.getFastestRpcProvider()` and you're good to go. While all attempts are made to ensure the fastest provider is always used, it's important to note that the fastest provider can change over time, so it's recommended to call `RPCHandler.getFastestRpcProvider()` at the start of your app or at regular intervals. Also, it is possible that all providers fail, in which case the package will throw an error.

No more slow RPCs, no more failed requests, no more headaches.
55 changes: 55 additions & 0 deletions build/dynamic-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { appendFile, writeFile } from "fs/promises";
import { BlockExplorer, NativeToken } from "../types/handler";
import { generateChainData } from "../lib/chainlist/utils/fetch";

/**
* This produces dynamic types and constants for:
* - CHAINS_IDS
* - NETWORKS
* - NETWORK_FAUCETS
* - NETWORK_EXPLORERS
* - NETWORK_CURRENCIES
* - EXTRA_RPCS
*/

export async function createDynamicTypes() {
const data = await generateChainData();
const idToNetwork: Record<string, string> = {};
const networkToId: Record<string, number> = {};
const idToRpc: Record<string, string[]> = {};
const idToFaucet: Record<string, string[]> = {};
const idToExplorers = {} as Record<string, BlockExplorer[]>;
const idToNativeCurrency: Record<string, NativeToken> = {};
const extraRpcs: Record<string, string[]> = {};

for (const chain of data) {
let { name, chainId, networkId, rpc, faucets, explorers, nativeCurrency } = chain;
name = name.toLowerCase().replace(/\s/g, "-");
idToNetwork[chainId] = name;
networkToId[name] = networkId;
idToRpc[chainId] = rpc;
idToFaucet[chainId] = faucets;

if (explorers && explorers.length > 0) {
idToExplorers[chainId] = explorers;
}

if (rpc && rpc.length > 0) {
extraRpcs[chainId] = rpc;
}

idToNativeCurrency[chainId] = nativeCurrency;
}

const filename = "dynamic.ts";

// Clear the file
await writeFile(filename, "/* eslint-disable sonarjs/no-duplicate-string */\n\n");

appendFile(filename, `\nexport const CHAINS_IDS = ${JSON.stringify(idToNetwork, null, 2)} as const;\n`);
appendFile(filename, `\nexport const NETWORKS = ${JSON.stringify(networkToId, null, 2)} as const;\n`);
appendFile(filename, `\nexport const NETWORK_FAUCETS = ${JSON.stringify(idToFaucet, null, 2)};\n`);
appendFile(filename, `\nexport const NETWORK_EXPLORERS = ${JSON.stringify(idToExplorers, null, 2)};\n`);
appendFile(filename, `\nexport const NETWORK_CURRENCIES = ${JSON.stringify(idToNativeCurrency, null, 2)};\n`);
appendFile(filename, `\nexport const EXTRA_RPCS = ${JSON.stringify(extraRpcs, null, 2)};\n`);
}
Loading

0 comments on commit 9a20758

Please sign in to comment.