Skip to content

Use finalized block head for selected EVM chains - #88

Open
mncdg wants to merge 2 commits into
developfrom
codex/polygon-finalized-block-reader
Open

Use finalized block head for selected EVM chains#88
mncdg wants to merge 2 commits into
developfrom
codex/polygon-finalized-block-reader

Conversation

@mncdg

@mncdg mncdg commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Adds finalized block head handling for selected EVM chains. For Polygon, BSC, and Avalanche, the scanner now uses the RPC finalized block as the scan boundary, falling back to the existing confirmations-based logic only if the finalized block request fails or returns an invalid response.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a mechanism to scan up to the finalized block for specific EVM chains (BSC, Polygon, and Avalanche) instead of relying solely on block confirmations. The review feedback highlights a potential issue where a lagging finalized block (where the finalized block number is less than the last scanned block) could trigger stale RPC protection and progress rollback. To address this, the reviewer suggests capping the target block to be at least the last scanned block for finalized chains, and adding a corresponding unit test to verify that progress is not rolled back when the finalized block lags behind.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +80 to 81
const toBlock = to ?? (await this.getConfirmedBlockNumber(web3, chainConfig));
let fromBlock = from ?? supportedChain.latestBlock;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

When transitioning a chain to use finalized blocks, or if the finalized block temporarily lags behind the head, the finalized block number (toBlock) can be less than the last scanned block (fromBlock).

Under the current implementation, if fromBlock > toBlock, the stale RPC protection logic on line 96 will be triggered, which rolls back the latestBlock in the database to safeBlock (the last event block or toBlock). This causes the node to perform redundant scanning of already processed blocks once the finalized block catches up.

Since finalized blocks are guaranteed not to reorg, we do not need to roll back the progress. Instead, we should cap toBlock to be at least fromBlock for finalized chains, which safely pauses scanning without triggering a database rollback.

Suggested change
const toBlock = to ?? (await this.getConfirmedBlockNumber(web3, chainConfig));
let fromBlock = from ?? supportedChain.latestBlock;
let toBlock = to ?? (await this.getConfirmedBlockNumber(web3, chainConfig));
let fromBlock = from ?? supportedChain.latestBlock;
if (CHAINS_WITH_FINALIZED_BLOCK.has(chainConfig.chainId) && toBlock < fromBlock) {
toBlock = fromBlock;
}

Comment on lines +251 to +266
it('Polygon uses finalized block even when it is behind confirmations fallback', async () => {
const { service, chainId, getPastEventsMock } = await buildService({
chainId: 137,
latestBlock: 1000,
rpcBlockNumber: 1300,
blockConfirmation: 256,
finalizedBlockNumber: 1020,
});

await service.action(chainId);

expect(getPastEventsMock).toBeCalledWith('Sent', {
fromBlock: 1000,
toBlock: 1020,
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure that the scanner does not trigger a database rollback or perform redundant scans when the finalized block is behind the latest scanned block, we should add a dedicated unit test verifying this behavior.

    it('Polygon uses finalized block even when it is behind confirmations fallback', async () => {
      const { service, chainId, getPastEventsMock } = await buildService({
        chainId: 137,
        latestBlock: 1000,
        rpcBlockNumber: 1300,
        blockConfirmation: 256,
        finalizedBlockNumber: 1020,
      });

      await service.action(chainId);

      expect(getPastEventsMock).toBeCalledWith('Sent', {
        fromBlock: 1000,
        toBlock: 1020,
      });
    });

    it('Polygon does not roll back or scan when finalized block is behind latest scanned block', async () => {
      const { service, chainId, getPastEventsMock, updateMock } = await buildService({
        chainId: 137,
        latestBlock: 1000,
        rpcBlockNumber: 1300,
        blockConfirmation: 256,
        finalizedBlockNumber: 980,
      });

      await service.action(chainId);

      expect(getPastEventsMock).not.toBeCalled();
      expect(updateMock).not.toBeCalled();
    });

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant