Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

getLatestBlock never resolves (and never stops the polling loop) if it encounters an error fetching the latest block number #311

@mcmire

Description

@mcmire

If PollingBlockTracker has not fetched the latest block number yet, getLatestBlock will try to do so. To do this, it will start the block tracker if it's not running, wait for the next block to arrive, stop the block tracker, and then return the fetched block number. This method is asynchronous, so it must be awaited.

However, if fetch the latest block number fails, then the block tracker will never stop, and the promise that getLatestBlock returns will never resolve.

Why does this matter? The use of getLatestBlock to fetch the latest block number is a common prerequisite step in the eth-json-rpc-middleware. In other words, when a request is made through the provider, before it is sent to the RPC node, it first passes through some layers, and some of them require that the latest block number is fetched first. Therefore, if the latest block number cannot be fetched, any request to the network will hang. This is not normally a problem; however, it throws a wrench in the RPC failover project that we've been attempting to complete. When we've configured a failover node for an Infura RPC endpoint, and Infura is down, we don't want the request to hang; we want it to fail so that we can log an error and trigger a failover to Quicknode. Similarly, if we have failed over to Quicknode, and Quickdown is down, we don't want the request to hang, either; we want to log the error so that the SRE team knows to revive Quicknode.

Fixing this problem is a bit tricky because there are a couple of other cases we need to handle that we don't right now:

  • What happens if the block tracker is stopped vs. already running
    • If the block tracker is stopped, then we should not be starting it, we should be able to just make the fetch call.
  • What happens if the method is called multiple times without awaiting (i.e. multiple invocations are running concurrently)
    • If the method has already been called then we should not be making another fetch call. We can use cached promises to guarantee that the first invocation "wins" and subsequent concurrent invocations just await the first one.

Acceptance Criteria

These tests should pass (feel free to tweak these test names etc.):

when the block tracker is not running
  if no other concurrent call exists
    if the latest block number has already been fetched once
      it returns the block number
    if the latest block number has not been fetched yet
      it does not start the block tracker
      if the latest block number is successfully fetched
        it returns the fetched latest block number
      if an error occurs while fetching the latest block number
        it re-throws the error
        it does not emit "error"
  if already called concurrently
    if the latest block number is successfully fetched
      it returns the block number that the other call returns
    if an error occurs while fetching the latest block number
      it throws the error that the other call throws
when the block tracker is already started
  if no other concurrent call exists
    if the latest block number has already been fetched once
      it returns the block number
    if the latest block number has not been fetched yet
      if the latest block number is successfully fetched on the next poll iteration
        it returns the fetched latest block number
        it does not stop the block tracker once complete
      if an error occurs while fetching the latest block number on the next poll iteration
        it throws an error
        it emits "error" if anything is listening to "error"
        it logs an error if nothing is listening to "error"
        it does not stop the block tracker once complete
  if already called concurrently
    if the latest block number is successfully fetched on the next poll iteration
      it returns the block number that the other call returns
      it does not stop the block tracker once complete
    if an error occurs while fetching the latest block number on the next poll iteration
      it throws the error that the other call throws
      it emits "error" only once if anything is listening to "error"
      it logs an error only once if nothing is listening to "error"
      it does not stop the block tracker once complete

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions