Repository: smartcontractkit/chainlink
Pull request #16056: [CCIP-4171] integration-tests/smoke/ccip: add re-org tests
Author: login: makramkd
Created: 2025-01-24T10:42:45Z | Merged: 2025-04-09T09:37:32Z

Add re-org tests that cover the following cases:
* re-org on source less than finality, should be fine
* re-org on dest less than finality, should be fine
* re-org on source greater than finality, that source is no longer processed
* re-org on dest greater than finality, bridge stops


Diff:
diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml
index 2f065194375..f8e288d5490 100644
--- a/.github/e2e-tests.yml
+++ b/.github/e2e-tests.yml
@@ -1019,6 +1019,32 @@ runner-test-matrix:
 
   # START: CCIPv1.6 tests
 
+  - id: smoke/ccip/ccip_reorg_test.go:LessThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - Nightly E2E Tests
+    test_cmd: cd integration-tests/smoke/ccip && go test -run "Test_CCIPReorg_BelowFinality_OnSource|Test_CCIPReorg_BelowFinality_OnDest" ccip_reorg_test.go -timeout 25m -test.parallel=1 -count=1 -json
+    pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
+    test_env_vars:
+      E2E_TEST_SELECTED_NETWORK: SIMULATED_1_DEEPER_FINALITY,SIMULATED_2_DEEPER_FINALITY
+      E2E_JD_VERSION: 0.9.0
+      CCIP_V16_TEST_ENV: docker
+
+  - id: smoke/ccip/ccip_reorg_test.go:GreaterThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - Nightly E2E Tests
+    test_cmd: cd integration-tests/smoke/ccip && go test -run "Test_CCIPReorg_GreaterThanFinality_OnSource|Test_CCIPReorg_GreaterThanFinality_OnDest" ccip_reorg_test.go -timeout 25m -test.parallel=1 -count=1 -json
+    pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
+    test_env_vars:
+      E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3
+      E2E_JD_VERSION: 0.9.0
+      CCIP_V16_TEST_ENV: docker
+
   - id: smoke/ccip/ccip_token_price_updates_test.go:*
     path: integration-tests/smoke/ccip/ccip_token_price_updates_test.go
     test_env_type: docker
diff --git a/deployment/ccip/changeset/testhelpers/test_environment.go b/deployment/ccip/changeset/testhelpers/test_environment.go
index 502f68cd22d..84af1e119aa 100644
--- a/deployment/ccip/changeset/testhelpers/test_environment.go
+++ b/deployment/ccip/changeset/testhelpers/test_environment.go
@@ -50,6 +50,12 @@ const (
 	ENVTESTTYPE         = "CCIP_V16_TEST_ENV"
 )
 
+type LogMessageToIgnore struct {
+	Msg    string
+	Reason string
+	Level  zapcore.Level
+}
+
 type TestConfigs struct {
 	Type      EnvType // set by env var CCIP_V16_TEST_ENV, defaults to Memory
 	CreateJob bool
@@ -74,7 +80,18 @@ type TestConfigs struct {
 	LinkPrice                  *big.Int
 	WethPrice                  *big.Int
 	BlockTime                  time.Duration
-	CLNodeConfigOpts           []memory.ConfigOpt
+	// Test env related configs
+
+	// LogMessagesToIgnore are log messages emitted by the chainlink node that cause
+	// the test to auto-fail if they were logged.
+	// In some tests we don't want this to happen where a failure is expected, e.g
+	// we are purposely re-orging beyond finality.
+	LogMessagesToIgnore []LogMessageToIgnore
+
+	// ExtraConfigTomls contains the filenames of additional toml files to be loaded
+	// to potentially override default configs.
+	ExtraConfigTomls []string
+	CLNodeConfigOpts []memory.ConfigOpt
 }
 
 func (tc *TestConfigs) Validate() error {
@@ -119,6 +136,18 @@ func DefaultTestConfigs() *TestConfigs {
 
 type TestOps func(testCfg *TestConfigs)
 
+func WithLogMessagesToIgnore(logMessages []LogMessageToIgnore) TestOps {
+	return func(testCfg *TestConfigs) {
+		testCfg.LogMessagesToIgnore = logMessages
+	}
+}
+
+func WithExtraConfigTomls(extraTomls []string) TestOps {
+	return func(testCfg *TestConfigs) {
+		testCfg.ExtraConfigTomls = extraTomls
+	}
+}
+
 func WithCLNodeConfigOpts(opts ...memory.ConfigOpt) TestOps {
 	return func(testCfg *TestConfigs) {
 		testCfg.CLNodeConfigOpts = opts
diff --git a/deployment/environment/nodeclient/chainlink_models.go b/deployment/environment/nodeclient/chainlink_models.go
index 7d895eead36..fe7e704122c 100644
--- a/deployment/environment/nodeclient/chainlink_models.go
+++ b/deployment/environment/nodeclient/chainlink_models.go
@@ -33,17 +33,23 @@ type ResponseSlice struct {
 	Data []map[string]interface{}
 }
 
+// HealthCheck corresponds to presenters.Check.
+type HealthCheck struct {
+	Name   string `json:"name"`
+	Status string `json:"status"`
+	Output string `json:"output"`
+}
+
+// HealthResponseDetails is the generic model for services health statuses.
+type HealthResponseDetail struct {
+	Type       string      `json:"type"`
+	ID         string      `json:"id"`
+	Attributes HealthCheck `json:"attributes"`
+}
+
 // HealthResponse is the generic model for services health statuses
 type HealthResponse struct {
-	Data []struct {
-		Type       string `json:"type"`
-		ID         string `json:"id"`
-		Attributes struct {
-			Name   string `json:"name"`
-			Status string `json:"status"`
-			Output string `json:"output"`
-		} `json:"attributes"`
-	} `json:"data"`
+	Data []HealthResponseDetail `json:"data"`
 }
 
 // Response is the generic model that can be used for all Chainlink API responses
diff --git a/integration-tests/smoke/ccip/ccip_add_chain_test.go b/integration-tests/smoke/ccip/ccip_add_chain_test.go
index dbc9f4bcf4b..aeff481984b 100644
--- a/integration-tests/smoke/ccip/ccip_add_chain_test.go
+++ b/integration-tests/smoke/ccip/ccip_add_chain_test.go
@@ -11,7 +11,7 @@ import (
 	"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext"
 
 	"github.com/smartcontractkit/chainlink/deployment"
-	state2 "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
 	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_6"
 	"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
 
@@ -54,7 +54,7 @@ func Test_AddChain(t *testing.T) {
 	/////////////////////////////////////
 	e = setupChain(t, e, tEnv, toDeploy, false)
 
-	state, err := state2.LoadOnchainState(e.Env)
+	state, err := ccipcs.LoadOnchainState(e.Env)
 	require.NoError(t, err)
 	tEnv.UpdateDeployedEnvironment(e)
 	// check RMNRemote is up and RMNProxy is correctly wired.
@@ -174,7 +174,7 @@ func Test_AddChain(t *testing.T) {
 	// transferred to MCMS.
 	e = setupChain(t, e, tEnv, []uint64{remainingChain}, true)
 
-	state, err = state2.LoadOnchainState(e.Env)
+	state, err = ccipcs.LoadOnchainState(e.Env)
 	require.NoError(t, err)
 	tEnv.UpdateDeployedEnvironment(e)
 
@@ -198,7 +198,7 @@ func Test_AddChain(t *testing.T) {
 		true, // mcmsEnabled
 	)
 
-	state, err = state2.LoadOnchainState(e.Env)
+	state, err = ccipcs.LoadOnchainState(e.Env)
 	require.NoError(t, err)
 
 	assertChainWiringOutbound(
@@ -316,7 +316,7 @@ func Test_AddChain(t *testing.T) {
 		true,  // mcmsEnabled
 	)
 
-	state, err = state2.LoadOnchainState(e.Env)
+	state, err = ccipcs.LoadOnchainState(e.Env)
 	require.NoError(t, err)
 
 	assertChainWiringOutbound(
@@ -535,7 +535,7 @@ func setupChain(t *testing.T, e testhelpers.DeployedEnv, tEnv testhelpers.TestEn
 // It doesn't check that the existingChains have the newChain enabled as a dest.
 func assertChainWiringInbound(
 	t *testing.T,
-	state state2.CCIPOnChainState,
+	state ccipcs.CCIPOnChainState,
 	newChain uint64,
 	existingChains []uint64,
 	testRouterEnabled bool,
@@ -581,7 +581,7 @@ func assertChainWiringInbound(
 // It doesn't check that the newChain can process the requests.
 func assertChainWiringOutbound(
 	t *testing.T,
-	state state2.CCIPOnChainState,
+	state ccipcs.CCIPOnChainState,
 	newChain uint64,
 	existingChains []uint64,
 	testRouterEnabled bool,
@@ -731,7 +731,7 @@ func offRampSourceUpdates(t *testing.T, dests []uint64, sources []uint64, testRo
 	return
 }
 
-func assertRMNRemoteAndProxyState(t *testing.T, chains []uint64, state state2.CCIPOnChainState) {
+func assertRMNRemoteAndProxyState(t *testing.T, chains []uint64, state ccipcs.CCIPOnChainState) {
 	for _, chain := range chains {
 		require.NotEqual(t, common.Address{}, state.Chains[chain].RMNRemote.Address())
 		_, err := state.Chains[chain].RMNRemote.GetCursedSubjects(&bind.CallOpts{
@@ -755,7 +755,7 @@ func transferToMCMSAndRenounceTimelockDeployer(
 	t *testing.T,
 	e testhelpers.DeployedEnv,
 	chains []uint64,
-	state state2.CCIPOnChainState,
+	state ccipcs.CCIPOnChainState,
 	onlyChainContracts bool,
 ) {
 	apps := make([]commonchangeset.ConfiguredChangeSet, 0, len(chains)+1)
diff --git a/integration-tests/smoke/ccip/ccip_reorg_test.go b/integration-tests/smoke/ccip/ccip_reorg_test.go
new file mode 100644
index 00000000000..7107cd0573e
--- /dev/null
+++ b/integration-tests/smoke/ccip/ccip_reorg_test.go
@@ -0,0 +1,533 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/onramp"
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
+	// TODO: timing out right away, needs to get fixed.
+	// gomega.NewWithT(t).Consistently(func() bool {
+	// 	it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+	// 	require.NoError(t, err)
+	// 	return !it.Next()
+	// }, 1*time.Minute, 10*time.Second, tests.WaitTimeout(t)).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSource := allChains[0]
+	nonReorgSource := allChains[1]
+	destSelector := allChains[2]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSource], nil)
+
+	// Multi-lane setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSource, destSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, nonReorgSource, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send messages from both sources
+	sendCCIPMessage(t, e.Env, state, reorgSource, destSelector, l)
+	sendCCIPMessage(t, e.Env, state, nonReorgSource, destSelector, l)
+
+	// Deep reorg execution
+	performReorg(t, reorgSourceClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, reorgSource),
+		getLogPollerService(t, reorgSource),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Validate non-reorged source
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		nonReorgSource,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed for non-reorged source")
+
+	// Commit absence check on the reorged source
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+		require.NoError(t, err)
+		var found bool
+	outer:
+		for it.Next() {
+			for _, mr := range it.Event.UnblessedMerkleRoots {
+				if mr.SourceChainSelector == reorgSource {
+					found = true
+					break outer
+				}
+			}
+		}
+		return !found
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+func getServiceHealth(serviceName string, healthResponses []nodeclient.HealthResponseDetail) nodeclient.HealthCheck {
+	for _, d := range healthResponses {
+		if d.Attributes.Name == serviceName {
+			return d.Attributes
+		}
+	}
+
+	return nodeclient.HealthCheck{}
+}
+
+// Helper to initialize common test components
+func setupReorgTest(t *testing.T, testOpts ...testhelpers.TestOps) (
+	testhelpers.DeployedEnv,
+	logging.Logger,
+	*testsetups.DeployedLocalDevEnvironment,
+	[]string,
+	ccipcs.CCIPOnChainState,
+) {
+	require.Equal(t, os.Getenv(testhelpers.ENVTESTTYPE), string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments")
+
+	l := logging.GetTestLogger(t)
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(t, testOpts...)
+	dockerEnv := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+	nonBootstrapP2PIDs := getNonBootstrapP2PIDs(nodeInfos)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	return e, l, dockerEnv, nonBootstrapP2PIDs, state
+}
+
+// Extract non-bootstrap P2P IDs
+func getNonBootstrapP2PIDs(nodeInfos deployment.Nodes) []string {
+	ids := make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		ids = append(ids, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+	return ids
+}
+
+// Build chain selector to RPC URL map
+func buildChainSelectorToRPCURLMap(t *testing.T, dockerEnv *testsetups.DeployedLocalDevEnvironment) map[uint64]string {
+	devEnvConfig := dockerEnv.GetDevEnvConfig()
+	require.NotNil(t, devEnvConfig)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range devEnvConfig.Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+	return chainSelToRPCURL
+}
+
+// Get log poller service name
+func getLogPollerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.LogPoller", chain.EvmChainID)
+}
+
+func getHeadTrackerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.HeadTracker", chain.EvmChainID)
+}
+
+// Send CCIP message helper
+func sendCCIPMessage(
+	t *testing.T,
+	env deployment.Environment,
+	state ccipcs.CCIPOnChainState,
+	sourceSelector, destSelector uint64,
+	l logging.Logger,
+) *onramp.OnRampCCIPMessageSent {
+	msgEvent := testhelpers.TestSendRequest(t, env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+
+	l.Info().
+		Str("messageID", hexutil.Encode(msgEvent.Message.Header.MessageId[:])).
+		Uint64("blockNumber", msgEvent.Raw.BlockNumber).
+		Str("blockHash", msgEvent.Raw.BlockHash.String()).
+		Msg("Sent CCIP message")
+
+	return msgEvent
+}
+
+// Check finality violations helper
+// Uses require.Eventually or gomega.Consistently based on expected count
+func checkFinalityViolations(
+	t *testing.T,
+	nodeAPIs []*nodeclient.ChainlinkClient,
+	nonBootstrapP2PIDs []string,
+	headTrackerServiceName,
+	logPollerServiceName string,
+	l logging.Logger,
+	expectedCount int,
+	timeout time.Duration,
+	interval time.Duration,
+) {
+	checkFunc := func() bool {
+		violated := 0
+		headTrackerViolated := 0
+		logPollerViolated := 0
+		for _, node := range nodeAPIs {
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+			if len(p2pKeys.Data) == 0 || !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).Msg("Skipping bootstrap node")
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			lpHealth := getServiceHealth(logPollerServiceName, resp.Data)
+			htHealth := getServiceHealth(headTrackerServiceName, resp.Data)
+			var lpViolated, htViolated bool
+			if lpHealth.Status == "failing" && lpHealth.Output == commontypes.ErrFinalityViolated.Error() {
+				logPollerViolated++
+				lpViolated = true
+			}
+
+			if htHealth.Status == "failing" && (htHealth.Output == commontypes.ErrFinalityViolated.Error() || strings.Contains(htHealth.Output, "finality violated")) {
+				headTrackerViolated++
+				htViolated = true
+				l.Info().
+					Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+					Str("output", htHealth.Output).
+					Str("headTrackerService", headTrackerServiceName).
+					Msg("Head tracker finality violation")
+			}
+
+			l.Info().
+				Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+				Str("htStatus", htHealth.Status).
+				Str("htOutput", htHealth.Output).
+				Str("lpStatus", lpHealth.Status).
+				Str("lpOutput", lpHealth.Output).
+				Str("headTrackerService", headTrackerServiceName).
+				Bool("headTrackerFinalityViolated", htViolated).
+				Bool("logPollerFinalityViolated", lpViolated).
+				Str("logPollerService", logPollerServiceName).
+				Msg("finality violation checks")
+
+			if lpViolated || htViolated {
+				violated++
+			}
+		}
+
+		l.
+			Info().
+			Int("violated", violated).
+			Int("expectedCount", expectedCount).
+			Int("headTrackerViolated", headTrackerViolated).
+			Int("logPollerViolated", logPollerViolated).
+			Msg("Checking finality violations")
+		return violated == expectedCount
+	}
+	if expectedCount > 0 {
+		require.Eventually(t, checkFunc, timeout, interval)
+	} else {
+		gomega.NewWithT(t).Consistently(checkFunc, timeout, interval).Should(gomega.BeTrue())
+	}
+}
+
+// waitForLogPollerFilters waits for log poller filters to be registered
+func waitForLogPollerFilters(l logging.Logger) {
+	l.Info().Msg("Waiting for log poller filters to get registered")
+	time.Sleep(30 * time.Second) // Consider making duration configurable if needed
+}
+
+// waitForBlockNumber waits until chain reaches target block number
+func waitForBlockNumber(
+	t *testing.T,
+	client *ctf_client.RPCClient,
+	targetBlock uint64,
+	timeout time.Duration,
+	checkInterval time.Duration,
+	l logging.Logger,
+) {
+	require.Eventually(t, func() bool {
+		bn, err := client.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("currentBlock", bn).
+			Uint64("targetBlock", targetBlock).
+			Msg("Waiting for chain progression")
+		return bn >= int64(targetBlock) //nolint:gosec // no risk of overflow
+	}, timeout, checkInterval, "Timeout waiting for block number")
+}
+
+// performReorg executes a chain reorg and verifies its success
+func performReorg(
+	t *testing.T,
+	client *ctf_client.RPCClient,
+	reorgDepth int,
+	l logging.Logger,
+) {
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Msg("Starting blockchain reorg")
+
+	// Get current block before reorg for verification
+	preReorgBlock, err := client.BlockNumber()
+	require.NoError(t, err)
+
+	err = client.GethSetHead(reorgDepth)
+	require.NoError(t, err, "Failed to execute reorg")
+
+	// Verify post-reorg state
+	postReorgBlock, err := client.BlockNumber()
+	require.NoError(t, err)
+
+	l.Info().
+		Int64("preReorgBlock", preReorgBlock).
+		Int64("postReorgBlock", postReorgBlock).
+		Msg("Reorg completed")
+
+	require.Less(t, postReorgBlock, preReorgBlock,
+		"Block number should decrease after reorg")
+}
diff --git a/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnDest.toml b/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnDest.toml
new file mode 100644
index 00000000000..6249d7951a6
--- /dev/null
+++ b/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnDest.toml
@@ -0,0 +1,12 @@
+#! These are intended to be overrides for the test Test_CCIPReorg_BelowFinality_OnDest.
+[NodeConfig.ChainConfigTOMLByChainID]
+#! FinalityDepth on 2337 overridden to 10.
+2337 = """
+AutoCreateKey = true
+FinalityDepth = 10
+MinContractPayment = 0
+[GasEstimator]
+PriceMax = '200 gwei'
+LimitDefault = 6000000
+FeeCapDefault = '200 gwei'
+"""
diff --git a/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnSource.toml b/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnSource.toml
new file mode 100644
index 00000000000..12dc6de4c81
--- /dev/null
+++ b/integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnSource.toml
@@ -0,0 +1,12 @@
+#! These are intended to be overrides for the test Test_CCIPReorg_BelowFinality_OnSource.
+[NodeConfig.ChainConfigTOMLByChainID]
+#! FinalityDepth on 1337 overridden to 10.
+1337 = """
+AutoCreateKey = true
+FinalityDepth = 10
+MinContractPayment = 0
+[GasEstimator]
+PriceMax = '200 gwei'
+LimitDefault = 6000000
+FeeCapDefault = '200 gwei'
+"""
diff --git a/integration-tests/testconfig/ccip/ccip.toml b/integration-tests/testconfig/ccip/ccip.toml
index 41cf1657a72..48a3c57abda 100644
--- a/integration-tests/testconfig/ccip/ccip.toml
+++ b/integration-tests/testconfig/ccip/ccip.toml
@@ -83,6 +83,87 @@ evm_supports_eip1559 = true
 evm_default_gas_limit = 6000000
 evm_finality_depth = 1
 
+# SIMULATED_1_DEEPER_FINALITY, SIMULATED_2_DEEPER_FINALITY, SIMULATED_3_DEEPER_FINALITY
+# are the same as SIMULATED_1, SIMULATED_2, SIMULATED_3, but with finality_depth = 10.
+# These are primarily used in the reorg tests, but can technically be used in any tests.
+[Network.EVMNetworks.SIMULATED_1_DEEPER_FINALITY]
+evm_name = 'chain-1337'
+evm_chain_id = 1337
+evm_keys = [
+    "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
+    "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+    "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
+    "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
+    "47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a",
+    "8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba",
+    "92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e",
+    "4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356",
+    "dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
+    "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
+    "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897"
+]
+evm_simulated = true
+client_implementation = 'Ethereum'
+evm_chainlink_transaction_limit = 50000
+evm_transaction_timeout = '2m'
+evm_minimum_confirmations = 1
+evm_gas_estimation_buffer = 1000
+evm_supports_eip1559 = true
+evm_default_gas_limit = 6000000
+evm_finality_depth = 10
+
+[Network.EVMNetworks.SIMULATED_2_DEEPER_FINALITY]
+evm_name = 'chain-2337'
+evm_chain_id = 2337
+evm_keys = [
+    "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
+    "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+    "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
+    "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
+    "47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a",
+    "8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba",
+    "92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e",
+    "4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356",
+    "dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
+    "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
+    "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897"
+]
+evm_simulated = true
+client_implementation = 'Ethereum'
+evm_chainlink_transaction_limit = 50000
+evm_transaction_timeout = '2m'
+evm_minimum_confirmations = 1
+evm_gas_estimation_buffer = 1000
+evm_supports_eip1559 = true
+evm_default_gas_limit = 6000000
+evm_finality_depth = 10
+
+[Network.EVMNetworks.SIMULATED_3_DEEPER_FINALITY]
+evm_name = 'chain-3337'
+evm_chain_id = 3337
+evm_keys = [
+    "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
+    "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+    "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
+    "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
+    "47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a",
+    "8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba",
+    "92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e",
+    "4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356",
+    "dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
+    "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
+    "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897"
+]
+evm_simulated = true
+client_implementation = 'Ethereum'
+evm_chainlink_transaction_limit = 50000
+evm_transaction_timeout = '2m'
+evm_minimum_confirmations = 1
+evm_gas_estimation_buffer = 1000
+evm_supports_eip1559 = true
+evm_default_gas_limit = 6000000
+evm_finality_depth = 10
+
 [NodeConfig]
 BaseConfigTOML = """
 [Feature]
@@ -237,6 +318,94 @@ addresses_to_fund = [
     "0xBcd4042DE499D14e55001CcbB24a551F3b954096"
 ]
 
+[CCIP.PrivateEthereumNetworks.SIMULATED_1_DEEPER_FINALITY]
+# either eth1 or eth2 (for post-Merge); for eth2 Prysm is used for consensus layer.
+ethereum_version = "eth1"
+# geth, besu, erigon or nethermind
+execution_layer = "geth"
+# eth2-only, if set to true environment startup will wait until at least 1 epoch has been finalised
+wait_for_finalization=false
+
+[CCIP.PrivateEthereumNetworks.SIMULATED_1_DEEPER_FINALITY.EthereumChainConfig]
+# eth2-only, the lower the value the faster the block production (3 is minimum)
+seconds_per_slot = 3
+# eth2-only, the lower the value the faster the epoch finalisation (2 is minimum)
+slots_per_epoch = 2
+# eht2-only, the lower tha value the faster the chain starts (10 is minimum)
+genesis_delay = 15
+# eth2-only, number of validators
+validator_count = 4
+chain_id = 1337
+# address that should be founded in genesis wih ETH
+addresses_to_fund = [
+    "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+    "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
+    "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
+    "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65",
+    "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
+    "0x976ea74026e726554db657fa54763abd0c3a0aa9",
+    "0x14dc79964da2c08b23698b3d3cc7ca32193d9955",
+    "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f",
+    "0xa0ee7a142d267c1f36714e4a8f75612f20a79720",
+    "0xBcd4042DE499D14e55001CcbB24a551F3b954096"
+]
+
+
+#[CCIP.Env.PrivateEthereumNetworks.SIMULATED_1_DEEPER_THAN_FINALITY.CustomDockerImages]
+# custom docker image that will be used for execution layer client. It has to be one of: hyperledger/besu, nethermind/nethermind, thorax/erigon or ethereum/client-go.
+# instead of using a specific tag you can also use "latest_available" to use latest published tag in Github or "latest_stable" to use latest stable release from Github
+# (if corresponding Docker image on Docker Hub has not been published environment creation will fail).
+#execution_layer="hyperledger/besu:latest_stable"
+
+[CCIP.PrivateEthereumNetworks.SIMULATED_2_DEEPER_FINALITY]
+ethereum_version = "eth1"
+execution_layer = "geth"
+
+[CCIP.PrivateEthereumNetworks.SIMULATED_2_DEEPER_FINALITY.EthereumChainConfig]
+seconds_per_slot = 3
+slots_per_epoch = 2
+genesis_delay = 15
+validator_count = 4
+chain_id = 2337
+addresses_to_fund = [
+    "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+    "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
+    "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
+    "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65",
+    "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
+    "0x976ea74026e726554db657fa54763abd0c3a0aa9",
+    "0x14dc79964da2c08b23698b3d3cc7ca32193d9955",
+    "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f",
+    "0xa0ee7a142d267c1f36714e4a8f75612f20a79720",
+    "0xBcd4042DE499D14e55001CcbB24a551F3b954096"
+]
+
+[CCIP.PrivateEthereumNetworks.SIMULATED_3_DEEPER_FINALITY]
+ethereum_version = "eth1"
+execution_layer = "geth"
+
+[CCIP.PrivateEthereumNetworks.SIMULATED_3_DEEPER_FINALITY.EthereumChainConfig]
+seconds_per_slot = 3
+slots_per_epoch = 2
+genesis_delay = 15
+validator_count = 4
+chain_id = 3337
+addresses_to_fund = [
+    "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
+    "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
+    "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc",
+    "0x90f79bf6eb2c4f870365e785982e1f101e93b906",
+    "0x15d34aaf54267db7d7c367839aaf71a00a2c6a65",
+    "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc",
+    "0x976ea74026e726554db657fa54763abd0c3a0aa9",
+    "0x14dc79964da2c08b23698b3d3cc7ca32193d9955",
+    "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f",
+    "0xa0ee7a142d267c1f36714e4a8f75612f20a79720",
+    "0xBcd4042DE499D14e55001CcbB24a551F3b954096"
+]
+
 [Seth]
 # Seth specific configuration, no need for generating ephemeral addresses for ccip-tests.
 ephemeral_addresses_number = 0
@@ -277,4 +446,4 @@ WaitBeforeStart = "30s"
 # Chaos experiment total duration (chaos + recovery)
 ExperimentFullInterval = "90m"
 # Chaos time
-ExperimentInjectionInterval = "9m"
\ No newline at end of file
+ExperimentInjectionInterval = "9m"
diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go
index 3ec61f42607..cd56bb58741 100644
--- a/integration-tests/testconfig/testconfig.go
+++ b/integration-tests/testconfig/testconfig.go
@@ -293,7 +293,22 @@ const (
 	Base64OverrideEnvVarName = k8s_config.EnvBase64ConfigOverride
 )
 
-func GetConfig(configurationNames []string, product Product) (TestConfig, error) {
+// GetConfig returns a TestConfig struct with the given configuration names
+// and product. It reads the configuration from the default.toml, <product>.toml,
+// and overrides.toml files. It also reads the configuration from the
+// environment variables.
+// If extraFileNames are provided, it will read the configuration from those files
+// as well.
+// If the Base64OverrideEnvVarName environment variable is set, it will override
+// the configuration with the base64 encoded TOML config.
+// If the configuration is embedded, it will read the configuration from the
+// embedded files.
+// If the configuration is not embedded, it will read the configuration from
+// the file system.
+// If the configuration is not found, it will return an error.
+// If the configuration is found, it will validate the configuration and return
+// the TestConfig struct.
+func GetConfig(configurationNames []string, product Product, extraFileNames ...string) (TestConfig, error) {
 	logger := logging.GetTestLogger(nil)
 
 	for idx, configurationName := range configurationNames {
@@ -313,6 +328,9 @@ func GetConfig(configurationNames []string, product Product) (TestConfig, error)
 		fmt.Sprintf("%s.toml", product),
 		"overrides.toml",
 	}
+	// add extra file names to the list
+	// no-op if nothing is provided.
+	fileNames = append(fileNames, extraFileNames...)
 
 	testConfig := TestConfig{}
 	testConfig.ConfigurationNames = configurationNames
@@ -352,7 +370,7 @@ func GetConfig(configurationNames []string, product Product) (TestConfig, error)
 			} else if err != nil {
 				return TestConfig{}, errors.Wrapf(err, "error looking for file %s", filePath)
 			}
-			logger.Debug().Str("location", filePath).Msgf("Found config file %s", fileName)
+			logger.Info().Str("location", filePath).Msgf("Found config file %s", fileName)
 
 			content, err := readFile(filePath)
 			if err != nil {
diff --git a/integration-tests/testconfig/testconfig_utils.go b/integration-tests/testconfig/testconfig_utils.go
index 8d41ed55be9..709e1971235 100644
--- a/integration-tests/testconfig/testconfig_utils.go
+++ b/integration-tests/testconfig/testconfig_utils.go
@@ -57,8 +57,11 @@ selected_networks=`
 	return fmt.Errorf("%s\n%s", errStr, intro)
 }
 
-func GetChainAndTestTypeSpecificConfig(testType string, product Product) (TestConfig, error) {
-	config, err := GetConfig([]string{testType}, product)
+// GetChainAndTestTypeSpecificConfig returns a TestConfig with the chain and test type specific configuration.
+// extraFileNames are optional and can be used to specify additional config files to load
+// in order to override certain config values (e.g NodeConfig.ChainConfigTOMLByChainID).
+func GetChainAndTestTypeSpecificConfig(testType string, product Product, extraFileNames ...string) (TestConfig, error) {
+	config, err := GetConfig([]string{testType}, product, extraFileNames...)
 	if err != nil {
 		return TestConfig{}, fmt.Errorf("error getting config: %w", err)
 	}
@@ -69,6 +72,7 @@ func GetChainAndTestTypeSpecificConfig(testType string, product Product) (TestCo
 			fmt.Sprintf("%s-%s", config.GetNetworkConfig().SelectedNetworks[0], testType),
 		},
 		product,
+		extraFileNames...,
 	)
 	if err != nil {
 		return TestConfig{}, fmt.Errorf("error getting config: %w", err)
diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go
index 258eef6e621..ea4798bcbe8 100644
--- a/integration-tests/testsetups/ccip/test_helpers.go
+++ b/integration-tests/testsetups/ccip/test_helpers.go
@@ -60,6 +60,14 @@ type DeployedLocalDevEnvironment struct {
 	devEnvCfg       *devenv.EnvironmentConfig
 }
 
+func (l *DeployedLocalDevEnvironment) GetCLClusterTestEnv() *test_env.CLClusterTestEnv {
+	return l.testEnv
+}
+
+func (l *DeployedLocalDevEnvironment) GetDevEnvConfig() *devenv.EnvironmentConfig {
+	return l.devEnvCfg
+}
+
 func (l *DeployedLocalDevEnvironment) DeployedEnvironment() testhelpers.DeployedEnv {
 	return l.DeployedEnv
 }
@@ -75,7 +83,7 @@ func (l *DeployedLocalDevEnvironment) TestConfigs() *testhelpers.TestConfigs {
 func (l *DeployedLocalDevEnvironment) StartChains(t *testing.T) {
 	lggr := logger.TestLogger(t)
 	ctx := testcontext.Get(t)
-	envConfig, testEnv, cfg := CreateDockerEnv(t)
+	envConfig, testEnv, cfg := CreateDockerEnv(t, l.GenericTCConfig)
 	l.devEnvTestCfg = cfg
 	l.testEnv = testEnv
 	l.devEnvCfg = envConfig
@@ -341,7 +349,7 @@ func GenerateTestRMNConfig(t *testing.T, nRMNNodes int, tenv testhelpers.Deploye
 // CreateDockerEnv creates a new test environment with simulated private ethereum networks and job distributor
 // It returns the EnvironmentConfig which holds the chain config and JD config
 // The test environment is then used to start chainlink nodes
-func CreateDockerEnv(t *testing.T) (
+func CreateDockerEnv(t *testing.T, v1_6TestConfig *testhelpers.TestConfigs) (
 	*devenv.EnvironmentConfig,
 	*test_env.CLClusterTestEnv,
 	tc.TestConfig,
@@ -350,7 +358,13 @@ func CreateDockerEnv(t *testing.T) (
 		require.NoError(t, gotenv.Load(".env"), "Error loading .env file")
 	}
 
-	cfg, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.CCIP)
+	var cfg tc.TestConfig
+	var err error
+	if v1_6TestConfig != nil {
+		cfg, err = tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.CCIP, v1_6TestConfig.ExtraConfigTomls...)
+	} else {
+		cfg, err = tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.CCIP)
+	}
 	require.NoError(t, err, "Error getting config")
 
 	evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig())
@@ -365,11 +379,12 @@ func CreateDockerEnv(t *testing.T) (
 	}
 
 	// ignore critical CL node logs until they are fixed, as otherwise tests will fail
-	var logScannerSettings = test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(testreporters.NewAllowedLogMessage(
-		"No live RPC nodes available",
-		"CL nodes are started before simulated chains, so this is expected",
-		zapcore.DPanicLevel,
-		testreporters.WarnAboutAllowedMsgs_No),
+	var allowedMessages = []testreporters.AllowedLogMessage{
+		testreporters.NewAllowedLogMessage(
+			"No live RPC nodes available",
+			"CL nodes are started before simulated chains, so this is expected",
+			zapcore.DPanicLevel,
+			testreporters.WarnAboutAllowedMsgs_No),
 		testreporters.NewAllowedLogMessage(
 			"Lane processing is stopped because source chain is cursed or CommitStore is down",
 			"Curse test are expected to trigger this logs",
@@ -385,7 +400,18 @@ func CreateDockerEnv(t *testing.T) (
 			"Possible lifecycle bug in chainlink.",
 			zapcore.DPanicLevel,
 			testreporters.WarnAboutAllowedMsgs_No),
-	)
+	}
+	if v1_6TestConfig != nil {
+		for _, logMsg := range v1_6TestConfig.LogMessagesToIgnore {
+			allowedMessages = append(allowedMessages, testreporters.NewAllowedLogMessage(
+				logMsg.Msg,
+				logMsg.Reason,
+				logMsg.Level,
+				testreporters.WarnAboutAllowedMsgs_No,
+			))
+		}
+	}
+	var logScannerSettings = test_env.GetDefaultChainlinkNodeLogScannerSettingsWithExtraAllowedMessages(allowedMessages...)
 
 	builder := test_env.NewCLTestEnvBuilder().
 		WithTestConfig(&cfg).
@@ -601,10 +627,7 @@ func FundNodes(t *testing.T, lggr zerolog.Logger, env *test_env.CLClusterTestEnv
 				if receipt == nil {
 					return fmt.Errorf("receipt is nil")
 				}
-				txHash := "(none)"
-				if receipt != nil {
-					txHash = receipt.TxHash.String()
-				}
+				txHash := receipt.TxHash.String()
 				lggr.Info().
 					Str("From", fromAddress.Hex()).
 					Str("To", toAddr.String()).


Reviews:
user: login: AnieeG
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-30T00:29:49Z
user: login: b-gopalswami
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-30T04:27:53Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-30T13:35:26Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T09:29:56Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T09:32:24Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T10:57:07Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T11:02:59Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-31T11:05:06Z
user: login: mateusz-sekara
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-13T10:55:31Z
user: login: b-gopalswami
body: LGTM
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T19:27:55Z
user: login: AnieeG
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-06T19:14:50Z
user: login: dimkouv
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T11:21:39Z
user: login: mateusz-sekara
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T12:59:45Z
user: login: carte7000
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T13:53:52Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:04:35Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:12:19Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:12:52Z
user: login: carte7000
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:33:01Z
user: login: AnieeG
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:36:46Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:51:46Z
user: login: AnieeG
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:55:36Z
user: login: makramkd
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:59:14Z
user: login: dimkouv
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:00:27Z
user: login: carte7000
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T16:00:27Z
user: login: mateusz-sekara
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T08:57:04Z
user: login: mateusz-sekara
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:00:58Z
user: login: mateusz-sekara
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:04:35Z
user: login: 0xnogo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:15:44Z

Review comments:
diff_hunk: @@ -59,6 +59,14 @@ type DeployedLocalDevEnvironment struct {
 	devEnvCfg       *devenv.EnvironmentConfig
 }
 
+func (l *DeployedLocalDevEnvironment) GetCLClusterTestEnv() *test_env.CLClusterTestEnv {
+	return l.testEnv
+}
+
+func (l *DeployedLocalDevEnvironment) GetDevEnvConfig() devenv.EnvironmentConfig {
+	return *l.devEnvCfg
path: integration-tests/testsetups/ccip/test_helpers.go
user: login: AnieeG
body: nil pointer check
created_at: 2025-01-30T00:01:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -293,7 +293,22 @@ const (
 	Base64OverrideEnvVarName = k8s_config.EnvBase64ConfigOverride
 )
 
-func GetConfig(configurationNames []string, product Product) (TestConfig, error) {
+// GetConfig returns a TestConfig struct with the given configuration names
path: integration-tests/testconfig/testconfig.go
user: login: AnieeG
body: 👏
created_at: 2025-01-30T00:04:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,87 @@ evm_supports_eip1559 = true
 evm_default_gas_limit = 6000000
 evm_finality_depth = 1
 
+# SIMULATED_1_DEEPER_FINALITY, SIMULATED_2_DEEPER_FINALITY, SIMULATED_3_DEEPER_FINALITY
+# are the same as SIMULATED_1, SIMULATED_2, SIMULATED_3, but with finality_depth = 10.
+# These are primarily used in the reorg tests, but can technically be used in any tests.
+[Network.EVMNetworks.SIMULATED_1_DEEPER_FINALITY]
+evm_name = 'chain-1337'
+evm_chain_id = 1337
+evm_keys = [
+    "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
+    "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+    "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
+    "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
+    "47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a",
+    "8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba",
+    "92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e",
+    "4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356",
+    "dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
+    "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
+    "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897"
+]
+evm_simulated = true
+client_implementation = 'Ethereum'
+evm_chainlink_transaction_limit = 50000
+evm_transaction_timeout = '2m'
+evm_minimum_confirmations = 1
+evm_gas_estimation_buffer = 1000
+evm_supports_eip1559 = true
+evm_default_gas_limit = 6000000
+evm_finality_depth = 10
path: integration-tests/testconfig/ccip/ccip.toml
user: login: AnieeG
body: nit : is the chainlink node config updated as part of this updated finality? if not you can create a similar func like [this](https://github.com/smartcontractkit/chainlink/blob/develop/integration-tests/types/config/node/core.go#L89) to update the finality in `evmConfig.Chain ` from the finality mentioned in blockchain.EVMNetwork input param. It's not updated in the existing function because other product do not use finality.
created_at: 2025-01-30T00:08:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,12 @@
+#! These are intended to be overrides for the test Test_CCIPReorg_BelowFinality_OnDest.
+[NodeConfig.ChainConfigTOMLByChainID]
+#! FinalityDepth on 2337 overridden to 10.
+2337 = """
+AutoCreateKey = true
+FinalityDepth = 10
path: integration-tests/testconfig/ccip/Test_CCIPReorg_BelowFinality_OnDest.toml
user: login: AnieeG
body: if you only need this because of Finality Depth , please check my other comment 
created_at: 2025-01-30T00:09:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: how about a check to validate whether reorg happened? bnAfterReorg < minChainBlockNumberBeforeReorg
created_at: 2025-01-30T00:16:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: Is it okay to use gomega? I know there is no `consistently` check in `require`. But I got a comment before on a PR that we are getting rid of gomega.
created_at: 2025-01-30T00:20:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: if we check the seq num of this reorgingMsgEvent , should it be equal to previous reorgingMsgEvent which was lost because of reorg?
created_at: 2025-01-30T00:21:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: can we put this whole thing into a wrapper func?
created_at: 2025-01-30T00:23:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: would be easier to update if you put something like these into var 
`ReorgDepthGreaterThanFinality` and `ReorgDepthLesserThanFinality`
created_at: 2025-01-30T00:24:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: nit: a wrapper func for checking the finality violation is reported and not reported given the chain details
created_at: 2025-01-30T00:26:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 2*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// the commit should NOT go through on the re-orged dest chain.
+	// TODO: this is not a great way to assert not-happenings.
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destChainSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{
+			Start: 0,
+		})
+		require.NoError(t, err)
+		return !it.Next()
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	noreorgSourceSelector := allChains[1]
+	noreorgSourceChain, ok := chainsel.ChainBySelector(noreorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body: nit : func var with this
created_at: 2025-01-30T00:28:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 2*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// the commit should NOT go through on the re-orged dest chain.
+	// TODO: this is not a great way to assert not-happenings.
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destChainSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{
+			Start: 0,
+		})
+		require.NoError(t, err)
+		return !it.Next()
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	noreorgSourceSelector := allChains[1]
+	noreorgSourceChain, ok := chainsel.ChainBySelector(noreorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	noreorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", noreorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s, no reorg log poller service name: %s",
+			reorgLogPollerService, noreorgLogPollerService)
+	destChainSelector := allChains[2]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, noreorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+	msgEvent := testhelpers.TestSendRequest(t, e.Env, state, noreorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will not get re-orged, msgSentEvent: %x", msgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 50
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 3*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// expect the commit to still go through on the non-reorged source chain.
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body:  are you validating that reorged message is not processed
created_at: 2025-01-30T00:29:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 2*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// the commit should NOT go through on the re-orged dest chain.
+	// TODO: this is not a great way to assert not-happenings.
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destChainSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{
+			Start: 0,
+		})
+		require.NoError(t, err)
+		return !it.Next()
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	noreorgSourceSelector := allChains[1]
+	noreorgSourceChain, ok := chainsel.ChainBySelector(noreorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	noreorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", noreorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s, no reorg log poller service name: %s",
+			reorgLogPollerService, noreorgLogPollerService)
+	destChainSelector := allChains[2]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, noreorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+	msgEvent := testhelpers.TestSendRequest(t, e.Env, state, noreorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will not get re-orged, msgSentEvent: %x", msgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 50
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 3*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// expect the commit to still go through on the non-reorged source chain.
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: AnieeG
body:  are you validating that reorged message is not processed
created_at: 2025-01-30T00:29:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: b-gopalswami
body: This is mostly not enough in in-memory test. Until we have proper solution, I would bump this to 30 sec.
created_at: 2025-01-30T03:45:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: b-gopalswami
body: We can extract this out as it is similar in the other test as well. 
created_at: 2025-01-30T04:14:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: b-gopalswami
body: How consistently is this works? In v1.5 test, we had flakiness in detecting the violation within given time by all nodes. I have tried some trade offs like certain number of nodes detection and all but it was still flakey. I see your approach of moving forward with certain blocks and then applying the re-org. Is that helps to produce consistent results?
created_at: 2025-01-30T04:23:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -83,6 +83,87 @@ evm_supports_eip1559 = true
 evm_default_gas_limit = 6000000
 evm_finality_depth = 1
 
+# SIMULATED_1_DEEPER_FINALITY, SIMULATED_2_DEEPER_FINALITY, SIMULATED_3_DEEPER_FINALITY
+# are the same as SIMULATED_1, SIMULATED_2, SIMULATED_3, but with finality_depth = 10.
+# These are primarily used in the reorg tests, but can technically be used in any tests.
+[Network.EVMNetworks.SIMULATED_1_DEEPER_FINALITY]
+evm_name = 'chain-1337'
+evm_chain_id = 1337
+evm_keys = [
+    "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d",
+    "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80",
+    "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a",
+    "7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6",
+    "47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a",
+    "8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba",
+    "92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e",
+    "4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356",
+    "dbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97",
+    "2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6",
+    "f214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897"
+]
+evm_simulated = true
+client_implementation = 'Ethereum'
+evm_chainlink_transaction_limit = 50000
+evm_transaction_timeout = '2m'
+evm_minimum_confirmations = 1
+evm_gas_estimation_buffer = 1000
+evm_supports_eip1559 = true
+evm_default_gas_limit = 6000000
+evm_finality_depth = 10
path: integration-tests/testconfig/ccip/ccip.toml
user: login: makramkd
body: Oh interesting, yeah I do change the settings of the Chainlink node but its via the TOML overrides, I suppose that's not the best way to do things?
created_at: 2025-01-30T13:35:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: It should be, yeah, can add an assertion.
created_at: 2025-01-31T09:29:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: We still use it a lot all over the codebase, should be OK. If we want to prevent it I believe we can add a lint rule.
created_at: 2025-01-31T09:32:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: I think it should be a bit more consistent, yeah. I think most consistent would be to ensure that all nodes are on the same block before performing the re-org, though with the current CL APIs I think thats pretty much impossible.
created_at: 2025-01-31T10:57:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: Yeah gonna refactor this
created_at: 2025-01-31T11:02:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,675 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router"
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+	destChainSelector := allChains[1]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHashBeforeReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msg("sent CCIP message that will get re-orged before getting finalized")
+
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reorgingMsgEvent.Raw.BlockNumber + reorgDepth - 1
+	// let reorgDepth - 1 blocks pass by before re-orging the message.
+	// This will effectively rewind the chain to a block where the message didn't exist.
+	require.Eventually(t, func() bool {
+		bn, err := reorgSourceClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgSourceClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	reorgingMsgEvent = testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("blockHashAfterReorg", reorgingMsgEvent.Raw.BlockHash.String()).
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("re-sent CCIP message after the re-org")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to NOT report finality violation on the logpoller, since re-org is less than finality", nonBootstrapCount)
+	gomega.NewWithT(t).Consistently(func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == 0
+	}, time.Minute, 10*time.Second).Should(gomega.BeTrue())
+
+	// expect the commit to still go through on the non-reorged source chain.
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		reorgSourceSelector,
+		e.Env.Chains[destChainSelector],
+		state.Chains[destChainSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// prior to the message block being finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithExtraConfigTomls([]string{
+			t.Name() + ".toml",
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s", reorgLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.
+		Info().
+		Str("messageID", hexutil.Encode(reorgingMsgEvent.Message.Header.MessageId[:])).
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Str("messageBlockHash", reorgingMsgEvent.Raw.BlockHash.String()).
+		Msgf("sent CCIP message that whose commit report will re-org on dest before getting finalized")
+
+	// expect the commit to still go through for the message.
+	reportEvent, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+
+	l.
+		Info().
+		Uint64("reportBlockNumber", reportEvent.Raw.BlockNumber).
+		Str("reportBlockHash", reportEvent.Raw.BlockHash.String()).
+		Msg("got commit report on dest, preparing to re-org it")
+
+	// re-org the dest chain less than finality blocks.
+	const reorgDepth = 7
+	var minChainBlockNumberBeforeReorg = reportEvent.Raw.BlockNumber + reorgDepth - 1
+	// Wait for chain to progress
+	require.Eventually(t, func() bool {
+		bn, err := reorgDestClient.BlockNumber()
+		require.NoError(t, err)
+		l.Info().
+			Int64("blockNumber", bn).
+			Uint64("targetBlockNumber", minChainBlockNumberBeforeReorg).
+			Msg("Waiting for chain to progress above target block number")
+		return bn >= int64(minChainBlockNumberBeforeReorg) //nolint:gosec // this will not overflow in the current test
+	}, 1*time.Minute, 500*time.Millisecond, "timeout exceeded: chain did not progress above the target block number")
+
+	// Run reorg below finality depth
+	l.Info().
+		Uint64("messageBlockNumber", reorgingMsgEvent.Raw.BlockNumber).
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", sourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	bnAfterReorg, err := reorgDestClient.BlockNumber()
+	require.NoError(t, err, "error getting block number after reorg")
+
+	l.Info().Int64("blockNumberAfterReorg", bnAfterReorg).Msg("block number after reorg")
+
+	// commit should be re-submitted after the re-org
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err)
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destChainSelector := allChains[1]
+	destChain, ok := chainsel.ChainBySelector(destChainSelector)
+	require.True(t, ok)
+	destLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", destChain.EvmChainID)
+	l.
+		Info().
+		Msgf("reorging dest log poller service name: %s", destLogPollerService)
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgDestClient := ctf_client.NewRPCClient(chainSelToRPCURL[destChainSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, sourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 60
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("destChainSelector", destChainSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgDestClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == destLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(destLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 2*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// the commit should NOT go through on the re-orged dest chain.
+	// TODO: this is not a great way to assert not-happenings.
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destChainSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{
+			Start: 0,
+		})
+		require.NoError(t, err)
+		return !it.Next()
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	require.Equal(
+		t,
+		os.Getenv(testhelpers.ENVTESTTYPE),
+		string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments",
+	)
+
+	l := logging.GetTestLogger(t)
+
+	// This test sends a ccip message and re-orgs the chain
+	// after the message block is finalized.
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(
+		t,
+		testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+			{
+				Msg:    "Got very old block.",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Reorg greater than finality depth detected",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+			{
+				Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+				Reason: "We are expecting a re-org beyond finality",
+				Level:  zapcore.DPanicLevel,
+			},
+		}),
+	)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+
+	var nonBootstrapP2PIDs = make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		nonBootstrapP2PIDs = append(nonBootstrapP2PIDs, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+
+	l.Info().Msgf("nonBootstrapP2PIDs: %s", nonBootstrapP2PIDs)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSourceSelector := allChains[0]
+	reorgSourceChain, ok := chainsel.ChainBySelector(reorgSourceSelector)
+	require.True(t, ok)
+	noreorgSourceSelector := allChains[1]
+	noreorgSourceChain, ok := chainsel.ChainBySelector(noreorgSourceSelector)
+	require.True(t, ok)
+	reorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", reorgSourceChain.EvmChainID)
+	noreorgLogPollerService := fmt.Sprintf("EVM.%d.LogPoller", noreorgSourceChain.EvmChainID)
+	l.Info().
+		Msgf("reorging log poller service name: %s, no reorg log poller service name: %s",
+			reorgLogPollerService, noreorgLogPollerService)
+	destChainSelector := allChains[2]
+
+	dockerEnv, ok := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+	require.True(t, ok)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range dockerEnv.GetDevEnvConfig().Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSourceSelector], nil)
+
+	// setup lanes from s1 and s2 to destChainSelector
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSourceSelector, destChainSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, noreorgSourceSelector, destChainSelector, false)
+
+	// wait for log poller filters to get registered.
+	l.Info().Msg("waiting for log poller filters to get registered")
+	time.Sleep(15 * time.Second)
+	reorgingMsgEvent := testhelpers.TestSendRequest(t, e.Env, state, reorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will get re-orged, msg id: %x", reorgingMsgEvent.Message.Header.MessageId)
+	msgEvent := testhelpers.TestSendRequest(t, e.Env, state, noreorgSourceSelector, destChainSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destChainSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+	l.Info().Msgf("sent CCIP message that will not get re-orged, msgSentEvent: %x", msgEvent.Message.Header.MessageId)
+
+	// Run reorg above finality depth
+	const reorgDepth = 50
+	l.Info().
+		Int("reorgDepth", reorgDepth).
+		Uint64("sourceChainSelector", reorgSourceSelector).
+		Msg("starting blockchain reorg on Simulated Geth chain")
+	err = reorgSourceClient.GethSetHead(reorgDepth)
+	require.NoError(t, err, "error starting blockchain reorg on Simulated Geth chain")
+
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	nonBootstrapCount := len(nodeAPIs) - 1
+	l.Info().Msgf("waiting for %d non-bootstrap nodes to report finality violation on the logpoller", nonBootstrapCount)
+	require.Eventually(t, func() bool {
+		violatedResponses := make(map[string]struct{})
+		for _, node := range nodeAPIs {
+			// skip bootstrap nodes, they won't have any logpoller filters
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+
+			l.Debug().Msgf("got p2pKeys from node API: %+v", p2pKeys)
+
+			require.GreaterOrEqual(t, len(p2pKeys.Data), 1)
+			if !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Msgf("skipping bootstrap node w/ p2p id %s", p2pKeys.Data[0].Attributes.PeerID)
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			for _, d := range resp.Data {
+				if d.Attributes.Name == reorgLogPollerService &&
+					d.Attributes.Output == "finality violated" &&
+					d.Attributes.Status == "failing" {
+					violatedResponses[p2pKeys.Data[0].Attributes.PeerID] = struct{}{}
+					break
+				}
+			}
+
+			if _, ok := violatedResponses[p2pKeys.Data[0].Attributes.PeerID]; ok {
+				l.Info().Msgf("node %s reported finality violation", p2pKeys.Data[0].Attributes.PeerID)
+			} else {
+				l.Info().Msgf("node %s did not report finality violation, log poller response: %+v",
+					p2pKeys.Data[0].Attributes.PeerID,
+					getLogPollerHealth(reorgLogPollerService, resp.Data),
+				)
+			}
+		}
+
+		l.Info().Msgf("%d nodes reported finality violation", len(violatedResponses))
+		return len(violatedResponses) == nonBootstrapCount
+	}, 3*time.Minute, 5*time.Second, "not all the nodes report finality violation")
+	l.Info().Msg("All nodes reported finality violation")
+
+	// expect the commit to still go through on the non-reorged source chain.
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: Not explicitly, though I can add an assertion for that.
created_at: 2025-01-31T11:05:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: nit, but how about performing multiple reorgs instead of only one? I'm thinking about something like this

```go
finalityDepth = 50

for i := 0; i < reorgAttempts; i++ {
    performReorg(10 blocks)
    emit couple of logs
}```
created_at: 2025-04-08T12:55:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: Should we skip the test if this check doesn't work?
created_at: 2025-04-08T12:56:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
+	// TODO: timing out right away, needs to get fixed.
+	// gomega.NewWithT(t).Consistently(func() bool {
+	// 	it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+	// 	require.NoError(t, err)
+	// 	return !it.Next()
+	// }, 1*time.Minute, 10*time.Second, tests.WaitTimeout(t)).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSource := allChains[0]
+	nonReorgSource := allChains[1]
+	destSelector := allChains[2]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSource], nil)
+
+	// Multi-lane setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSource, destSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, nonReorgSource, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send messages from both sources
+	sendCCIPMessage(t, e.Env, state, reorgSource, destSelector, l)
+	sendCCIPMessage(t, e.Env, state, nonReorgSource, destSelector, l)
+
+	// Deep reorg execution
+	performReorg(t, reorgSourceClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, reorgSource),
+		getLogPollerService(t, reorgSource),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Validate non-reorged source
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		nonReorgSource,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed for non-reorged source")
+
+	// Commit absence check on the reorged source
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+		require.NoError(t, err)
+		var found bool
+	outer:
+		for it.Next() {
+			for _, mr := range it.Event.UnblessedMerkleRoots {
+				if mr.SourceChainSelector == reorgSource {
+					found = true
+					break outer
+				}
+			}
+		}
+		return !found
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+func getServiceHealth(serviceName string, healthResponses []nodeclient.HealthResponseDetail) nodeclient.HealthCheck {
+	for _, d := range healthResponses {
+		if d.Attributes.Name == serviceName {
+			return d.Attributes
+		}
+	}
+
+	return nodeclient.HealthCheck{}
+}
+
+// Helper to initialize common test components
+func setupReorgTest(t *testing.T, testOpts ...testhelpers.TestOps) (
+	testhelpers.DeployedEnv,
+	logging.Logger,
+	*testsetups.DeployedLocalDevEnvironment,
+	[]string,
+	ccipcs.CCIPOnChainState,
+) {
+	require.Equal(t, os.Getenv(testhelpers.ENVTESTTYPE), string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments")
+
+	l := logging.GetTestLogger(t)
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(t, testOpts...)
+	dockerEnv := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+	nonBootstrapP2PIDs := getNonBootstrapP2PIDs(nodeInfos)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	return e, l, dockerEnv, nonBootstrapP2PIDs, state
+}
+
+// Extract non-bootstrap P2P IDs
+func getNonBootstrapP2PIDs(nodeInfos deployment.Nodes) []string {
+	ids := make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		ids = append(ids, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+	return ids
+}
+
+// Build chain selector to RPC URL map
+func buildChainSelectorToRPCURLMap(t *testing.T, dockerEnv *testsetups.DeployedLocalDevEnvironment) map[uint64]string {
+	devEnvConfig := dockerEnv.GetDevEnvConfig()
+	require.NotNil(t, devEnvConfig)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range devEnvConfig.Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+	return chainSelToRPCURL
+}
+
+// Get log poller service name
+func getLogPollerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.LogPoller", chain.EvmChainID)
+}
+
+func getHeadTrackerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.HeadTracker", chain.EvmChainID)
+}
+
+// Send CCIP message helper
+func sendCCIPMessage(
+	t *testing.T,
+	env deployment.Environment,
+	state ccipcs.CCIPOnChainState,
+	sourceSelector, destSelector uint64,
+	l logging.Logger,
+) *onramp.OnRampCCIPMessageSent {
+	msgEvent := testhelpers.TestSendRequest(t, env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+
+	l.Info().
+		Str("messageID", hexutil.Encode(msgEvent.Message.Header.MessageId[:])).
+		Uint64("blockNumber", msgEvent.Raw.BlockNumber).
+		Str("blockHash", msgEvent.Raw.BlockHash.String()).
+		Msg("Sent CCIP message")
+
+	return msgEvent
+}
+
+// Check finality violations helper
+// Uses require.Eventually or gomega.Consistently based on expected count
+func checkFinalityViolations(
+	t *testing.T,
+	nodeAPIs []*nodeclient.ChainlinkClient,
+	nonBootstrapP2PIDs []string,
+	headTrackerServiceName,
+	logPollerServiceName string,
+	l logging.Logger,
+	expectedCount int,
+	timeout time.Duration,
+	interval time.Duration,
+) {
+	checkFunc := func() bool {
+		violated := 0
+		headTrackerViolated := 0
+		logPollerViolated := 0
+		for _, node := range nodeAPIs {
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+			if len(p2pKeys.Data) == 0 || !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).Msg("Skipping bootstrap node")
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			lpHealth := getServiceHealth(logPollerServiceName, resp.Data)
+			htHealth := getServiceHealth(headTrackerServiceName, resp.Data)
+			var lpViolated, htViolated bool
+			if lpHealth.Status == "failing" {
+				if lpHealth.Output == commontypes.ErrFinalityViolated.Error() {
+					logPollerViolated++
+					lpViolated = true
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller finality violation")
+				} else {
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller unhealthy but not finality violation")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: You can put the log outside of the `if/else` clause

```go
 lpHealth.Output == commontypes.ErrFinalityViolated.Error() {
         logPollerViolated++
         lpViolated = true
} 

l.Info().
		Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
		Str("output", lpHealth.Output).
		Str("logPollerService", logPollerServiceName).
                                              Str("finalityViolated", finalityViolated).
		Msg("Log poller finality violation check")
```
created_at: 2025-04-08T12:58:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
+	// TODO: timing out right away, needs to get fixed.
+	// gomega.NewWithT(t).Consistently(func() bool {
+	// 	it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+	// 	require.NoError(t, err)
+	// 	return !it.Next()
+	// }, 1*time.Minute, 10*time.Second, tests.WaitTimeout(t)).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSource := allChains[0]
+	nonReorgSource := allChains[1]
+	destSelector := allChains[2]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSource], nil)
+
+	// Multi-lane setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSource, destSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, nonReorgSource, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send messages from both sources
+	sendCCIPMessage(t, e.Env, state, reorgSource, destSelector, l)
+	sendCCIPMessage(t, e.Env, state, nonReorgSource, destSelector, l)
+
+	// Deep reorg execution
+	performReorg(t, reorgSourceClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, reorgSource),
+		getLogPollerService(t, reorgSource),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Validate non-reorged source
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		nonReorgSource,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed for non-reorged source")
+
+	// Commit absence check on the reorged source
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+		require.NoError(t, err)
+		var found bool
+	outer:
+		for it.Next() {
+			for _, mr := range it.Event.UnblessedMerkleRoots {
+				if mr.SourceChainSelector == reorgSource {
+					found = true
+					break outer
+				}
+			}
+		}
+		return !found
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+func getServiceHealth(serviceName string, healthResponses []nodeclient.HealthResponseDetail) nodeclient.HealthCheck {
+	for _, d := range healthResponses {
+		if d.Attributes.Name == serviceName {
+			return d.Attributes
+		}
+	}
+
+	return nodeclient.HealthCheck{}
+}
+
+// Helper to initialize common test components
+func setupReorgTest(t *testing.T, testOpts ...testhelpers.TestOps) (
+	testhelpers.DeployedEnv,
+	logging.Logger,
+	*testsetups.DeployedLocalDevEnvironment,
+	[]string,
+	ccipcs.CCIPOnChainState,
+) {
+	require.Equal(t, os.Getenv(testhelpers.ENVTESTTYPE), string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments")
+
+	l := logging.GetTestLogger(t)
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(t, testOpts...)
+	dockerEnv := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+	nonBootstrapP2PIDs := getNonBootstrapP2PIDs(nodeInfos)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	return e, l, dockerEnv, nonBootstrapP2PIDs, state
+}
+
+// Extract non-bootstrap P2P IDs
+func getNonBootstrapP2PIDs(nodeInfos deployment.Nodes) []string {
+	ids := make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		ids = append(ids, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+	return ids
+}
+
+// Build chain selector to RPC URL map
+func buildChainSelectorToRPCURLMap(t *testing.T, dockerEnv *testsetups.DeployedLocalDevEnvironment) map[uint64]string {
+	devEnvConfig := dockerEnv.GetDevEnvConfig()
+	require.NotNil(t, devEnvConfig)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range devEnvConfig.Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+	return chainSelToRPCURL
+}
+
+// Get log poller service name
+func getLogPollerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.LogPoller", chain.EvmChainID)
+}
+
+func getHeadTrackerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.HeadTracker", chain.EvmChainID)
+}
+
+// Send CCIP message helper
+func sendCCIPMessage(
+	t *testing.T,
+	env deployment.Environment,
+	state ccipcs.CCIPOnChainState,
+	sourceSelector, destSelector uint64,
+	l logging.Logger,
+) *onramp.OnRampCCIPMessageSent {
+	msgEvent := testhelpers.TestSendRequest(t, env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+
+	l.Info().
+		Str("messageID", hexutil.Encode(msgEvent.Message.Header.MessageId[:])).
+		Uint64("blockNumber", msgEvent.Raw.BlockNumber).
+		Str("blockHash", msgEvent.Raw.BlockHash.String()).
+		Msg("Sent CCIP message")
+
+	return msgEvent
+}
+
+// Check finality violations helper
+// Uses require.Eventually or gomega.Consistently based on expected count
+func checkFinalityViolations(
+	t *testing.T,
+	nodeAPIs []*nodeclient.ChainlinkClient,
+	nonBootstrapP2PIDs []string,
+	headTrackerServiceName,
+	logPollerServiceName string,
+	l logging.Logger,
+	expectedCount int,
+	timeout time.Duration,
+	interval time.Duration,
+) {
+	checkFunc := func() bool {
+		violated := 0
+		headTrackerViolated := 0
+		logPollerViolated := 0
+		for _, node := range nodeAPIs {
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+			if len(p2pKeys.Data) == 0 || !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).Msg("Skipping bootstrap node")
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			lpHealth := getServiceHealth(logPollerServiceName, resp.Data)
+			htHealth := getServiceHealth(headTrackerServiceName, resp.Data)
+			var lpViolated, htViolated bool
+			if lpHealth.Status == "failing" {
+				if lpHealth.Output == commontypes.ErrFinalityViolated.Error() {
+					logPollerViolated++
+					lpViolated = true
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller finality violation")
+				} else {
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller unhealthy but not finality violation")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: It will make the code easier to read and reduce number of indents 
created_at: 2025-04-08T12:59:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1019,6 +1019,34 @@ runner-test-matrix:
 
   # START: CCIPv1.6 tests
 
+  - id: smoke/ccip/ccip_reorg_test.go:LessThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - PR E2E Core Tests
+      - Nightly E2E Tests
+    test_cmd: cd integration-tests/smoke/ccip && go test -run "Test_CCIPReorg_BelowFinality_OnSource|Test_CCIPReorg_BelowFinality_OnDest" ccip_reorg_test.go -timeout 25m -test.parallel=1 -count=1 -json
+    pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
+    test_env_vars:
+      E2E_TEST_SELECTED_NETWORK: SIMULATED_1_DEEPER_FINALITY,SIMULATED_2_DEEPER_FINALITY
+      E2E_JD_VERSION: 0.6.0
path: .github/e2e-tests.yml
user: login: carte7000
body: I have seen most test using 0.9.0 now any reason to use 0.6.0 here?
created_at: 2025-04-08T13:48:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1019,6 +1019,34 @@ runner-test-matrix:
 
   # START: CCIPv1.6 tests
 
+  - id: smoke/ccip/ccip_reorg_test.go:LessThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - PR E2E Core Tests
+      - Nightly E2E Tests
+    test_cmd: cd integration-tests/smoke/ccip && go test -run "Test_CCIPReorg_BelowFinality_OnSource|Test_CCIPReorg_BelowFinality_OnDest" ccip_reorg_test.go -timeout 25m -test.parallel=1 -count=1 -json
+    pyroscope_env: ci-smoke-ccipv1_6-evm-simulated
+    test_env_vars:
+      E2E_TEST_SELECTED_NETWORK: SIMULATED_1_DEEPER_FINALITY,SIMULATED_2_DEEPER_FINALITY
+      E2E_JD_VERSION: 0.6.0
path: .github/e2e-tests.yml
user: login: makramkd
body: Just copy/pasta as this is an old PR, updating to 0.9
created_at: 2025-04-08T14:04:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: I wasn't able to get to the bottom of why this code times out right away, I think its tricky to write such assertions now anyway, it would be better with some kind of telemetry consumption that checks that all observations are empty or something similar.
created_at: 2025-04-08T14:12:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: Performing multiple reorgs on different chains or on the same chain? 
created_at: 2025-04-08T14:12:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1019,6 +1019,34 @@ runner-test-matrix:
 
   # START: CCIPv1.6 tests
 
+  - id: smoke/ccip/ccip_reorg_test.go:LessThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - PR E2E Core Tests
path: .github/e2e-tests.yml
user: login: AnieeG
body: would you like to keep it running in nightly for few days to check for  flakeyness before enabling in PR?
created_at: 2025-04-08T15:36:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1019,6 +1019,34 @@ runner-test-matrix:
 
   # START: CCIPv1.6 tests
 
+  - id: smoke/ccip/ccip_reorg_test.go:LessThanFinalityTests
+    path: integration-tests/smoke/ccip/ccip_reorg_test.go
+    test_env_type: docker
+    runs_on: ubuntu-latest
+    triggers:
+      - PR E2E Core Tests
path: .github/e2e-tests.yml
user: login: makramkd
body: Good note, gonna keep it in nightly for now.
created_at: 2025-04-08T15:51:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
+	// TODO: timing out right away, needs to get fixed.
+	// gomega.NewWithT(t).Consistently(func() bool {
+	// 	it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+	// 	require.NoError(t, err)
+	// 	return !it.Next()
+	// }, 1*time.Minute, 10*time.Second, tests.WaitTimeout(t)).Should(gomega.BeTrue())
+}
+
+// This test sends a ccip message and re-orgs the chain
+// after the message block has been finalized.
+// The result should be that the plugin does not process
+// messages from the re-orged chain anymore.
+// However, it should gracefully process messages from non-reorged chains.
+func Test_CCIPReorg_GreaterThanFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 3)
+	reorgSource := allChains[0]
+	nonReorgSource := allChains[1]
+	destSelector := allChains[2]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	reorgSourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[reorgSource], nil)
+
+	// Multi-lane setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, reorgSource, destSelector, false)
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, nonReorgSource, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send messages from both sources
+	sendCCIPMessage(t, e.Env, state, reorgSource, destSelector, l)
+	sendCCIPMessage(t, e.Env, state, nonReorgSource, destSelector, l)
+
+	// Deep reorg execution
+	performReorg(t, reorgSourceClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, reorgSource),
+		getLogPollerService(t, reorgSource),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Validate non-reorged source
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		nonReorgSource,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed for non-reorged source")
+
+	// Commit absence check on the reorged source
+	gomega.NewWithT(t).Consistently(func() bool {
+		it, err := state.Chains[destSelector].OffRamp.FilterCommitReportAccepted(&bind.FilterOpts{Start: 0})
+		require.NoError(t, err)
+		var found bool
+	outer:
+		for it.Next() {
+			for _, mr := range it.Event.UnblessedMerkleRoots {
+				if mr.SourceChainSelector == reorgSource {
+					found = true
+					break outer
+				}
+			}
+		}
+		return !found
+	}, 1*time.Minute, 10*time.Second).Should(gomega.BeTrue())
+}
+
+func getServiceHealth(serviceName string, healthResponses []nodeclient.HealthResponseDetail) nodeclient.HealthCheck {
+	for _, d := range healthResponses {
+		if d.Attributes.Name == serviceName {
+			return d.Attributes
+		}
+	}
+
+	return nodeclient.HealthCheck{}
+}
+
+// Helper to initialize common test components
+func setupReorgTest(t *testing.T, testOpts ...testhelpers.TestOps) (
+	testhelpers.DeployedEnv,
+	logging.Logger,
+	*testsetups.DeployedLocalDevEnvironment,
+	[]string,
+	ccipcs.CCIPOnChainState,
+) {
+	require.Equal(t, os.Getenv(testhelpers.ENVTESTTYPE), string(testhelpers.Docker),
+		"Reorg tests are only supported in docker environments")
+
+	l := logging.GetTestLogger(t)
+	e, _, tEnv := testsetups.NewIntegrationEnvironment(t, testOpts...)
+	dockerEnv := tEnv.(*testsetups.DeployedLocalDevEnvironment)
+
+	nodeInfos, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain)
+	require.NoError(t, err)
+	nonBootstrapP2PIDs := getNonBootstrapP2PIDs(nodeInfos)
+
+	state, err := ccipcs.LoadOnchainState(e.Env)
+	require.NoError(t, err)
+
+	return e, l, dockerEnv, nonBootstrapP2PIDs, state
+}
+
+// Extract non-bootstrap P2P IDs
+func getNonBootstrapP2PIDs(nodeInfos deployment.Nodes) []string {
+	ids := make([]string, 0, len(nodeInfos.NonBootstraps()))
+	for _, n := range nodeInfos.NonBootstraps() {
+		ids = append(ids, strings.TrimPrefix(n.PeerID.String(), "p2p_"))
+	}
+	return ids
+}
+
+// Build chain selector to RPC URL map
+func buildChainSelectorToRPCURLMap(t *testing.T, dockerEnv *testsetups.DeployedLocalDevEnvironment) map[uint64]string {
+	devEnvConfig := dockerEnv.GetDevEnvConfig()
+	require.NotNil(t, devEnvConfig)
+
+	chainSelToRPCURL := make(map[uint64]string)
+	for _, chain := range devEnvConfig.Chains {
+		require.GreaterOrEqual(t, len(chain.HTTPRPCs), 1)
+		details, err := chainsel.GetChainDetailsByChainIDAndFamily(strconv.FormatUint(chain.ChainID, 10), chainsel.FamilyEVM)
+		require.NoError(t, err)
+		chainSelToRPCURL[details.ChainSelector] = chain.HTTPRPCs[0].Internal
+	}
+	return chainSelToRPCURL
+}
+
+// Get log poller service name
+func getLogPollerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.LogPoller", chain.EvmChainID)
+}
+
+func getHeadTrackerService(t *testing.T, chainSelector uint64) string {
+	chain, ok := chainsel.ChainBySelector(chainSelector)
+	require.True(t, ok)
+	return fmt.Sprintf("EVM.%d.HeadTracker", chain.EvmChainID)
+}
+
+// Send CCIP message helper
+func sendCCIPMessage(
+	t *testing.T,
+	env deployment.Environment,
+	state ccipcs.CCIPOnChainState,
+	sourceSelector, destSelector uint64,
+	l logging.Logger,
+) *onramp.OnRampCCIPMessageSent {
+	msgEvent := testhelpers.TestSendRequest(t, env, state, sourceSelector, destSelector, false, router.ClientEVM2AnyMessage{
+		Receiver:     common.LeftPadBytes(state.Chains[destSelector].Receiver.Address().Bytes(), 32),
+		Data:         []byte("hello world"),
+		TokenAmounts: nil,
+		FeeToken:     common.HexToAddress("0x0"),
+		ExtraArgs:    nil,
+	})
+
+	l.Info().
+		Str("messageID", hexutil.Encode(msgEvent.Message.Header.MessageId[:])).
+		Uint64("blockNumber", msgEvent.Raw.BlockNumber).
+		Str("blockHash", msgEvent.Raw.BlockHash.String()).
+		Msg("Sent CCIP message")
+
+	return msgEvent
+}
+
+// Check finality violations helper
+// Uses require.Eventually or gomega.Consistently based on expected count
+func checkFinalityViolations(
+	t *testing.T,
+	nodeAPIs []*nodeclient.ChainlinkClient,
+	nonBootstrapP2PIDs []string,
+	headTrackerServiceName,
+	logPollerServiceName string,
+	l logging.Logger,
+	expectedCount int,
+	timeout time.Duration,
+	interval time.Duration,
+) {
+	checkFunc := func() bool {
+		violated := 0
+		headTrackerViolated := 0
+		logPollerViolated := 0
+		for _, node := range nodeAPIs {
+			p2pKeys, err := node.MustReadP2PKeys()
+			require.NoError(t, err)
+			if len(p2pKeys.Data) == 0 || !slices.Contains(nonBootstrapP2PIDs, p2pKeys.Data[0].Attributes.PeerID) {
+				l.Info().Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).Msg("Skipping bootstrap node")
+				continue
+			}
+
+			resp, _, err := node.Health()
+			require.NoError(t, err)
+			lpHealth := getServiceHealth(logPollerServiceName, resp.Data)
+			htHealth := getServiceHealth(headTrackerServiceName, resp.Data)
+			var lpViolated, htViolated bool
+			if lpHealth.Status == "failing" {
+				if lpHealth.Output == commontypes.ErrFinalityViolated.Error() {
+					logPollerViolated++
+					lpViolated = true
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller finality violation")
+				} else {
+					l.Info().
+						Str("p2pKey", p2pKeys.Data[0].Attributes.PeerID).
+						Str("output", lpHealth.Output).
+						Str("logPollerService", logPollerServiceName).
+						Msg("Log poller unhealthy but not finality violation")
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: makramkd
body: Fixed!
created_at: 2025-04-08T15:59:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: On the same chain. I wonder if there is any edge when multiple below finality reorgs happen very close to each other 
created_at: 2025-04-09T08:57:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,556 @@
+package ccip
+
+import (
+	"fmt"
+	"os"
+	"slices"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/ethereum/go-ethereum/accounts/abi/bind"
+	"github.com/ethereum/go-ethereum/common"
+	"github.com/ethereum/go-ethereum/common/hexutil"
+	"github.com/onsi/gomega"
+	chainsel "github.com/smartcontractkit/chain-selectors"
+	"github.com/stretchr/testify/require"
+	"go.uber.org/zap/zapcore"
+
+	commontypes "github.com/smartcontractkit/chainlink-common/pkg/types"
+	"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
+
+	"github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
+
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_2_0/router"
+	"github.com/smartcontractkit/chainlink-evm/gethwrappers/ccip/generated/v1_6_0/onramp"
+	ctf_client "github.com/smartcontractkit/chainlink-testing-framework/lib/client"
+	"github.com/smartcontractkit/chainlink-testing-framework/lib/logging"
+	"github.com/smartcontractkit/chainlink/deployment"
+	ccipcs "github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
+	"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
+	"github.com/smartcontractkit/chainlink/deployment/environment/nodeclient"
+	testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip"
+)
+
+var (
+	logsToIgnoreOpt = testhelpers.WithLogMessagesToIgnore([]testhelpers.LogMessageToIgnore{
+		{
+			Msg:    "Got very old block.",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Reorg greater than finality depth detected",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+		{
+			Msg:    "Failed to poll and save logs due to finality violation, retrying later",
+			Reason: "We are expecting a re-org beyond finality",
+			Level:  zapcore.DPanicLevel,
+		},
+	})
+)
+
+const (
+	greaterThanFinalityReorgDepth = 60
+	lessThanFinalityReorgDepth    = 7
+)
+
+func Test_CCIPReorg_BelowFinality_OnSource(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	// Chain setup
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Build RPC map and get clients
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	sourceClient := ctf_client.NewRPCClient(chainSelToRPCURL[sourceSelector], nil)
+
+	// Setup CCIP lane
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Send initial message
+	msgBeforeReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+
+	// Wait and perform reorg
+	minBlock := msgBeforeReorg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, sourceClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, sourceClient, lessThanFinalityReorgDepth, l)
+
+	// Verify message consistency
+	msgAfterReorg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	require.Equal(t, msgBeforeReorg.Message.Header.SequenceNumber, msgAfterReorg.Message.Header.SequenceNumber)
+	require.Equal(t, msgBeforeReorg.Message.Header.MessageId, msgAfterReorg.Message.Header.MessageId)
+
+	// Check node health
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, sourceSelector),
+		getLogPollerService(t, sourceSelector),
+		l,
+		0,              // no nodes reporting finality violation
+		1*time.Minute,  // timeout
+		10*time.Second, // interval
+	)
+
+	// Verify commit
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed")
+}
+
+func Test_CCIPReorg_BelowFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, _, state := setupReorgTest(t,
+		testhelpers.WithExtraConfigTomls([]string{t.Name() + ".toml"}),
+	)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	msg := sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Reorg execution
+	minBlock := msg.Raw.BlockNumber + lessThanFinalityReorgDepth - 1
+	waitForBlockNumber(t, destClient, minBlock, 1*time.Minute, 500*time.Millisecond, l)
+	performReorg(t, destClient, lessThanFinalityReorgDepth, l)
+
+	// Post-reorg validation
+	_, err = testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed after reorg")
+}
+
+func Test_CCIPReorg_GreaterThanFinality_OnDest(t *testing.T) {
+	e, l, dockerEnv, nonBootstrapP2PIDs, state := setupReorgTest(t, logsToIgnoreOpt)
+
+	allChains := e.Env.AllChainSelectors()
+	require.GreaterOrEqual(t, len(allChains), 2)
+	sourceSelector := allChains[0]
+	destSelector := allChains[1]
+	l.
+		Info().
+		Uint64("sourceSelector", sourceSelector).
+		Uint64("destSelector", destSelector).
+		Msg("Chain selectors")
+
+	// Chain setup
+	chainSelToRPCURL := buildChainSelectorToRPCURLMap(t, dockerEnv)
+	destClient := ctf_client.NewRPCClient(chainSelToRPCURL[destSelector], nil)
+
+	// Test setup
+	testhelpers.AddLaneWithDefaultPricesAndFeeQuoterConfig(t, &e, state, sourceSelector, destSelector, false)
+	waitForLogPollerFilters(l)
+
+	// Initial operation
+	sendCCIPMessage(t, e.Env, state, sourceSelector, destSelector, l)
+	_, err := testhelpers.ConfirmCommitWithExpectedSeqNumRange(
+		t,
+		sourceSelector,
+		e.Env.Chains[destSelector],
+		state.Chains[destSelector].OffRamp,
+		nil, // startBlock
+		ccipocr3.NewSeqNumRange(1, 1),
+		false, // enforceSingleCommit
+	)
+	require.NoError(t, err, "Commit verification failed before reorg")
+
+	// Deep reorg execution
+	performReorg(t, destClient, greaterThanFinalityReorgDepth, l)
+	nodeAPIs := dockerEnv.GetCLClusterTestEnv().ClCluster.NodeAPIs()
+
+	// Finality violation check
+	checkFinalityViolations(
+		t,
+		nodeAPIs,
+		nonBootstrapP2PIDs,
+		getHeadTrackerService(t, destSelector),
+		getLogPollerService(t, destSelector),
+		l,
+		len(nodeAPIs)-1,
+		tests.WaitTimeout(t),
+		10*time.Second,
+	)
+
+	// Commit absence check
path: integration-tests/smoke/ccip/ccip_reorg_test.go
user: login: mateusz-sekara
body: I know it might be a bit brittle, but maybe we should keep polling for, let's say 1 minute to check for commit report absence like you did in the commented code. We can assume that in 99% cases if CCIP is not halted, Commit will be there in 1 minute at most. Otherwise, it means that processing stopped and test is green, because nothing was published. 
created_at: 2025-04-09T09:00:58Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-01-24T11:02:49Z
body: 












<cicore>

## AER Report: [CI Core](https://github.com/smartcontractkit/chainlink/actions/runs/13674156595) ran successfully :white_check_mark:

[aer_workflow](https://github.com/smartcontractkit/chainlink/actions/runs/13674348841/job/38231423619) , [commit](https://github.com/smartcontractkit/chainlink/commit/c1ccdb326c252c9922eac43bf9d39ff141491c40)

</cicore>
  
    











<operatoruici>

## AER Report: [Operator UI CI](https://github.com/smartcontractkit/chainlink/actions/runs/13674156567) ran successfully :white_check_mark:

[aer_workflow](https://github.com/smartcontractkit/chainlink/actions/runs/13674179615/job/38230885490) , [commit](https://github.com/smartcontractkit/chainlink/commit/c1ccdb326c252c9922eac43bf9d39ff141491c40)

</operatoruici>

author_association: CONTRIBUTOR
user: login: cl-sonarqube-production[bot]
created_at: 2025-01-31T15:35:02Z
body: ## [![Quality Gate failed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/failed-16px.png 'Quality Gate failed')](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056) **Quality Gate failed**  
Failed conditions  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/failed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_reliability_rating&view=list) [C Reliability Rating on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_reliability_rating&view=list) (required ≥ A)  
  
[See analysis details on SonarQube](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056)

![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/bulb-16px.png '') Catch issues before they fail your Quality Gate with our IDE extension [![SonarLint](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/sonarlint-16px.png 'SonarLint')](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request) [SonarLint](https://www.sonarsource.com/products/sonarlint/features/connected-mode/?referrer=pull-request)
author_association: NONE
user: login: cl-sonarqube-production[bot]
created_at: 2025-03-05T10:55:19Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&resolved=false) [1 New issue](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16056) [0 Fixed issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16056)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&issueStatuses=ACCEPTED) [0 Accepted issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16056) [0 Security Hotspots](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16056)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_coverage&view=list) [0.0% Coverage on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056)


author_association: NONE
user: login: cl-sonarqube-production[bot]
created_at: 2025-04-09T07:45:18Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&resolved=false) [1 New issue](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16056) [0 Fixed issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&fixedInPullRequest=16056)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&issueStatuses=ACCEPTED) [0 Accepted issues](https://sonarqube.main.prod.cldev.sh/project/issues?id=smartcontractkit_chainlink&pullRequest=16056&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16056) [0 Security Hotspots](https://sonarqube.main.prod.cldev.sh/security_hotspots?id=smartcontractkit_chainlink&pullRequest=16056)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_coverage&view=list) [0.0% Coverage on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://sonarqube.main.prod.cldev.sh/component_measures?id=smartcontractkit_chainlink&pullRequest=16056&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://sonarqube.main.prod.cldev.sh/dashboard?id=smartcontractkit_chainlink&pullRequest=16056)


author_association: NONE