EIP-1898 object selectors fail before downstream RPC in eth-json-rpc-middleware 26.0.0
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 62/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- typescript
- Lĩnh vực
- api, blockchain
Hướng nghiên cứu
Lỗi nằm ở packages/eth-json-rpc-middleware/src/retryOnEmpty.ts, nơi một tham chiếu khối không phải chuỗi bị xử lý như số, và ở src/block-cache.ts, nơi các selector này có thể rơi vào các bucket cache dạng số hoặc latest. Hãy bắt đầu bằng cách đọc cả hai tệp quanh phần xử lý tham chiếu khối, sau đó chạy src/eip1898-regression.test.ts được đề xuất. Hoàn thành khi các trường hợp selector dạng object đều pass ở cả hai thứ tự middleware và bộ test middleware hiện có vẫn xanh.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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 });
+ });
+});
- Ngôn ngữ chính
- TypeScript
- Star
- 420
- Fork
- 313
- Merge trung bình
- 1 ngày 12 giờ
- Pull request đã merge (30 ngày)
- 384
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Không có hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của MetaMask/core
-
CI: "Validate changelog diffs" job fails on every fork-sourced pull requestCó thể đã có người làm @Roxen-AS đã nhận 12 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
Maintainer thường phản hồi trong vòng 1 ngày
-
platform-api-docs: `scan` strategy misses ESM-only packages, which ship `.d.ts` rather than `.d.cts`Có thể đã có người làm @gaohan1994 đã nhận 13 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[utils] Replace the `uuid` dependency with `crypto.randomUUID()`Có thể đã có người làm @avantikonde đã nhận 26 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
MetaMask/core#10210 · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[utils] `toWei` accepts fractional wei and returns wrong valuesCó thể đã có người làm @Roxen-AS đã nhận 12 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
Maintainer thường phản hồi trong vòng 1 ngày
-
eth-json-rpc-middleware: `TransactionParamsStruct` rejects numeric `chainId`, breaking dapp transactionsCó thể đã có người làm @truehazker đã nhận 70 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
MetaMask/core#9964 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của MetaMask/core
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
riverqueue/river#1576 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
[bug] A prompt queued during a run is assessed without its textCó thể đã có người làm @jwiegley đã nhận hôm nay. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
DevMortimer/pi-warden#211 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 84/100
mattermost/compass-design#191 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Add unit tests for the organization admin role checkCó thể đã có người làm @SaffuSafwan đã nhận hôm nay. Đang mởarea/core good first issue help wanted
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
elmohq/elmo#1095 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
HorusGoul/trizum#559 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày