ethers.js icon indicating copy to clipboard operation
ethers.js copied to clipboard

Provider (Ankr) returns wrong eth_blockNumber response, causing EventFilter polling errors

Open mstephen77 opened this issue 1 year ago • 3 comments

Ethers Version

6.10.0

Search Terms

polling, network

Describe the Problem

I've set up a listener for events (Logs, Filters, or any other names) on a JsonRpcProvider through public HTTP endpoint.

For some reason (e.g. connectivity issues, node load, etc.) the library doesn't get a response in a timely manner, and that causes the library to send another request before the previous request resolves (or possibly due to multiple setTimeout calls?). That race condition causes the library to make an EventFilter where filter.fromBlock > filter.toBlock and results in the server returning the following error:

{
  "code": "UNKNOWN_ERROR",
  "error": {
    "code": -32000,
    "message": "invalid block range params"
  },
  "payload": {
    "method": "eth_getLogs",
    "params": [
      {
        "topics": [
          null
        ],
        "fromBlock": "0x123f6dd",
        "toBlock": "0x123f6db"
      }
    ],
    "id": 96,
    "jsonrpc": "2.0"
  },
  "shortMessage": "could not coalesce error"
}

I've a workaround in my code (which is not ideal) like so (subscriber-polling.ts):

  async #poll(blockNumber: number): Promise<void> {
        // The initial block hasn't been determined yet
        if (this.#blockNumber === -2) { return; }

        const filter = copy(this.#filter);
-       filter.fromBlock = this.#blockNumber + 1;
-       filter.toBlock = blockNumber;
+       filter.fromBlock = Math.min(this.#blockNumber + 1, blockNumber);
+       filter.toBlock = Math.max(this.#blockNumber + 1, blockNumber);

        const logs = await this.#provider.getLogs(filter);

        // No logs could just mean the node has not indexed them yet,
        // so we keep a sliding window of 60 blocks to keep scanning
        if (logs.length === 0) {
            if (this.#blockNumber < blockNumber - 60) {
                this.#blockNumber = blockNumber - 60;
            }
            return;
        }

        for (const log of logs) {
            this.#provider.emit(this.#filter, log);

            // Only advance the block number when logs were found to
            // account for networks (like BNB and Polygon) which may
            // sacrifice event consistency for block event speed
-           this.#blockNumber = log.blockNumber;
+           this.#blockNumber = Math.max(this.#blockNumber, log.blockNumber);
        }
    }

Code Snippet

No response

Contract ABI

No response

Errors

No response

Environment

Ethereum (mainnet/ropsten/rinkeby/goerli), node.js (v12 or newer)

Environment (Other)

No response

mstephen77 avatar Feb 01 '24 14:02 mstephen77

Trying to replicate the problem in a Fiddle, turns out the provider (using Ankr at https://rpc.ankr.com/eth) is returning wrong (or cached?) data which causes these errors. Fiddle: https://jsfiddle.net/9jv5hrwe/17

In my console it shows: Request for eth_blockNumber with request id 123 image Request for eth_blockNumber with request id 126 image

I think it would help to check for condition filter.fromBlock > filter.toBlock Let me know if there's anything I could do to help

mstephen77 avatar Feb 02 '24 11:02 mstephen77

Thanks! Is this reliably reproducible?

ricmoo avatar Feb 02 '24 11:02 ricmoo

As of my experience, it often fails within less than <25 new blocks on Ankr public HTTP RPC endpoint. I haven't tested other endpoints so far, and the fix is fairly simple to make sure fromBlock < toBlock (in case we get a cached response from the server).

I opened PR #4573 for this issue.

mstephen77 avatar Feb 02 '24 12:02 mstephen77