Skip to content

Commit

Permalink
fix: fallback fall through behavior when allowlist is set
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Feb 11, 2025
1 parent 82ca61e commit dee7aa6
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/popular-walls-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Fixed issue where `fallback` transports would fall through when other transports do not support the JSON-RPC method (has set up a method allowlist on the transport).
1 change: 1 addition & 0 deletions src/clients/transports/createTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export function createTransport<
return {
config: {
key,
methods,
name,
request,
retryCount,
Expand Down
47 changes: 47 additions & 0 deletions src/clients/transports/fallback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createHttpServer } from '~test/src/utils.js'
import { getBlockNumber } from '../../actions/public/getBlockNumber.js'
import { localhost } from '../../chains/index.js'
import {
InternalRpcError,
MethodNotSupportedRpcError,
UserRejectedRequestError,
} from '../../errors/rpc.js'
Expand Down Expand Up @@ -31,6 +32,7 @@ test('default', () => {
{
"config": {
"key": "fallback",
"methods": undefined,
"name": "Fallback",
"request": [Function],
"retryCount": 3,
Expand All @@ -45,6 +47,7 @@ test('default', () => {
{
"config": {
"key": "http",
"methods": undefined,
"name": "HTTP JSON-RPC",
"request": [Function],
"retryCount": 0,
Expand All @@ -61,6 +64,7 @@ test('default', () => {
{
"config": {
"key": "http",
"methods": undefined,
"name": "HTTP JSON-RPC",
"request": [Function],
"retryCount": 0,
Expand Down Expand Up @@ -267,6 +271,46 @@ describe('request', () => {
expect(await transport.request({ method: 'eth_c' })).toBe('0x2')
})

test('methods.include (error)', async () => {
const server1 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(
JSON.stringify({
error: { code: InternalRpcError.code, message: 'sad times' },
}),
)
})
const server2 = await createHttpServer((_req, res) => {
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(JSON.stringify({ result: '0x2' }))
})

const transport = fallback([
http(server1.url, { methods: { include: ['eth_a', 'eth_b'] } }),
http(server2.url, { methods: { exclude: ['eth_a'] } }),
http(server2.url, { methods: { include: ['eth_b'] } }),
])({
chain: localhost,
})

await expect(() =>
transport.request({ method: 'eth_a' }),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[InternalRpcError: An internal error was received.
URL: http://localhost
Request body: {"method":"eth_a"}
Details: sad times
Version: [email protected]]
`)
expect(await transport.request({ method: 'eth_b' })).toBe('0x2')
})

test('error (rpc)', async () => {
let count = 0
const server1 = await createHttpServer((_req, res) => {
Expand Down Expand Up @@ -556,6 +600,7 @@ describe('client', () => {
"request": [Function],
"transport": {
"key": "fallback",
"methods": undefined,
"name": "Fallback",
"onResponse": [Function],
"request": [Function],
Expand All @@ -566,6 +611,7 @@ describe('client', () => {
{
"config": {
"key": "http",
"methods": undefined,
"name": "HTTP JSON-RPC",
"request": [Function],
"retryCount": 0,
Expand All @@ -582,6 +628,7 @@ describe('client', () => {
{
"config": {
"key": "http",
"methods": undefined,
"name": "HTTP JSON-RPC",
"request": [Function],
"retryCount": 0,
Expand Down
12 changes: 12 additions & 0 deletions src/clients/transports/fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ export function fallback<const transports extends readonly Transport[]>(
key,
name,
async request({ method, params }) {
let includes: boolean | undefined

const fetch = async (i = 0): Promise<any> => {
const transport = transports[i]({
...rest,
Expand Down Expand Up @@ -158,6 +160,16 @@ export function fallback<const transports extends readonly Transport[]>(
// If we've reached the end of the fallbacks, throw the error.
if (i === transports.length - 1) throw err

// Check if at least one other transport includes the method
includes ??= transports.slice(i + 1).some((transport) => {
const { include, exclude } =
transport({ chain }).config.methods || {}
if (include) return include.includes(method)
if (exclude) return !exclude.includes(method)
return true
})
if (!includes) throw err

// Otherwise, try the next fallback.
return fetch(i + 1)
}
Expand Down

0 comments on commit dee7aa6

Please sign in to comment.