From a9ba0a923e280ea05ec9e8788b7d854297342c24 Mon Sep 17 00:00:00 2001 From: Balamurali Gopalswami <167726375+b-gopalswami@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:30:02 -0400 Subject: [PATCH] CCIP-2645: Introducing leader lane and setting PriceReportingDisabled in Test (#1120) ## Motivation This [PR](https://github.com/smartcontractkit/ccip/pull/1071) introduces leader lane feature. Setting leader lane will avoid duplicate gas price fetch for the same destination from different source chain. Here in this PR, the expectation is to add a test where leader lane is setup and verify every commitstore report transaction hash emitted two `UsdPerUnitGasUpdated` events. ## Solution Introduce config variable to enable leader lane setup and based on network pair, automatically define set of lanes as leader and non-leader lanes, track the tx hash from the event `UsdPerUnitGasUpdated` and ensure transaction count matches to the number of sources chain selector expected. --- .github/workflows/integration-tests.yml | 7 + .../ccip/testhelpers/ccip_contracts.go | 4 +- .../ocr2/plugins/ccip/testhelpers/config.go | 1 + .../testhelpers_1_4_0/ccip_contracts_1_4_0.go | 4 +- .../testhelpers_1_4_0/config_1_4_0.go | 1 + .../ccip-tests/actions/ccip_helpers.go | 61 +++++--- .../ccip-tests/contracts/contract_deployer.go | 5 +- .../ccip-tests/contracts/contract_models.go | 9 ++ .../ccip-tests/testconfig/README.md | 3 + .../testconfig/tomls/ccip-default.toml | 1 + .../testconfig/tomls/leader-lane.toml | 4 + .../ccip-tests/testsetups/ccip.go | 135 ++++++++++++++++++ 12 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 339bc98df4..a4d82ab67f 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -366,6 +366,13 @@ jobs: os: ubuntu-latest file: ccip run: -run ^TestSmokeCCIPOffRampAggRateLimit$ + - name: ccip-smoke-leader-lane + nodes: 15 + dir: ccip-tests/smoke + os: ubuntu-latest + file: ccip + run: -run ^TestSmokeCCIPForBidirectionalLane$ + config_path: ./integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml - name: runlog id: runlog nodes: 2 diff --git a/core/services/ocr2/plugins/ccip/testhelpers/ccip_contracts.go b/core/services/ocr2/plugins/ccip/testhelpers/ccip_contracts.go index 1f745df9d5..6f9e8f1525 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/ccip_contracts.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/ccip_contracts.go @@ -97,7 +97,8 @@ func NewCommitOffchainConfig( ExecGasPriceDeviationPPB uint32, TokenPriceHeartBeat config.Duration, TokenPriceDeviationPPB uint32, - InflightCacheExpiry config.Duration) CommitOffchainConfig { + InflightCacheExpiry config.Duration, + priceReportingDisabled bool) CommitOffchainConfig { return CommitOffchainConfig{v1_2_0.JSONCommitOffchainConfig{ GasPriceHeartBeat: GasPriceHeartBeat, DAGasPriceDeviationPPB: DAGasPriceDeviationPPB, @@ -105,6 +106,7 @@ func NewCommitOffchainConfig( TokenPriceHeartBeat: TokenPriceHeartBeat, TokenPriceDeviationPPB: TokenPriceDeviationPPB, InflightCacheExpiry: InflightCacheExpiry, + PriceReportingDisabled: priceReportingDisabled, }} } diff --git a/core/services/ocr2/plugins/ccip/testhelpers/config.go b/core/services/ocr2/plugins/ccip/testhelpers/config.go index 4e2f358835..c9d1ca5a12 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/config.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/config.go @@ -41,6 +41,7 @@ func (c *CCIPContracts) createCommitOffchainConfig(t *testing.T, feeUpdateHearBe *config.MustNewDuration(feeUpdateHearBeat), 1, *config.MustNewDuration(inflightCacheExpiry), + false, ).Encode() require.NoError(t, err) return config diff --git a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/ccip_contracts_1_4_0.go b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/ccip_contracts_1_4_0.go index 4ea5bb18d7..47eb8bc62c 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/ccip_contracts_1_4_0.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/ccip_contracts_1_4_0.go @@ -96,7 +96,8 @@ func NewCommitOffchainConfig( ExecGasPriceDeviationPPB uint32, TokenPriceHeartBeat config.Duration, TokenPriceDeviationPPB uint32, - InflightCacheExpiry config.Duration) CommitOffchainConfig { + InflightCacheExpiry config.Duration, + priceReportingDisabled bool) CommitOffchainConfig { return CommitOffchainConfig{v1_2_0.JSONCommitOffchainConfig{ GasPriceHeartBeat: GasPriceHeartBeat, DAGasPriceDeviationPPB: DAGasPriceDeviationPPB, @@ -104,6 +105,7 @@ func NewCommitOffchainConfig( TokenPriceHeartBeat: TokenPriceHeartBeat, TokenPriceDeviationPPB: TokenPriceDeviationPPB, InflightCacheExpiry: InflightCacheExpiry, + PriceReportingDisabled: priceReportingDisabled, }} } diff --git a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/config_1_4_0.go b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/config_1_4_0.go index 751ae5c1a9..666ad79e59 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/config_1_4_0.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/testhelpers_1_4_0/config_1_4_0.go @@ -38,6 +38,7 @@ func (c *CCIPContracts) createCommitOffchainConfig(t *testing.T, feeUpdateHearBe *config.MustNewDuration(feeUpdateHearBeat), 1, *config.MustNewDuration(inflightCacheExpiry), + false, ).Encode() require.NoError(t, err) return config diff --git a/integration-tests/ccip-tests/actions/ccip_helpers.go b/integration-tests/ccip-tests/actions/ccip_helpers.go index 94b4de984c..384b240649 100644 --- a/integration-tests/ccip-tests/actions/ccip_helpers.go +++ b/integration-tests/ccip-tests/actions/ccip_helpers.go @@ -44,7 +44,6 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mockserver" "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/reorg" "github.com/smartcontractkit/chainlink-testing-framework/networks" - "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/contracts/laneconfig" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testconfig" @@ -185,6 +184,7 @@ type CCIPCommon struct { tokenPriceUpdateWatcher map[common.Address]*big.Int // key - token; value - timestamp of update gasUpdateWatcherMu *sync.Mutex gasUpdateWatcher map[uint64]*big.Int // key - destchain id; value - timestamp of update + GasUpdateEvents []contracts.GasUpdateEvent } // FreeUpUnusedSpace sets nil to various elements of ccipModule which are only used @@ -524,6 +524,9 @@ func (ccipModule *CCIPCommon) WaitForPriceUpdates( } } +// WatchForPriceUpdates helps to ensure the price updates are happening in price registry by subscribing to a couple +// of price update events and add the event details to watchers. It subscribes to 'UsdPerUnitGasUpdated' +// and 'UsdPerTokenUpdated' event. func (ccipModule *CCIPCommon) WatchForPriceUpdates(ctx context.Context, lggr *zerolog.Logger) error { gasUpdateEventLatest := make(chan *price_registry.PriceRegistryUsdPerUnitGasUpdated) tokenUpdateEvent := make(chan *price_registry.PriceRegistryUsdPerTokenUpdated) @@ -549,20 +552,28 @@ func (ccipModule *CCIPCommon) WatchForPriceUpdates(ctx context.Context, lggr *ze if tokenUpdateSub == nil { return fmt.Errorf("no event subscription found") } - processEvent := func(timestamp *big.Int, destChainSelector uint64) error { + processEvent := func(value, timestamp *big.Int, destChainSelector uint64, raw types.Log) error { destChain, err := chainselectors.ChainIdFromSelector(destChainSelector) if err != nil { return err } ccipModule.gasUpdateWatcherMu.Lock() ccipModule.gasUpdateWatcher[destChain] = timestamp + + ccipModule.GasUpdateEvents = append(ccipModule.GasUpdateEvents, contracts.GasUpdateEvent{ + Sender: raw.Address.Hex(), + Tx: raw.TxHash.Hex(), + Value: value, + DestChain: destChain, + Source: ccipModule.ChainClient.GetNetworkName(), + }) ccipModule.gasUpdateWatcherMu.Unlock() lggr.Info(). Uint64("chainSelector", destChainSelector). - Str("source_chain", ccipModule.ChainClient.GetNetworkName()). Uint64("dest_chain", destChain). Str("price_registry", ccipModule.PriceRegistry.Address()). - Msgf("UsdPerUnitGasUpdated event received for dest chain %d source chain %s", + Str("tx hash", raw.TxHash.Hex()). + Msgf("UsdPerUnitGasUpdated event received for dest chain: %d, source chain: %s", destChain, ccipModule.ChainClient.GetNetworkName()) return nil } @@ -572,13 +583,14 @@ func (ccipModule *CCIPCommon) WatchForPriceUpdates(ctx context.Context, lggr *ze tokenUpdateSub.Unsubscribe() ccipModule.gasUpdateWatcher = nil ccipModule.gasUpdateWatcherMu = nil + ccipModule.GasUpdateEvents = nil ccipModule.tokenPriceUpdateWatcher = nil ccipModule.tokenPriceUpdateWatcherMu = nil }() for { select { case e := <-gasUpdateEventLatest: - err := processEvent(e.Timestamp, e.DestChain) + err := processEvent(e.Value, e.Timestamp, e.DestChain, e.Raw) if err != nil { continue } @@ -2623,23 +2635,24 @@ func CCIPRequestFromTxHash(txHash common.Hash, chainClient blockchain.EVMClient) } type CCIPLane struct { - Test *testing.T - Logger *zerolog.Logger - SourceNetworkName string - DestNetworkName string - SourceChain blockchain.EVMClient - DestChain blockchain.EVMClient - Source *SourceCCIPModule - Dest *DestCCIPModule - NumberOfReq int - Reports *testreporters.CCIPLaneStats - Balance *BalanceSheet - SentReqs map[common.Hash][]CCIPRequest - TotalFee *big.Int // total fee for all the requests. Used for balance validation. - ValidationTimeout time.Duration - Context context.Context - SrcNetworkLaneCfg *laneconfig.LaneConfig - DstNetworkLaneCfg *laneconfig.LaneConfig + Test *testing.T + Logger *zerolog.Logger + SourceNetworkName string + DestNetworkName string + SourceChain blockchain.EVMClient + DestChain blockchain.EVMClient + Source *SourceCCIPModule + Dest *DestCCIPModule + NumberOfReq int + Reports *testreporters.CCIPLaneStats + Balance *BalanceSheet + SentReqs map[common.Hash][]CCIPRequest + TotalFee *big.Int // total fee for all the requests. Used for balance validation. + ValidationTimeout time.Duration + Context context.Context + SrcNetworkLaneCfg *laneconfig.LaneConfig + DstNetworkLaneCfg *laneconfig.LaneConfig + PriceReportingDisabled bool } func (lane *CCIPLane) TokenPricesConfig() (string, error) { @@ -3645,7 +3658,7 @@ func (lane *CCIPLane) DeployNewCCIPLane( jobParams.P2PV2Bootstrappers = []string{p2pBootstrappersCommit.P2PV2Bootstrapper()} - err = SetOCR2Config(lane.Context, lane.Logger, *testConf, commitNodes, execNodes, *lane.Dest) + err = SetOCR2Config(lane.Context, lane.Logger, *testConf, commitNodes, execNodes, *lane.Dest, lane.PriceReportingDisabled) if err != nil { return fmt.Errorf("failed to set ocr2 config: %w", err) } @@ -3683,6 +3696,7 @@ func SetOCR2Config( commitNodes, execNodes []*client.CLNodesWithKeys, destCCIP DestCCIPModule, + priceReportingDisabled bool, ) error { inflightExpiryExec := commonconfig.MustNewDuration(InflightExpiryExec) inflightExpiryCommit := commonconfig.MustNewDuration(InflightExpiryCommit) @@ -3719,6 +3733,7 @@ func SetOCR2Config( *commonconfig.MustNewDuration(5 * time.Second), 1e6, *inflightExpiryCommit, + priceReportingDisabled, ) if err != nil { return fmt.Errorf("failed to create commit offchain config: %w", err) diff --git a/integration-tests/ccip-tests/contracts/contract_deployer.go b/integration-tests/ccip-tests/contracts/contract_deployer.go index 57d96e5cb6..043c1d4422 100644 --- a/integration-tests/ccip-tests/contracts/contract_deployer.go +++ b/integration-tests/ccip-tests/contracts/contract_deployer.go @@ -1399,7 +1399,8 @@ func NewCommitOffchainConfig( ExecGasPriceDeviationPPB uint32, TokenPriceHeartBeat config.Duration, TokenPriceDeviationPPB uint32, - InflightCacheExpiry config.Duration) (ccipconfig.OffchainConfig, error) { + InflightCacheExpiry config.Duration, + priceReportingDisabled bool) (ccipconfig.OffchainConfig, error) { switch VersionMap[CommitStoreContract] { case Latest: return testhelpers.NewCommitOffchainConfig( @@ -1409,6 +1410,7 @@ func NewCommitOffchainConfig( TokenPriceHeartBeat, TokenPriceDeviationPPB, InflightCacheExpiry, + priceReportingDisabled, ), nil case V1_2_0: return testhelpers_1_4_0.NewCommitOffchainConfig( @@ -1418,6 +1420,7 @@ func NewCommitOffchainConfig( TokenPriceHeartBeat, TokenPriceDeviationPPB, InflightCacheExpiry, + priceReportingDisabled, ), nil default: return nil, fmt.Errorf("version not supported: %s", VersionMap[CommitStoreContract]) diff --git a/integration-tests/ccip-tests/contracts/contract_models.go b/integration-tests/ccip-tests/contracts/contract_models.go index 0ec55a1e54..7008d51b62 100644 --- a/integration-tests/ccip-tests/contracts/contract_models.go +++ b/integration-tests/ccip-tests/contracts/contract_models.go @@ -61,6 +61,15 @@ type Version struct { semver.Version } +// GasUpdateEvent holds the event details of Gas price update +type GasUpdateEvent struct { + Sender string + Tx string + Value *big.Int + DestChain uint64 + Source string +} + // MustVersion creates a new Version object from a semver string and panics if it fails func MustVersion(version string) Version { v := semver.MustParse(version) diff --git a/integration-tests/ccip-tests/testconfig/README.md b/integration-tests/ccip-tests/testconfig/README.md index 3f069934c4..c32aee3d91 100644 --- a/integration-tests/ccip-tests/testconfig/README.md +++ b/integration-tests/ccip-tests/testconfig/README.md @@ -393,6 +393,9 @@ base_url = "https://grafana..../" dashboard_url = "/d/6vjVx-1V8/ccip-long-running-tests" ``` +### CCIP.Env.Lane.LeaderLaneEnabled +Specifies whether to enable the leader lane feature. This setting is only applicable for new deployments. + ## CCIP.Groups Specifies the test config specific to each test type. Available test types are: - **CCIP.Groups.load** diff --git a/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml b/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml index f62612996c..77dc97f77f 100644 --- a/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml +++ b/integration-tests/ccip-tests/testconfig/tomls/ccip-default.toml @@ -284,6 +284,7 @@ CCIPOwnerTokens = false # if true, the test will use deploy the tokens by the CC # ['SIMULATED_1', 'SIMULATED_2'], ['SIMULATED_1', 'SIMULATED_3'], ['SIMULATED_2', 'SIMULATED_3'] #MaxNoOfLanes = # maximum number of lanes to be added in the test; mainly used for scalability tests + [CCIP.Groups.load] # uncomment the following with specific values of lane combinations to be tested, if you want to run your tests to run only on these specific network pairs # if specific network pairs are not mentioned, then all the network pairs will be tested based on values in CCIP.Env.NetworkPairs and CCIP.Groups..NoOfNetworks diff --git a/integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml b/integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml new file mode 100644 index 0000000000..76b97ad97b --- /dev/null +++ b/integration-tests/ccip-tests/testconfig/tomls/leader-lane.toml @@ -0,0 +1,4 @@ +[CCIP] +[CCIP.Groups.smoke] +NoOfNetworks = 4 +NoOfRoutersPerPair = 2 \ No newline at end of file diff --git a/integration-tests/ccip-tests/testsetups/ccip.go b/integration-tests/ccip-tests/testsetups/ccip.go index 27980517d8..3fb25bc675 100644 --- a/integration-tests/ccip-tests/testsetups/ccip.go +++ b/integration-tests/ccip-tests/testsetups/ccip.go @@ -71,6 +71,12 @@ type NetworkPair struct { ChainClientB blockchain.EVMClient } +// LeaderLane is to hold the details of leader lane source and destination network +type LeaderLane struct { + source string + dest string +} + type CCIPTestConfig struct { Test *testing.T EnvInput *testconfig.Common @@ -80,6 +86,7 @@ type CCIPTestConfig struct { AllNetworks map[string]blockchain.EVMNetwork SelectedNetworks []blockchain.EVMNetwork NetworkPairs []NetworkPair + LeaderLanes []LeaderLane GethResourceProfile map[string]interface{} } @@ -322,17 +329,78 @@ func (c *CCIPTestConfig) SetNetworkPairs(lggr zerolog.Logger) error { c.NetworkPairs = newNetworkPairs } + // setting leader lane details to network pairs if it is enabled and only in simulated environments + if !pointer.GetBool(c.TestGroupInput.ExistingDeployment) { + c.defineLeaderLanes(lggr) + } for _, n := range c.NetworkPairs { lggr.Info(). Str("NetworkA", fmt.Sprintf("%s-%d", n.NetworkA.Name, n.NetworkA.ChainID)). Str("NetworkB", fmt.Sprintf("%s-%d", n.NetworkB.Name, n.NetworkB.ChainID)). Msg("Network Pairs") } + for _, lane := range c.LeaderLanes { + lggr.Info(). + Str("Source", lane.source). + Str("Destination", lane.dest). + Msg("Leader Lane: ") + } lggr.Info().Int("Pairs", len(c.NetworkPairs)).Msg("No Of Lanes") return allError } +// defineLeaderLanes goes over the available network pairs and define one leader lane per destination +func (c *CCIPTestConfig) defineLeaderLanes(lggr zerolog.Logger) { + if !isLeaderLaneFeatureEnabled(&lggr) { + return + } + // the way we are defining leader lane is simply by tagging the destination as key along with the first source network + // as value to the map. + leaderLanes := make(map[string]string) + for _, n := range c.NetworkPairs { + if _, ok := leaderLanes[n.NetworkB.Name]; !ok { + leaderLanes[n.NetworkB.Name] = n.NetworkA.Name + } + if pointer.GetBool(c.TestGroupInput.BiDirectionalLane) { + if _, ok := leaderLanes[n.NetworkA.Name]; !ok { + leaderLanes[n.NetworkA.Name] = n.NetworkB.Name + } + } + } + for k, v := range leaderLanes { + c.LeaderLanes = append(c.LeaderLanes, LeaderLane{ + source: v, + dest: k, + }) + } +} + +// isPriceReportingDisabled checks the given lane is leader lane and return boolean accordingly +func (c *CCIPTestConfig) isPriceReportingDisabled(lggr *zerolog.Logger, source, dest string) bool { + for _, leader := range c.LeaderLanes { + if leader.source == source && leader.dest == dest { + lggr.Debug(). + Str("Source", source). + Str("Destination", dest). + Msg("Non-leader lane") + return true + } + } + return false +} + +func isLeaderLaneFeatureEnabled(lggr *zerolog.Logger) bool { + if err := contracts.MatchContractVersionsOrAbove(map[contracts.Name]contracts.Version{ + contracts.OffRampContract: contracts.V1_2_0, + contracts.OnRampContract: contracts.V1_2_0, + }); err != nil { + lggr.Info().Str("Required contract version", contracts.V1_2_0.String()).Msg("Leader lane feature is not enabled") + return false + } + return true +} + func (c *CCIPTestConfig) FormNetworkPairCombinations() { for i := 0; i < c.TestGroupInput.NoOfNetworks; i++ { for j := i + 1; j < c.TestGroupInput.NoOfNetworks; j++ { @@ -635,6 +703,10 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair( Balance: o.Balance, Context: testcontext.Get(t), } + // if it non leader lane, disable the price reporting + ccipLaneA2B.PriceReportingDisabled = len(o.Cfg.LeaderLanes) > 0 && + !o.Cfg.isPriceReportingDisabled(lggr, ccipLaneA2B.SourceNetworkName, ccipLaneA2B.DestNetworkName) + contractsA, ok := o.LaneContractsByNetwork.Load(networkA.Name) if !ok { return errors.WithStack(fmt.Errorf("failed to load lane contracts for %s", networkA.Name)) @@ -689,6 +761,9 @@ func (o *CCIPTestSetUpOutputs) AddLanesForNetworkPair( SrcNetworkLaneCfg: ccipLaneA2B.DstNetworkLaneCfg, DstNetworkLaneCfg: ccipLaneA2B.SrcNetworkLaneCfg, } + // if it non leader lane, disable the price reporting + ccipLaneB2A.PriceReportingDisabled = len(o.Cfg.LeaderLanes) > 0 && + !o.Cfg.isPriceReportingDisabled(lggr, ccipLaneB2A.SourceNetworkName, ccipLaneB2A.DestNetworkName) b2aLogger := lggr.With().Str("env", namespace).Str("Lane", fmt.Sprintf("%s-->%s", ccipLaneB2A.SourceNetworkName, ccipLaneB2A.DestNetworkName)).Logger() ccipLaneB2A.Logger = &b2aLogger @@ -855,6 +930,63 @@ func (o *CCIPTestSetUpOutputs) WaitForPriceUpdates() { require.NoError(t, priceUpdateGrp.Wait()) } +// CheckGasUpdateTransaction checks the gas update transactions count +func (o *CCIPTestSetUpOutputs) CheckGasUpdateTransaction(lggr *zerolog.Logger) error { + transactionsBySource := make(map[string]string) + destToSourcesList := make(map[string][]string) + // create a map to hold the unique destination with list of sources + for _, n := range o.Cfg.NetworkPairs { + destToSourcesList[n.NetworkB.Name] = append(destToSourcesList[n.NetworkB.Name], n.NetworkA.Name) + if pointer.GetBool(o.Cfg.TestGroupInput.BiDirectionalLane) { + destToSourcesList[n.NetworkA.Name] = append(destToSourcesList[n.NetworkA.Name], n.NetworkB.Name) + } + } + lggr.Debug().Interface("list", destToSourcesList).Msg("Dest to Source") + // a function to read the gas update events and create a map with unique source and store the tx hash + filterGasUpdateEventTxBySource := func(lane *actions.CCIPLane) error { + for _, g := range lane.Source.Common.GasUpdateEvents { + if g.Value == nil { + return fmt.Errorf("gas update value should not be nil in tx %s", g.Tx) + } + if _, ok := transactionsBySource[g.Source]; !ok { + transactionsBySource[g.Source] = g.Tx + } + } + return nil + } + + for _, lane := range o.ReadLanes() { + if err := filterGasUpdateEventTxBySource(lane.ForwardLane); err != nil { + return fmt.Errorf("error in filtering gas update transactions in the lane source: %s and destination: %s, error: %w", + lane.ForwardLane.SourceNetworkName, lane.ForwardLane.DestNetworkName, err) + } + if lane.ReverseLane != nil { + if err := filterGasUpdateEventTxBySource(lane.ReverseLane); err != nil { + return fmt.Errorf("error in filtering gas update transactions in the lane source: %s and destination: %s, error: %w", + lane.ReverseLane.SourceNetworkName, lane.ReverseLane.DestNetworkName, err) + } + } + } + + lggr.Debug().Interface("Tx hashes by source", transactionsBySource).Msg("Checked Gas Update Transactions by Source") + // when leader lane setup is enabled, number of unique transaction from the source + // should match the number of leader lanes defined. + if len(transactionsBySource) != len(o.Cfg.LeaderLanes) { + lggr.Error(). + Int("Tx hashes expected", len(o.Cfg.LeaderLanes)). + Int("Tx hashes received", len(transactionsBySource)). + Int("Leader lanes count", len(o.Cfg.LeaderLanes)). + Msg("Checked Gas Update transactions count doesn't match") + return fmt.Errorf("checked Gas Update transactions count doesn't match") + } + lggr.Debug(). + Int("Tx hashes by source", len(transactionsBySource)). + Int("Leader lanes count", len(o.Cfg.LeaderLanes)). + Msg("Checked Gas Update transactions count matches") + + return nil +} + // CCIPDefaultTestSetUp sets up the environment for CCIP tests // if configureCLNode is set as false, it assumes: // 1. contracts are already deployed on live networks @@ -1057,6 +1189,9 @@ func CCIPDefaultTestSetUp( require.NoError(t, setUpArgs.JobAddGrp.Wait(), "Creating jobs shouldn't fail") // wait for price updates to be available setUpArgs.WaitForPriceUpdates() + if isLeaderLaneFeatureEnabled(lggr) && !pointer.GetBool(setUpArgs.Cfg.TestGroupInput.ExistingDeployment) { + require.NoError(t, setUpArgs.CheckGasUpdateTransaction(lggr), "gas update transaction check shouldn't fail") + } // if dynamic price update is required if setUpArgs.Cfg.TestGroupInput.TokenConfig.IsDynamicPriceUpdate() { require.NoError(t, setUpArgs.SetupDynamicTokenPriceUpdates(), "setting up dynamic price update should not fail")