-
-
Notifications
You must be signed in to change notification settings - Fork 83
getLatestBlock never resolves (and never stops the polling loop) if it encounters an error fetching the latest block number #311
Description
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