EIP-1898 object selectors fail before downstream RPC in eth-json-rpc-middleware 26.0.0
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 62/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- typescript
- Ambito
- api, blockchain
Direzione di ricerca
Il bug si trova in packages/eth-json-rpc-middleware/src/retryOnEmpty.ts, dove un riferimento a blocco che non è una stringa viene trattato come un numero, e in src/block-cache.ts, dove tali selettori possono finire nei bucket di cache numerici o latest. Inizia leggendo entrambi i file intorno alla gestione dei riferimenti a blocco, quindi esegui il src/eip1898-regression.test.ts proposto. È completato quando i casi con selettori oggetto passano in entrambi gli ordini dei middleware e la suite di test esistente del middleware resta verde.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
eth_call with { blockHash, requireCanonical: true } or { blockNumber } can fail in the block-cache/retry-on-empty middleware before the request reaches downstream RPC. The retry path treats the block reference as a string (blockRef.slice). Both middleware orders are covered by the regression.
Versions and source
@metamask/eth-json-rpc-middleware26.0.0@metamask/network-controller37.1.1 (HTTP integration)- Node 24.22.1, Yarn 4.17.1
- Current core source checked at commit
4c3af5af945a1e6496819c90a8b59ff77e815071: the two affected source files are byte-identical to the pinned 26.0.0 source. - This is a reproducible library issue. It does not establish the exact stack inside any installed extension binary.
Request shape
{
jsonrpc: '2.0', id: 1, method: 'eth_call',
params: [
{ to: '0x' + '11'.repeat(20), data: '0x313ce567' },
{ blockHash: '0x' + 'ab'.repeat(32), requireCanonical: true }
]
}
The address and block hash are synthetic. No live chain, account, key or wallet is needed. Wire the middleware to a controlled downstream that checks the selector unchanged and returns a test value; the included native regression contains the full harness.
Expected
Object selectors reach the downstream validator/executor unchanged. Distinct hashes and canonical flags must not share a numeric/latest cache bucket. Downstream rejection must remain observable. Invalid selectors may be rejected downstream; this proposal does not make middleware a complete EIP-1898 validator.
Actual and measured regression
- Native typed regression with both guards removed: 27 expected FAIL, 0 PASS.
- Native custom/Infura-branch HTTP harness: baseline 4 PASS / 16 FAIL; candidate 20 PASS / 0 FAIL. Baseline swapped exactly two published npm 26.0.0 dist files into the candidate network-controller environment; it was not a pristine rebuild of the whole controller.
- The Infura branch uses the native URL construction, then an injected fetch adapter confines traffic to an owned loopback HTTP server. No real Infura connection or credential is used. Retries/failover are disabled in this diagnostic profile.
Proposed minimal change
- In retry-on-empty, bypass numeric block handling for non-string block selectors.
- In block-cache, do not place non-string block selectors into numeric/latest cache buckets.
The separate native regression covers object selectors across five RPC methods, both middleware orders, downstream errors and hash/canonical separation. The HTTP harness adds inflight coalescing, downstream recovery, numeric-cache and local-chainId controls.
Validation scope
Previously completed candidate checks: middleware suite 483 PASS, engine suite 260 PASS / 1 original SKIP; target semantic TypeScript, coverage thresholds, root build and root aggregate lint passed. Root TypeScript lint is a ratchet: raw root tsc still reports 1628 tracked baseline diagnostics. These suites overlap earlier runs and must not be summed.
Not established: full monorepo suite, extension IPC/TLS integration, default retry/failover behavior, installed wallet compatibility or real transaction execution. No signatures or sends were performed.
Please confirm whether this minimal selector-preservation approach belongs in core and advise on the appropriate regression/release path.
Proposed runtime diff (review proposal, not an official release)
--- a/packages/eth-json-rpc-middleware/src/block-cache.ts
+++ b/packages/eth-json-rpc-middleware/src/block-cache.ts
@@ -89,6 +89,11 @@
}
// check blockTag
const blockTag = blockTagForRequest(request);
+
+ // Object selectors must not share numeric/latest cache buckets.
+ if (blockTag !== undefined && typeof blockTag !== 'string') {
+ return false;
+ }
if (blockTag === 'pending') {
return false;
--- a/packages/eth-json-rpc-middleware/src/retryOnEmpty.ts
+++ b/packages/eth-json-rpc-middleware/src/retryOnEmpty.ts
@@ -64,6 +64,11 @@
: undefined;
// omitted blockRef implies "latest"
blockRef ??= 'latest';
+
+ // Preserve EIP-1898 selectors for downstream validation and execution.
+ if (typeof blockRef !== 'string') {
+ return next();
+ }
// skip if non-number block reference
if (['latest', 'pending'].includes(blockRef)) {
Native typed regression patch (27 cases)
--- /dev/null
+++ b/packages/eth-json-rpc-middleware/src/eip1898-regression.test.ts
@@ -0,0 +1,159 @@
+import { PollingBlockTracker } from '@metamask/eth-block-tracker';
+import { InternalProvider } from '@metamask/eth-json-rpc-provider';
+import { JsonRpcEngineV2 } from '@metamask/json-rpc-engine/v2';
+import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine/v2';
+import type { Json, JsonRpcRequest } from '@metamask/utils';
+
+import {
+ createRetryOnEmptyMiddleware,
+ createBlockCacheMiddleware,
+} from './index.js';
+
+type Counts = { tracker: number; terminal: number };
+type Harness = {
+ counts: Counts;
+ invoke: (method: string, params: Json[]) => Promise<Json>;
+};
+
+// Dropping either guard must fail at least one regression.
+describe('EIP-1898 selector preservation', () => {
+ const address = `0x${'11'.repeat(20)}`;
+ const hashOne = `0x${'ab'.repeat(32)}`;
+ const hashTwo = `0x${'cd'.repeat(32)}`;
+ const selectors: Json[] = [
+ { blockHash: hashOne, requireCanonical: true },
+ { blockNumber: '0x123' },
+ ];
+ const owned: (() => Promise<void>)[] = [];
+
+ afterEach(async () => {
+ for (const cleanup of owned.splice(0).reverse()) {
+ await cleanup();
+ }
+ });
+
+ function pipeline(
+ order: string[],
+ terminal: (request: Readonly<JsonRpcRequest>) => Json,
+ ): Harness {
+ const counts: Counts = { tracker: 0, terminal: 0 };
+ const remote = JsonRpcEngineV2.create<
+ JsonRpcMiddleware<JsonRpcRequest, Json>
+ >({
+ middleware: [
+ async (): Promise<Json> => {
+ counts.tracker += 1;
+ throw Error('OBJECT_SELECTOR_REACHED_NUMERIC_TRACKER');
+ },
+ ],
+ });
+ const provider = new InternalProvider({ engine: remote });
+ const tracker = new PollingBlockTracker({
+ provider,
+ keepEventLoopActive: false,
+ });
+ const stages: Record<string, JsonRpcMiddleware<JsonRpcRequest, Json>> = {
+ cache: createBlockCacheMiddleware({ blockTracker: tracker }),
+ retry: createRetryOnEmptyMiddleware({ provider, blockTracker: tracker }),
+ };
+ const engine = JsonRpcEngineV2.create<
+ JsonRpcMiddleware<JsonRpcRequest, Json>
+ >({
+ middleware: [
+ ...order.map((key) => stages[key]),
+ async ({ request }): Promise<Json> => {
+ counts.terminal += 1;
+ return terminal(request);
+ },
+ ],
+ });
+ owned.push(async (): Promise<void> => {
+ await tracker.destroy();
+ await engine.destroy();
+ await remote.destroy();
+ });
+ let requestId = 0;
+ return {
+ counts,
+ invoke: (method: string, params: Json[]): Promise<Json> => {
+ requestId += 1;
+ return engine.handle({ jsonrpc: '2.0', id: requestId, method, params });
+ },
+ };
+ }
+
+ for (const order of [
+ ['cache', 'retry'],
+ ['retry', 'cache'],
+ ]) {
+ for (const method of [
+ 'eth_call',
+ 'eth_getBalance',
+ 'eth_getCode',
+ 'eth_getTransactionCount',
+ 'eth_getStorageAt',
+ ]) {
+ for (const selector of selectors) {
+ it(`${order.join('→')} forwards selector for ${method} ${JSON.stringify(selector)}`, async () => {
+ let params: Json[];
+ if (method === 'eth_call') {
+ params = [{ to: address, data: '0x313ce567' }, selector];
+ } else if (method === 'eth_getStorageAt') {
+ params = [address, '0x0', selector];
+ } else {
+ params = [address, selector];
+ }
+ const harness = pipeline(order, (request) => {
+ expect(request.params).toStrictEqual(params);
+ return '0x1234';
+ });
+ expect(await harness.invoke(method, params)).toBe('0x1234');
+ expect(harness.counts).toStrictEqual({ tracker: 0, terminal: 1 });
+ });
+ }
+ }
+ for (const reason of ['NON_CANONICAL', 'UNKNOWN_BLOCK', 'RATE_LIMIT']) {
+ it(`${order.join('→')} preserves downstream ${reason} without retry/cache`, async () => {
+ const error = Object.assign(new Error(reason), { code: -32000 });
+ let fail = true;
+ const harness = pipeline(order, () => {
+ if (fail) {
+ throw error;
+ }
+ return '0xbeef';
+ });
+ const params: Json[] = [
+ { to: address },
+ { blockHash: hashOne, requireCanonical: true },
+ ];
+ await expect(harness.invoke('eth_call', params)).rejects.toBe(error);
+ fail = false;
+ expect(await harness.invoke('eth_call', params)).toBe('0xbeef');
+ expect(harness.counts).toStrictEqual({ tracker: 0, terminal: 2 });
+ });
+ }
+ }
+
+ it('block cache keeps hash/canonical responses separate across repeated calls', async () => {
+ let index = 0;
+ const responses = ['0xaa', '0xbb', '0xcc'];
+ const harness = pipeline(['cache'], () => {
+ const result = responses[index];
+ index += 1;
+ if (result === undefined) {
+ throw Error('UNEXPECTED_EXTRA_DOWNSTREAM_REQUEST');
+ }
+ return result;
+ });
+ for (const [selector, expected] of [
+ [{ blockHash: hashOne, requireCanonical: true }, '0xaa'],
+ [{ blockHash: hashOne, requireCanonical: false }, '0xbb'],
+ [{ blockHash: hashTwo, requireCanonical: true }, '0xcc'],
+ ] as const) {
+ expect(
+ await harness.invoke('eth_call', [{ to: address }, selector]),
+ ).toBe(expected);
+ }
+ expect(harness.counts).toStrictEqual({ tracker: 0, terminal: 3 });
+ });
+});
- Lingua principale
- TypeScript
- Stelle
- 420
- Fork
- 313
- Merge medio
- 1g 12h
- PR unite (30g)
- 384
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Nessuna guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di MetaMask/core
-
CI: "Validate changelog diffs" job fails on every fork-sourced pull requestForse già presa @Roxen-AS l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
I maintainer di solito rispondono entro 1 giorno
-
platform-api-docs: `scan` strategy misses ESM-only packages, which ship `.d.ts` rather than `.d.cts`Forse già presa @gaohan1994 l’ha presa 12 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
[utils] Replace the `uuid` dependency with `crypto.randomUUID()`Forse già presa @avantikonde l’ha presa 25 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
MetaMask/core#10210 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
-
[utils] `toWei` accepts fractional wei and returns wrong valuesForse già presa @Roxen-AS l’ha presa 11 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
I maintainer di solito rispondono entro 1 giorno
-
eth-json-rpc-middleware: `TransactionParamsStruct` rejects numeric `chainId`, breaking dapp transactionsForse già presa @truehazker l’ha presa 69 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
MetaMask/core#9964 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di MetaMask/core
Issue simili
-
needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 66/100
microsoft/playwright#43875 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
feature
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
calagopus/panel#285 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 Meno di un'ora Idoneità per principianti 88/100
adobe/react-spectrum#10909 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
-
Run the dependency drift checkForse già presa @spaceninja l’ha presa oggi. Aperta
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 72/100
cloudfour/lighthouse-parade#449 ·
I maintainer di solito rispondono entro 1 giorno