Skip to content

Comments

(Requires Audit) PLEX-2473 LogPoller Fix Reorg Handing on Replay (Part 1)#355

Open
dhaidashenko wants to merge 3 commits intodevelopfrom
feature/PLEX-2473-fix-reorg-handing-on-replay
Open

(Requires Audit) PLEX-2473 LogPoller Fix Reorg Handing on Replay (Part 1)#355
dhaidashenko wants to merge 3 commits intodevelopfrom
feature/PLEX-2473-fix-reorg-handing-on-replay

Conversation

@dhaidashenko
Copy link
Contributor

@dhaidashenko dhaidashenko commented Feb 12, 2026

TestCase that were previously failing:

  1. LogPoller processes blocks to block 11
  2. Reorg replaces block 11 with a new block (some additional blocks may be added on top of it)
  3. Replay is initiated from block below 11.

Expected behaviour:

  1. LogPoller must replace reorged block 11 with a new data.
  2. DB must not contain at any point logs from both old and new block 11.
  3. Finality Violation must not occur, since chain did not violate finality depth.

Supports: #356

@github-actions
Copy link
Contributor

github-actions bot commented Feb 12, 2026

⚠️ API Diff Results - Breaking changes detected

📦 Module: github-com-smartcontractkit-chainlink-evm

🔴 Breaking Changes (2)

pkg/logpoller.(*logPoller) (1)
  • PollAndSaveLogs — Type changed:
func(
  context.Context, 
  - int64
  + int64, 
  + bool
)
pkg/logpoller.LogPollerTest (1)
  • PollAndSaveLogs — Type changed:
func(
  context.Context, 
  - int64
  + int64, 
  + bool
)

📄 View full apidiff report

@dhaidashenko dhaidashenko force-pushed the feature/PLEX-2473-fix-reorg-handing-on-replay branch from 73f8c25 to fd687cd Compare February 13, 2026 11:46
@dhaidashenko dhaidashenko force-pushed the feature/PLEX-2473-fix-reorg-handing-on-replay branch from fd687cd to abead20 Compare February 13, 2026 12:35
@dhaidashenko dhaidashenko changed the title PLEX-2473 LogPoller Fix Reorg Handing on Replay PLEX-2473 LogPoller Fix Reorg Handing on Replay (Part 1) Feb 13, 2026
@dhaidashenko dhaidashenko changed the title PLEX-2473 LogPoller Fix Reorg Handing on Replay (Part 1) (Requires Audit) PLEX-2473 LogPoller Fix Reorg Handing on Replay (Part 1) Feb 13, 2026
@dhaidashenko dhaidashenko marked this pull request as ready for review February 13, 2026 16:29
@dhaidashenko dhaidashenko requested a review from a team as a code owner February 13, 2026 16:29
Copilot AI review requested due to automatic review settings February 13, 2026 16:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes reorg (reorganization) handling in the LogPoller when replay operations encounter blocks that have been reorged. The core issue was that when LogPoller processed blocks up to block N, then a reorg replaced block N, and a replay was initiated from a block below N, the old and new versions of block N could coexist in the database, or a false finality violation could occur.

Changes:

  • Added isReplay boolean parameter to PollAndSaveLogs method and related internal functions to enable special reorg detection logic during replay operations
  • Extracted headerByNumber helper method to reduce code duplication when fetching block headers from RPC
  • Enhanced getCurrentBlockMaybeHandleReorg to detect reorgs during replay by checking if current and latest blocks in DB match their RPC counterparts
  • Refactored reorg handling logic into a new handleReorg method for better code organization
  • Added comprehensive test case covering three scenarios: replay immediately after reorg, replay a few blocks after reorg, and replay after reorged block is finalized

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
pkg/logpoller/log_poller.go Core implementation changes: added isReplay parameter, extracted headerByNumber and handleReorg methods, enhanced reorg detection logic for replay scenarios
pkg/logpoller/log_poller_test.go Updated all test calls to include isReplay=false parameter, added comprehensive TestLogPoller_Reorg_On_Replay test with three test cases
pkg/logpoller/log_poller_internal_test.go Updated all test calls to include isReplay=false parameter
pkg/logpoller/helper_test.go Updated PollAndSaveLogs wrapper to pass isReplay=false

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Unheilbar
Unheilbar previously approved these changes Feb 17, 2026
Krish-vemula
Krish-vemula previously approved these changes Feb 17, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

require.Len(t, logs, 1)
require.Equal(t, newLogData, big.NewInt(0).SetBytes(logs[0].Data).Int64(), "Log data should match the log from the new block, indicating that the old block's log was properly removed during replay")
// Ensure reorged block was replaced by a new one
dbBlock, err := th.ORM.SelectBlockByNumber(testutils.Context(t), reorgedBlock.Number().Int64())
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

Inconsistent context usage: This line uses testutils.Context(t) while the rest of the test function uses t.Context() (lines 2248, 2251, 2254). For consistency and to ensure the context is properly tied to the test lifecycle, consider using t.Context() here as well.

Suggested change
dbBlock, err := th.ORM.SelectBlockByNumber(testutils.Context(t), reorgedBlock.Number().Int64())
dbBlock, err := th.ORM.SelectBlockByNumber(t.Context(), reorgedBlock.Number().Int64())

Copilot uses AI. Check for mistakes.
require.NoError(t, err)
require.Equal(t, int64(reorgedBlockNumber), reorgedBlock.Number().Int64())

// Replace block 11 with a new block and bury it under 1 new block
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

Misleading comment: The comment says "bury it under 1 new block" but the actual number of blocks added after the reorg depends on tc.numberOfBlocksAfterReorg, which varies per test case (0, 1, or 5). Consider updating the comment to accurately reflect this, such as "Replace block 11 with a new block and optionally add additional blocks on top of it".

Suggested change
// Replace block 11 with a new block and bury it under 1 new block
// Replace the reorged block with a new block and add a variable number of blocks on top

Copilot uses AI. Check for mistakes.
if pkgerrors.Is(err, sql.ErrNoRows) {
lp.lggr.Criticalw("Unexpected state. Expected at least one block to be present in the db when handling reorg, but got no rows", "currentBlockNumber", currentBlock.Number, "err", err)
}
return nil, pkgerrors.Wrap(err, "failed to get latest finalized block from db")
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

Misleading error message: The error message says "failed to get latest finalized block from db" but the function actually calls orm.SelectLatestBlock (line 1063), not SelectLatestFinalizedBlock. The error message should be updated to "failed to get latest block from db" for accuracy.

Suggested change
return nil, pkgerrors.Wrap(err, "failed to get latest finalized block from db")
return nil, pkgerrors.Wrap(err, "failed to get latest block from db")

Copilot uses AI. Check for mistakes.

// PollAndSaveLogs On startup/crash current is the first block after the last processed block.
// currentBlockNumber is the block from where new logs are to be polled & saved. Under normal
// conditions this would be equal to lastProcessed.BlockNumber + 1.
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

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

Missing parameter documentation: The function comment should document the new isReplay parameter, explaining that it should be true when called during a replay operation and false during normal polling. This helps future maintainers understand when to use each value.

Suggested change
// conditions this would be equal to lastProcessed.BlockNumber + 1.
// conditions this would be equal to lastProcessed.BlockNumber + 1. The isReplay flag should be
// true when PollAndSaveLogs is called as part of a replay operation and false during normal polling.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants