Skip to content

feat: enable same HTTP methods on websocket#5217

Open
bootcodes wants to merge 36 commits intohiero-ledger:mainfrom
bootcodes:4991-enable-http-features-on-websocket
Open

feat: enable same HTTP methods on websocket#5217
bootcodes wants to merge 36 commits intohiero-ledger:mainfrom
bootcodes:4991-enable-http-features-on-websocket

Conversation

@bootcodes
Copy link
Copy Markdown
Contributor

@bootcodes bootcodes commented Apr 1, 2026

Description

The WebSocket server previously gated method dispatch in WS_CONSTANTS.METHODS. Any method not in that list (i.e. including eth_feeHistory), returned -32601 Method not found even though the relay implemented them.

Related issue(s)

Fixes #4991

Testing Guide

  1. Run your collection of eth_* methods on http relay server and ws relay server and check outcome is the same

Changes from original design (optional)

The bug
In main run : npm run start:ws
Run this script :

  const WebSocket = require('ws');

  async function sendRequest(ws, id) {
    return new Promise((resolve) => {
      ws.once('message', (data) => resolve(JSON.parse(data)));
      ws.send(JSON.stringify({ id, jsonrpc: '2.0', method: 'eth_chainId', params: [] }));
    });
  }

  async function connectAs(spoofedIp) {
    return new Promise((resolve, reject) => {
      const ws = new WebSocket('ws://localhost:8546/ws', {
        headers: { 'X-Forwarded-For': spoofedIp }
      });
      ws.on('open', () => resolve(ws));
      ws.on('error', reject);
    });
  }

  async function run() {
    const LIMIT = 3;

    // Connect as two different fake IPs
    const wsA = await connectAs('10.0.0.1');
    const wsB = await connectAs('10.0.0.2');

    // Exhaust the rate limit for IP A
    console.log('--- Exhausting rate limit for 10.0.0.1 ---');
    for (let i = 1; i <= LIMIT; i++) {
      const r = await sendRequest(wsA, i);
      console.log(`IP A req ${i}: ${r.result ?? JSON.stringify(r.error)}`);
    }

    // IP A's next request should be rate-limited
    const rA = await sendRequest(wsA, 99);
    console.log(`IP A req ${LIMIT+1}: ${rA.result ?? JSON.stringify(rA.error)}`);

    // KEY TEST: Does IP B get rate-limited too?
    console.log('\n--- Testing IP B (should be independent) ---');
    const rB = await sendRequest(wsB, 100);
    console.log(`IP B req 1: ${rB.result ?? JSON.stringify(rB.error)}`);

    if (rB.error?.code === -32605) {
      console.log('\nBUG CONFIRMED: IP B is rate-limited despite never exceeding its quota.');
      console.log('   app.proxy = true is missing — both IPs appear as 127.0.0.1');
    } else {
      console.log('\n IPs are independent — app.proxy = true is working correctly.');
    }

    wsA.close();
    wsB.close();
  }

  run().catch(console.error);"

Fix the bug

webSocketServer.ts was not setting app.proxy = true on its Koa instance.
Two features silently broke as a result:

  • WS_CONNECTION_LIMIT_PER_IP every WebSocket client appeared to originate from the same IP (the proxy's), so the per-IP connection limit either blocked all clients simultaneously once the proxy's bucket was full, or was trivially bypassed by any single client.
  • IP rate limiting on WS RPC methods — all clients shared one rate-limit counter, allowing one heavy client to exhaust the window for everyone else, and preventing any meaningful per-client throttling.

The HTTP server (server.ts) already had app.proxy = true and the RFC 7239 Forwarded→X-Forwarded-For translation middleware.

The fix

Extracted the proxy setup into src/server/utils/proxyUtils.ts and called it from both servers. The extracted utility:

  1. Sets app.proxy = true
  2. Translates RFC 7239 Forwarded headers to X-Forwarded-For

Verification

tests/ws-server/unit/proxyIpIsolation.spec.ts contains two tests that directly prove the behavior:

  • proxy=false (baseline): with app.proxy manually reverted to false, two clients with different X-Forwarded-For headers collapse to 127.0.0.1 and share one rate-limit bucket — IP B gets a -32605 error despite never making a request
  • proxy=true (fix): with app.proxy = true (set by applyProxyMiddleware), each spoofed IP has its own independent counter — IP A's exhausted bucket does not affect IP B

Additional work needed (optional)

Migrate duplicate eth_* acceptance tests to tests/protocol/. Methods tested independently in both
tests/server/acceptance/ and tests/ws-server/acceptance/ with identical assertions are candidates:
eth_blockNumber, eth_gasPrice, eth_getLogs, eth_getBalance, eth_call, eth_estimateGas, etc.

When migrating, delete both originals and replace with a single parameterized test in tests/protocol/

Related issue : #5223

Checklist

  • I've assigned an assignee to this PR and related issue(s) (if applicable)
  • I've assigned a label to this PR and related issue(s) (if applicable)
  • I've assigned a milestone to this PR and related issue(s) (if applicable)
  • I've updated documentation (code comments, README, etc. if applicable)
  • I've done sufficient testing (unit, integration, etc.)

Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
@bootcodes bootcodes self-assigned this Apr 1, 2026
@bootcodes bootcodes added the enhancement New feature or request label Apr 1, 2026
@bootcodes bootcodes added this to the 0.76.1 milestone Apr 1, 2026
@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 1, 2026

Not up to standards ⛔

🔴 Issues 70 medium · 2 minor

Alerts:
⚠ 72 issues (≤ 0 issues of at least minor severity)

Results:
72 new issues

Category Results
BestPractice 19 medium
CodeStyle 2 minor
Complexity 51 medium

View in Codacy

🟢 Metrics 96 complexity · -2 duplication

Metric Results
Complexity 96
Duplication -2

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

@quiet-node quiet-node modified the milestones: 0.76.1, 0.77.0 Apr 1, 2026
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
@bootcodes bootcodes marked this pull request as ready for review April 2, 2026 11:02
@bootcodes bootcodes requested review from a team as code owners April 2, 2026 11:02
Copy link
Copy Markdown
Contributor

@BartoszSolkaBD BartoszSolkaBD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks nice 😄
I like the idea of creating RpcProtocolClient for unified test cases.

Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
@bootcodes bootcodes requested a review from BartoszSolkaBD April 7, 2026 13:00
Copy link
Copy Markdown
Contributor

@Ferparishuertas Ferparishuertas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! Some nits

  • you mention that there could be some duplicated acceptance tests. Shall we open a follow up issue?
  • I am not seeing rate limiting test for WebSocket methods. As we have added all, Can you implement some tests?

@bootcodes
Copy link
Copy Markdown
Contributor Author

Good work! Some nits

  • you mention that there could be some duplicated acceptance tests. Shall we open a follow up issue?
  • I am not seeing rate limiting test for WebSocket methods. As we have added all, Can you implement some tests?

@Ferparishuertas - I opened an issue for unified acceptance tests here

jasuwienas
jasuwienas previously approved these changes Apr 9, 2026
Copy link
Copy Markdown
Contributor

@jasuwienas jasuwienas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Copy link
Copy Markdown
Contributor

@quiet-node quiet-node left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work overall. Left some items and concerns. Also looks like codacy is complaining about many files https://github.com/hiero-ledger/hiero-json-rpc-relay/pull/5217/checks?check_run_id=70629267661.

Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Copy link
Copy Markdown
Contributor

@quiet-node quiet-node left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Only two items left

Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
Signed-off-by: Thomas Boot <thomas.boot@swirldslabs.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 64.45783% with 59 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/server/utils/proxyUtils.ts 58.74% 59 Missing ⚠️

❌ Your project check has failed because the head coverage (70.53%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

❗ There is a different number of reports uploaded between BASE (c95f3f9) and HEAD (27bd985). Click for more details.

HEAD has 18 uploads less than BASE
Flag BASE (c95f3f9) HEAD (27bd985)
22 4
@@             Coverage Diff             @@
##             main    #5217       +/-   ##
===========================================
- Coverage   95.93%   70.53%   -25.40%     
===========================================
  Files         146      147        +1     
  Lines       25140    25215       +75     
  Branches     2044      724     -1320     
===========================================
- Hits        24117    17786     -6331     
- Misses       1001     7406     +6405     
- Partials       22       23        +1     
Files with missing lines Coverage Δ
src/server/server.ts 81.30% <100.00%> (-8.01%) ⬇️
src/ws-server/controllers/jsonRpcController.ts 81.28% <100.00%> (-17.55%) ⬇️
src/ws-server/utils/constants.ts 100.00% <100.00%> (ø)
src/ws-server/utils/utils.ts 88.17% <100.00%> (-11.83%) ⬇️
src/ws-server/webSocketServer.ts 66.99% <100.00%> (-29.41%) ⬇️
src/server/utils/proxyUtils.ts 58.74% <58.74%> (ø)

... and 86 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@quiet-node quiet-node left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great but looks like Codacy and codecov are complaining quite hard. can you please take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebSocket server should offer the same features as the HTTP server

5 participants