diff --git a/integration-tests/actions/actions.go b/integration-tests/actions/actions.go index c420f8e6729..198fa8e0dc8 100644 --- a/integration-tests/actions/actions.go +++ b/integration-tests/actions/actions.go @@ -25,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" ethContracts "github.com/smartcontractkit/chainlink/integration-tests/contracts/ethereum" "github.com/smartcontractkit/chainlink/integration-tests/wrappers" @@ -285,7 +286,7 @@ func fundChainlinkNodesAtAnyKey( return err } - fromAddress, err := privateKeyToAddress(privateKey) + fromAddress, err := PrivateKeyToAddress(privateKey) if err != nil { return err } @@ -336,7 +337,7 @@ type FundsToSendPayload struct { // to given address. You can override any or none of the following: gas limit, gas price, gas fee cap, gas tip cap. // Values that are not set will be estimated or taken from config. func SendFunds(logger zerolog.Logger, client *seth.Client, payload FundsToSendPayload) (*types.Receipt, error) { - fromAddress, err := privateKeyToAddress(payload.PrivateKey) + fromAddress, err := PrivateKeyToAddress(payload.PrivateKey) if err != nil { return nil, err } @@ -910,7 +911,7 @@ func deployAnyOCRv1Contracts( return ocrInstances, nil } -func privateKeyToAddress(privateKey *ecdsa.PrivateKey) (common.Address, error) { +func PrivateKeyToAddress(privateKey *ecdsa.PrivateKey) (common.Address, error) { publicKey := privateKey.Public() publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) if !ok { diff --git a/integration-tests/actions/refund.go b/integration-tests/actions/refund.go index 1835d9a04a4..bcdf6a380d8 100644 --- a/integration-tests/actions/refund.go +++ b/integration-tests/actions/refund.go @@ -234,6 +234,14 @@ func (r *OvershotTransferRetrier) Retry(ctx context.Context, logger zerolog.Logg // of strategies to attempt to return funds, including retrying with less funds if the transaction fails due to // insufficient funds, and retrying with a higher gas limit if the transaction fails due to gas too low. func ReturnFundsFromNodes(log zerolog.Logger, client *seth.Client, chainlinkNodes []contracts.ChainlinkNodeWithKeysAndAddress) error { + var keyExporters []contracts.ChainlinkKeyExporter + for _, node := range chainlinkNodes { + keyExporters = append(keyExporters, node) + } + return ReturnFundsFromKeyExporterNodes(log, client, keyExporters) +} + +func ReturnFundsFromKeyExporterNodes(log zerolog.Logger, client *seth.Client, chainlinkNodes []contracts.ChainlinkKeyExporter) error { if client == nil { return fmt.Errorf("seth client is nil, unable to return funds from chainlink nodes") } diff --git a/integration-tests/ccip-tests/testsetups/test_env.go b/integration-tests/ccip-tests/testsetups/test_env.go index 4d968e83315..263d291453d 100644 --- a/integration-tests/ccip-tests/testsetups/test_env.go +++ b/integration-tests/ccip-tests/testsetups/test_env.go @@ -332,6 +332,9 @@ func DeployLocalCluster( // a func to start the CL nodes asynchronously deployCL := func() error { noOfNodes := pointer.GetInt(testInputs.EnvInput.NewCLCluster.NoOfNodes) + if env.ClCluster == nil { + env.ClCluster = &test_env.ClCluster{} + } // if individual nodes are specified, then deploy them with specified configs if len(testInputs.EnvInput.NewCLCluster.Nodes) > 0 { for _, clNode := range testInputs.EnvInput.NewCLCluster.Nodes { diff --git a/integration-tests/contracts/contract_models.go b/integration-tests/contracts/contract_models.go index 46f10f06bb0..006ee5db6a8 100644 --- a/integration-tests/contracts/contract_models.go +++ b/integration-tests/contracts/contract_models.go @@ -151,9 +151,13 @@ type OffchainAggregatorData struct { type ChainlinkNodeWithKeysAndAddress interface { MustReadOCRKeys() (*client.OCRKeys, error) MustReadP2PKeys() (*client.P2PKeys, error) - ExportEVMKeysForChain(string) ([]*client.ExportedEVMKey, error) PrimaryEthAddress() (string, error) EthAddresses() ([]string, error) + ChainlinkKeyExporter +} + +type ChainlinkKeyExporter interface { + ExportEVMKeysForChain(string) ([]*client.ExportedEVMKey, error) } type ChainlinkNodeWithForwarder interface { diff --git a/integration-tests/deployment/README.md b/integration-tests/deployment/README.md index 1c2019b540b..000219c8aba 100644 --- a/integration-tests/deployment/README.md +++ b/integration-tests/deployment/README.md @@ -19,11 +19,11 @@ environments like testnet/mainnet. - In-memory environment for fast integration testing - EVM only -/deployment/docker +/deployment/devenv - Coming soon -- package name `docker` +- package name `devenv` - Docker environment for higher fidelity testing -- Support non-EVMs +- Support non-EVMs (yet to be implemented) /deployment/ccip - package name `ccipdeployment` diff --git a/integration-tests/deployment/ccip/add_chain_test.go b/integration-tests/deployment/ccip/add_chain_test.go index fdfbb6e69a6..9441f1a2da6 100644 --- a/integration-tests/deployment/ccip/add_chain_test.go +++ b/integration-tests/deployment/ccip/add_chain_test.go @@ -154,8 +154,9 @@ func TestAddChainInbound(t *testing.T) { // TODO: Send via all inbound lanes and use parallel helper // Now that the proposal has been executed we expect to be able to send traffic to this new 4th chain. - startBlock, err := e.Env.Chains[newChain].LatestBlockNum(testcontext.Get(t)) + latesthdr, err := e.Env.Chains[newChain].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) + startBlock := latesthdr.Number.Uint64() seqNr := SendRequest(t, e.Env, state, initialDeploy[0], newChain, true) require.NoError(t, ConfirmExecWithSeqNr(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, seqNr)) diff --git a/integration-tests/deployment/ccip/add_lane_test.go b/integration-tests/deployment/ccip/add_lane_test.go index 63af3b69c45..d43526d8d49 100644 --- a/integration-tests/deployment/ccip/add_lane_test.go +++ b/integration-tests/deployment/ccip/add_lane_test.go @@ -50,8 +50,9 @@ func TestAddLane(t *testing.T) { require.Len(t, offRamps, 0) } } - startBlock, err := e.Env.Chains[to].LatestBlockNum(testcontext.Get(t)) + latesthdr, err := e.Env.Chains[to].Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) + startBlock := latesthdr.Number.Uint64() seqNum := SendRequest(t, e.Env, state, from, to, false) require.Equal(t, uint64(1), seqNum) require.NoError(t, ConfirmExecWithSeqNr(t, e.Env.Chains[from], e.Env.Chains[to], state.Chains[to].OffRamp, &startBlock, seqNum)) diff --git a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go index 8de2c4617b4..90a8627f38d 100644 --- a/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go +++ b/integration-tests/deployment/ccip/changeset/2_initial_deploy_test.go @@ -62,8 +62,9 @@ func Test0002_InitialDeploy(t *testing.T) { if src == dest { continue } - block, err := destChain.LatestBlockNum(testcontext.Get(t)) + latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) + block := latesthdr.Number.Uint64() startBlocks[dest] = &block seqNum := ccipdeployment.SendRequest(t, e, state, src, dest, false) expectedSeqNum[dest] = seqNum diff --git a/integration-tests/deployment/ccip/test_assertions.go b/integration-tests/deployment/ccip/test_assertions.go index 2041c1a9779..02a10fff3e6 100644 --- a/integration-tests/deployment/ccip/test_assertions.go +++ b/integration-tests/deployment/ccip/test_assertions.go @@ -78,7 +78,15 @@ func ConfirmCommitWithExpectedSeqNumRange( } defer subscription.Unsubscribe() - timer := time.NewTimer(5 * time.Minute) + var duration time.Duration + deadline, ok := t.Deadline() + if ok { + // make this timer end a minute before so that we don't hit the deadline + duration = deadline.Sub(time.Now().Add(-1 * time.Minute)) + } else { + duration = 5 * time.Minute + } + timer := time.NewTimer(duration) defer timer.Stop() ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() @@ -97,8 +105,8 @@ func ConfirmCommitWithExpectedSeqNumRange( case subErr := <-subscription.Err(): return fmt.Errorf("subscription error: %w", subErr) case <-timer.C: - return fmt.Errorf("timed out waiting for commit report on chain selector %d from source selector %d expected seq nr range %s", - dest.Selector, src.Selector, expectedSeqNumRange.String()) + return fmt.Errorf("timed out after waiting %s duration for commit report on chain selector %d from source selector %d expected seq nr range %s", + duration.String(), dest.Selector, src.Selector, expectedSeqNumRange.String()) case report := <-sink: if len(report.Report.MerkleRoots) > 0 { // Check the interval of sequence numbers and make sure it matches diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index 4458a49abc7..330cbae1964 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -8,6 +8,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" @@ -158,7 +160,9 @@ func SendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, }, []uint64{dest}) require.NoError(t, err) require.True(t, it.Next()) - return it.Event.Message.Header.SequenceNumber + seqNum := it.Event.Message.Header.SequenceNumber + t.Logf("CCIP message sent from chain selector %d to chain selector %d tx %s seqNum %d", src, dest, tx.Hash().String(), seqNum) + return seqNum } // DeployedLocalDevEnvironment is a helper struct for setting up a local dev environment with docker @@ -179,15 +183,13 @@ func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLo require.NotEmpty(t, envConfig.JDConfig, "jdUrl should not be empty") chains, err := devenv.NewChains(lggr, envConfig.Chains) require.NoError(t, err) - homeChainSel := uint64(0) - homeChainEVM := uint64(0) + // locate the home chain + homeChainSel := envConfig.HomeChainSelector + require.NotEmpty(t, homeChainSel, "homeChainSel should not be empty") + homeChainEVM, err := chainsel.ChainIdFromSelector(homeChainSel) + require.NoError(t, err) + require.NotEmpty(t, homeChainEVM, "homeChainEVM should not be empty") - // Say first chain is home chain. - for chainSel := range chains { - homeChainEVM, _ = chainsel.ChainIdFromSelector(chainSel) - homeChainSel = chainSel - break - } // deploy the capability registry ab, capReg, err := DeployCapReg(lggr, chains, homeChainSel) require.NoError(t, err) @@ -205,10 +207,9 @@ func NewDeployedLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedLo require.NoError(t, err) require.NotNil(t, e) require.NotNil(t, don) - + zeroLogLggr := logging.GetTestLogger(t) // fund the nodes - require.NoError(t, don.FundNodes(ctx, deployment.E18Mult(10), e.Chains)) - + devenv.FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) return DeployedLocalDevEnvironment{ Ab: ab, Env: *e, diff --git a/integration-tests/deployment/devenv/.sample.env b/integration-tests/deployment/devenv/.sample.env index 97d550079a9..f0505cf857a 100644 --- a/integration-tests/deployment/devenv/.sample.env +++ b/integration-tests/deployment/devenv/.sample.env @@ -2,4 +2,24 @@ E2E_JD_IMAGE= E2E_JD_VERSION= E2E_TEST_CHAINLINK_IMAGE=public.ecr.aws/w0i8p0z9/chainlink-ccip -E2E_TEST_CHAINLINK_VERSION=2.14.0-ccip1.5.0 \ No newline at end of file +E2E_TEST_CHAINLINK_VERSION=2.14.0-ccip1.5.0 + +# RPC Configuration +E2E_TEST_SEPOLIA_WALLET_KEY= +E2E_TEST_SEPOLIA_RPC_HTTP_URL_1= +E2E_TEST_SEPOLIA_RPC_HTTP_URL_2= +E2E_TEST_SEPOLIA_RPC_WS_URL_1= +E2E_TEST_SEPOLIA_RPC_WS_URL_2= + +E2E_TEST_AVALANCHE_FUJI_WALLET_KEY= +E2E_TEST_AVALANCHE_FUJI_RPC_HTTP_URL_1= +E2E_TEST_AVALANCHE_FUJI_RPC_HTTP_URL_2= +E2E_TEST_AVALANCHE_FUJI_RPC_WS_URL_1= +E2E_TEST_AVALANCHE_FUJI_RPC_WS_URL_2= + +E2E_TEST_BSC_TESTNET_WALLET_KEY= +E2E_TEST_BSC_TESTNET_RPC_HTTP_URL_1= +E2E_TEST_BSC_TESTNET_RPC_HTTP_URL_2= +E2E_TEST_BSC_TESTNET_RPC_WS_URL_1= +E2E_TEST_BSC_TESTNET_RPC_WS_URL_2= + diff --git a/integration-tests/deployment/devenv/README.md b/integration-tests/deployment/devenv/README.md new file mode 100644 index 00000000000..a1f264dfce2 --- /dev/null +++ b/integration-tests/deployment/devenv/README.md @@ -0,0 +1,45 @@ +## Overview + +This package is used to create ephemeral environment for local/CI testing. +It sets up an environment with local Docker containers running Chainlink nodes and a job distributor. +It can either create new simulated private Ethereum network containers or connect to existing testnets/mainnets. + +### Run Tests with Devenv + +The tests created with this environment are run as [end-to-end integration smoke tests](../../smoke). + +#### Setting Up Testconfig with Simulated Private Ethereum Network + +To run tests (e.g., [ccip-test](../../smoke/ccip_test.go)), +you need to set up the testconfig following the [testconfig setup instructions](../../testconfig/README.md). +The testconfig specifies the details of the different configurations to set up the environment and run the tests. +Generally, tests are run with the [default](../../testconfig/default.toml) config unless overridden by product-specific config. +For example, the [ccip-test](../../smoke/ccip_test.go) uses [ccip.toml](../../testconfig/ccip/ccip.toml) to specify +CCIP-specific test environment details. + +There are additional secret configuration parameters required by the `devenv` environment that are not stored in the testconfig. +These are read from environment variables. For example, Chainlink image, Job-Distributor image, etc. +All such environment variables are listed in the [sample.env](./.sample.env) file. +You can create a `.env` file in the same directory of the test and set the required environment variables. + +#### Setting Up Testconfig for Running Tests with Existing Testnet/Mainnet + +To run tests with existing testnet/mainnet, set up the testconfig with the details of the testnet/mainnet networks. +Following the integration-test [testconfig framework](../../testconfig/README.md#configuration-and-overrides), +create a new `overrides.toml` file with testnet/mainnet network details and place it under any location in the `integration-tests` directory. +By default, tests are run with private Ethereum network containers set up in the same Docker network as +the Chainlink nodes and job distributor. To run tests against existing testnet/mainnet, +set the `selected_network` field in the testconfig with the specific network names. + +For example, if running [ccip-smoke](../../smoke/ccip_test.go) tests with Sepolia, Avax, and Binance testnets, +copy the contents of [sepolia_avax_binance.toml](../../testconfig/ccip/overrides/sepolia_avax_binance.toml) +to the `overrides.toml` file. + +Before running the test, ensure that RPC and wallet secrets are set as environment variables. +Refer to the environment variables pattern in the [sample.env](./.sample.env) file to +provide necessary secrets applicable to the network you are running the tests against: +- `E2E_TEST__WALLET_KEY_` +- `E2E_TEST__RPC_HTTP_URL_` +- `E2E_TEST__RPC_WS_URL_` + +Now you are all set to run the tests with the existing testnet/mainnet. \ No newline at end of file diff --git a/integration-tests/deployment/devenv/build_env.go b/integration-tests/deployment/devenv/build_env.go index 0373cf0b214..61e677ca435 100644 --- a/integration-tests/deployment/devenv/build_env.go +++ b/integration-tests/deployment/devenv/build_env.go @@ -9,22 +9,30 @@ import ( "github.com/AlekSi/pointer" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/rs/zerolog" chainselectors "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" "github.com/subosito/gotenv" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" + "github.com/smartcontractkit/chainlink-testing-framework/seth" + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/lib/config" ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env/job_distributor" "github.com/smartcontractkit/chainlink-testing-framework/lib/networks" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" + "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" clclient "github.com/smartcontractkit/chainlink/integration-tests/client" + "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/deployment" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/smartcontractkit/chainlink/v2/core/services/relay" ) @@ -43,18 +51,31 @@ func CreateDockerEnv(t *testing.T) ( cfg, err := tc.GetChainAndTestTypeSpecificConfig("Smoke", tc.CCIP) require.NoError(t, err, "Error getting config") + evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig()) + + // find out if the selected networks are provided with PrivateEthereumNetworks configs + // if yes, PrivateEthereumNetworkConfig will be used to create simulated private ethereum networks in docker environment var privateEthereumNetworks []*ctf_config.EthereumNetworkConfig - for _, network := range cfg.CCIP.PrivateEthereumNetworks { - privateEthereumNetworks = append(privateEthereumNetworks, network) + for _, name := range cfg.GetNetworkConfig().SelectedNetworks { + if network, exists := cfg.CCIP.PrivateEthereumNetworks[name]; exists { + privateEthereumNetworks = append(privateEthereumNetworks, network) + } } - env, err := test_env.NewCLTestEnvBuilder(). + + builder := test_env.NewCLTestEnvBuilder(). WithTestConfig(&cfg). WithTestInstance(t). - WithPrivateEthereumNetworks(privateEthereumNetworks). - WithStandardCleanup(). - Build() + WithStandardCleanup() + + // if private ethereum networks are provided, we will use them to create the test environment + // otherwise we will use the network URLs provided in the network config + if len(privateEthereumNetworks) > 0 { + builder = builder.WithPrivateEthereumNetworks(privateEthereumNetworks) + } + env, err := builder.Build() require.NoError(t, err, "Error building test environment") - chains := CreateChainConfigFromPrivateEthereumNetworks(t, env, cfg.CCIP.PrivateEthereumNetworks, cfg.GetNetworkConfig()) + + chains := CreateChainConfigFromNetworks(t, env, privateEthereumNetworks, cfg.GetNetworkConfig()) var jdConfig JDConfig // TODO : move this as a part of test_env setup with an input in testconfig @@ -89,9 +110,24 @@ func CreateDockerEnv(t *testing.T) ( } require.NotEmpty(t, jdConfig, "JD config is empty") + homeChainSelector, err := cfg.CCIP.GetHomeChainSelector() + require.NoError(t, err, "Error getting home chain selector") + homeChainID, err := chainselectors.ChainIdFromSelector(homeChainSelector) + require.NoError(t, err, "Error getting chain id from selector") + // verify if the home chain selector is valid + validHomeChain := false + for _, net := range evmNetworks { + if net.ChainID == int64(homeChainID) { + validHomeChain = true + break + } + } + require.True(t, validHomeChain, "Invalid home chain selector, chain not found in network config") + return &EnvironmentConfig{ - Chains: chains, - JDConfig: jdConfig, + Chains: chains, + JDConfig: jdConfig, + HomeChainSelector: homeChainSelector, }, env, cfg } @@ -107,12 +143,19 @@ func StartChainlinkNodes( ) error { evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig()) for i, net := range evmNetworks { - rpcProvider, err := env.GetRpcProvider(net.ChainID) - require.NoError(t, err, "Error getting rpc provider") - evmNetworks[i].HTTPURLs = rpcProvider.PrivateHttpUrls() - evmNetworks[i].URLs = rpcProvider.PrivateWsUrsl() + // if network is simulated, update the URLs with private chain RPCs in the docker test environment + // so that nodes can internally connect to the chain + if net.Simulated { + rpcProvider, err := env.GetRpcProvider(net.ChainID) + require.NoError(t, err, "Error getting rpc provider") + evmNetworks[i].HTTPURLs = rpcProvider.PrivateHttpUrls() + evmNetworks[i].URLs = rpcProvider.PrivateWsUrsl() + } } noOfNodes := pointer.GetInt(cfg.CCIP.CLNode.NoOfPluginNodes) + pointer.GetInt(cfg.CCIP.CLNode.NoOfBootstraps) + if env.ClCluster == nil { + env.ClCluster = &test_env.ClCluster{} + } var nodeInfo []NodeInfo for i := 1; i <= noOfNodes; i++ { if i <= pointer.GetInt(cfg.CCIP.CLNode.NoOfBootstraps) { @@ -172,17 +215,88 @@ func StartChainlinkNodes( InternalIP: n.API.InternalIP(), } } - envConfig.nodeInfo = nodeInfo return nil } -// CreateChainConfigFromPrivateEthereumNetworks creates a list of ChainConfig from the private ethereum networks created by the test environment. +// FundNodes sends funds to the chainlink nodes based on the provided test config +// It also sets up a clean-up function to return the funds back to the deployer account once the test is done +// It assumes that the chainlink nodes are already started and the account addresses for all chains are available +func FundNodes(t *testing.T, lggr zerolog.Logger, env *test_env.CLClusterTestEnv, cfg tc.TestConfig, nodes []Node) { + evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig()) + for i, net := range evmNetworks { + // if network is simulated, update the URLs with deployed chain RPCs in the docker test environment + if net.Simulated { + rpcProvider, err := env.GetRpcProvider(net.ChainID) + require.NoError(t, err, "Error getting rpc provider") + evmNetworks[i].HTTPURLs = rpcProvider.PublicHttpUrls() + evmNetworks[i].URLs = rpcProvider.PublicWsUrls() + } + } + t.Cleanup(func() { + for i := range evmNetworks { + // if simulated no need for balance return + if evmNetworks[i].Simulated { + continue + } + evmNetwork := evmNetworks[i] + sethClient, err := utils.TestAwareSethClient(t, cfg, &evmNetwork) + require.NoError(t, err, "Error getting seth client for network %s", evmNetwork.Name) + require.Greater(t, len(sethClient.PrivateKeys), 0, seth.ErrNoKeyLoaded) + var keyExporters []contracts.ChainlinkKeyExporter + for j := range nodes { + node := nodes[j] + keyExporters = append(keyExporters, &node) + } + if err := actions.ReturnFundsFromKeyExporterNodes(lggr, sethClient, keyExporters); err != nil { + lggr.Error().Err(err).Str("Network", evmNetwork.Name). + Msg("Error attempting to return funds from chainlink nodes to network's default wallet. " + + "Environment is left running so you can try manually!") + } + } + }) + for i := range evmNetworks { + evmNetwork := evmNetworks[i] + sethClient, err := utils.TestAwareSethClient(t, cfg, &evmNetwork) + require.NoError(t, err, "Error getting seth client for network %s", evmNetwork.Name) + require.Greater(t, len(sethClient.PrivateKeys), 0, seth.ErrNoKeyLoaded) + privateKey := sethClient.PrivateKeys[0] + for _, node := range nodes { + nodeAddr, ok := node.AccountAddr[uint64(evmNetwork.ChainID)] + require.True(t, ok, "Account address not found for chain %d", evmNetwork.ChainID) + fromAddress, err := actions.PrivateKeyToAddress(privateKey) + require.NoError(t, err, "Error getting address from private key") + amount := big.NewFloat(pointer.GetFloat64(cfg.Common.ChainlinkNodeFunding)) + toAddr := common.HexToAddress(nodeAddr) + receipt, err := actions.SendFunds(lggr, sethClient, actions.FundsToSendPayload{ + ToAddress: toAddr, + Amount: conversions.EtherToWei(amount), + PrivateKey: privateKey, + }) + require.NoError(t, err, "Error sending funds to node %s", node.Name) + require.NotNil(t, receipt, "Receipt is nil") + txHash := "(none)" + if receipt != nil { + txHash = receipt.TxHash.String() + } + lggr.Info(). + Str("From", fromAddress.Hex()). + Str("To", toAddr.String()). + Str("TxHash", txHash). + Str("Amount", amount.String()). + Msg("Funded Chainlink node") + } + } +} + +// CreateChainConfigFromNetworks creates a list of ChainConfig from the network config provided in test config. +// It either creates it from the private ethereum networks created by the test environment or from the +// network URLs provided in the network config ( if the network is a live testnet). // It uses the private keys from the network config to create the deployer key for each chain. -func CreateChainConfigFromPrivateEthereumNetworks( +func CreateChainConfigFromNetworks( t *testing.T, env *test_env.CLClusterTestEnv, - privateEthereumNetworks map[string]*ctf_config.EthereumNetworkConfig, + privateEthereumNetworks []*ctf_config.EthereumNetworkConfig, networkConfig *ctf_config.NetworkConfig, ) []ChainConfig { evmNetworks := networks.MustGetSelectedNetworkConfig(networkConfig) @@ -192,6 +306,29 @@ func CreateChainConfigFromPrivateEthereumNetworks( networkPvtKeys[net.ChainID] = net.PrivateKeys[0] } var chains []ChainConfig + // if private ethereum networks are not provided, we will create chains from the network URLs + if len(privateEthereumNetworks) == 0 { + for _, net := range evmNetworks { + chainId := net.ChainID + chainName, err := chainselectors.NameFromChainId(uint64(chainId)) + require.NoError(t, err, "Error getting chain name") + pvtKeyStr, exists := networkPvtKeys[chainId] + require.Truef(t, exists, "Private key not found for chain id %d", chainId) + pvtKey, err := crypto.HexToECDSA(pvtKeyStr) + require.NoError(t, err) + deployer, err := bind.NewKeyedTransactorWithChainID(pvtKey, big.NewInt(chainId)) + require.NoError(t, err) + chains = append(chains, ChainConfig{ + ChainID: uint64(chainId), + ChainName: chainName, + ChainType: "EVM", + WSRPCs: net.URLs, + HTTPRPCs: net.HTTPURLs, + DeployerKey: deployer, + }) + } + return chains + } for _, networkCfg := range privateEthereumNetworks { chainId := networkCfg.EthereumChainConfig.ChainID chainName, err := chainselectors.NameFromChainId(uint64(chainId)) @@ -205,14 +342,12 @@ func CreateChainConfigFromPrivateEthereumNetworks( deployer, err := bind.NewKeyedTransactorWithChainID(pvtKey, big.NewInt(int64(chainId))) require.NoError(t, err) chains = append(chains, ChainConfig{ - ChainID: uint64(chainId), - ChainName: chainName, - ChainType: "EVM", - WSRPCs: rpcProvider.PublicWsUrls(), - HTTPRPCs: rpcProvider.PublicHttpUrls(), - PrivateHTTPRPCs: rpcProvider.PrivateHttpUrls(), - PrivateWSRPCs: rpcProvider.PrivateWsUrsl(), - DeployerKey: deployer, + ChainID: uint64(chainId), + ChainName: chainName, + ChainType: "EVM", + WSRPCs: rpcProvider.PublicWsUrls(), + HTTPRPCs: rpcProvider.PublicHttpUrls(), + DeployerKey: deployer, }) } return chains diff --git a/integration-tests/deployment/devenv/chain.go b/integration-tests/deployment/devenv/chain.go index 6374a2c213d..e40bbc066fd 100644 --- a/integration-tests/deployment/devenv/chain.go +++ b/integration-tests/deployment/devenv/chain.go @@ -3,11 +3,9 @@ package devenv import ( "context" "fmt" - "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/sethvargo/go-retry" @@ -20,14 +18,12 @@ import ( // ChainConfig holds the configuration for a with a deployer key which can be used to send transactions to the chain. type ChainConfig struct { - ChainID uint64 // chain id as per EIP-155, mainly applicable for EVM chains - ChainName string // name of the chain populated from chainselector repo - ChainType string // should denote the chain family. Acceptable values are EVM, COSMOS, SOLANA, STARKNET, APTOS etc - WSRPCs []string // websocket rpcs to connect to the chain - HTTPRPCs []string // http rpcs to connect to the chain - PrivateWSRPCs []string // applicable for private chains spun up with docker/K8s only so that nodes within same cluster can connect internally - PrivateHTTPRPCs []string // applicable for private chains spun up with docker/K8s only so that nodes within same cluster can connect internally - DeployerKey *bind.TransactOpts // key to send transactions to the chain + ChainID uint64 // chain id as per EIP-155, mainly applicable for EVM chains + ChainName string // name of the chain populated from chainselector repo + ChainType string // should denote the chain family. Acceptable values are EVM, COSMOS, SOLANA, STARKNET, APTOS etc + WSRPCs []string // websocket rpcs to connect to the chain + HTTPRPCs []string // http rpcs to connect to the chain + DeployerKey *bind.TransactOpts // key to send transactions to the chain } func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployment.Chain, error) { @@ -52,10 +48,9 @@ func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployme return nil, fmt.Errorf("failed to connect to chain %s", chainCfg.ChainName) } chains[selector] = deployment.Chain{ - Selector: selector, - Client: ec, - DeployerKey: chainCfg.DeployerKey, - LatestBlockNum: ec.BlockNumber, + Selector: selector, + Client: ec, + DeployerKey: chainCfg.DeployerKey, Confirm: func(tx *types.Transaction) (uint64, error) { var blockNumber uint64 if tx == nil { @@ -64,9 +59,13 @@ func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployme err := retry.Do(context.Background(), retry.WithMaxDuration(3*time.Minute, retry.NewFibonacci(1*time.Second)), func(ctx context.Context) error { - receipt, err := ec.TransactionReceipt(ctx, tx.Hash()) + chainId, err := ec.ChainID(ctx) if err != nil { - return retry.RetryableError(fmt.Errorf("failed to get receipt: %w", err)) + return fmt.Errorf("failed to get chain id: %w", err) + } + receipt, err := bind.WaitMined(ctx, ec, tx) + if err != nil { + return retry.RetryableError(fmt.Errorf("failed to get receipt for chain %d: %w", chainId, err)) } if receipt != nil { blockNumber = receipt.BlockNumber.Uint64() @@ -90,32 +89,3 @@ func NewChains(logger logger.Logger, configs []ChainConfig) (map[uint64]deployme } return chains, nil } - -// TODO : Remove this when seth is integrated. -func FundAddress(ctx context.Context, from *bind.TransactOpts, to common.Address, amount *big.Int, c deployment.Chain) error { - nonce, err := c.Client.PendingNonceAt(ctx, from.From) - if err != nil { - return fmt.Errorf("failed to get nonce: %w", err) - } - gp, err := c.Client.SuggestGasPrice(ctx) - if err != nil { - return fmt.Errorf("failed to suggest gas price: %w", err) - } - rawTx := types.NewTx(&types.LegacyTx{ - Nonce: nonce, - GasPrice: gp, - Gas: 21000, - To: &to, - Value: amount, - }) - signedTx, err := from.Signer(from.From, rawTx) - if err != nil { - return fmt.Errorf("failed to sign tx: %w", err) - } - err = c.Client.SendTransaction(ctx, signedTx) - if err != nil { - return fmt.Errorf("failed to send tx: %w", err) - } - _, err = c.Confirm(signedTx) - return err -} diff --git a/integration-tests/deployment/devenv/don.go b/integration-tests/deployment/devenv/don.go index 663f4c3329a..ab64eab5c5e 100644 --- a/integration-tests/deployment/devenv/don.go +++ b/integration-tests/deployment/devenv/don.go @@ -3,17 +3,14 @@ package devenv import ( "context" "fmt" - "math/big" "strconv" "strings" "github.com/AlekSi/pointer" - "github.com/ethereum/go-ethereum/common" "github.com/hashicorp/go-multierror" - chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/rs/zerolog" clclient "github.com/smartcontractkit/chainlink/integration-tests/client" - "github.com/smartcontractkit/chainlink/integration-tests/deployment" nodev1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/node/v1" "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/shared/ptypes" "github.com/smartcontractkit/chainlink/integration-tests/web/sdk/client" @@ -38,6 +35,18 @@ type DON struct { Nodes []Node } +func (don *DON) PluginNodes() []Node { + var pluginNodes []Node + for _, node := range don.Nodes { + for _, label := range node.labels { + if label.Key == NodeLabelKeyType && pointer.GetString(label.Value) == NodeLabelValuePlugin { + pluginNodes = append(pluginNodes, node) + } + } + } + return pluginNodes +} + func (don *DON) NodeIds() []string { var nodeIds []string for _, node := range don.Nodes { @@ -46,27 +55,6 @@ func (don *DON) NodeIds() []string { return nodeIds } -func (don *DON) FundNodes(ctx context.Context, amount *big.Int, chains map[uint64]deployment.Chain) error { - var err error - for sel, chain := range chains { - for _, node := range don.Nodes { - // if node is bootstrap, no need to fund it - if node.multiAddr != "" { - continue - } - accountAddr, ok := node.AccountAddr[sel] - if !ok { - err = multierror.Append(err, fmt.Errorf("node %s has no account address for chain %d", node.Name, sel)) - continue - } - if err1 := FundAddress(ctx, chain.DeployerKey, common.HexToAddress(accountAddr), amount, chain); err1 != nil { - err = multierror.Append(err, err1) - } - } - } - return err -} - func (don *DON) CreateSupportedChains(ctx context.Context, chains []ChainConfig) error { var err error for i, node := range don.Nodes { @@ -129,24 +117,30 @@ func NewNode(nodeInfo NodeInfo) (*Node, error) { Password: nodeInfo.CLConfig.Password, }) if err != nil { - return nil, fmt.Errorf("failed to create FMS client: %w", err) + return nil, fmt.Errorf("failed to create node graphql client: %w", err) + } + chainlinkClient, err := clclient.NewChainlinkClient(&nodeInfo.CLConfig, zerolog.Logger{}) + if err != nil { + return nil, fmt.Errorf("failed to create node rest client: %w", err) } return &Node{ - gqlClient: gqlClient, - Name: nodeInfo.Name, - adminAddr: nodeInfo.AdminAddr, + gqlClient: gqlClient, + restClient: chainlinkClient, + Name: nodeInfo.Name, + adminAddr: nodeInfo.AdminAddr, }, nil } type Node struct { - NodeId string // node id returned by job distributor after node is registered with it - JDId string // job distributor id returned by node after Job distributor is created in node - Name string // name of the node - AccountAddr map[uint64]string // chain selector to node's account address mapping for supported chains - gqlClient client.Client // graphql client to interact with the node - labels []*ptypes.Label // labels with which the node is registered with the job distributor - adminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes - multiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes + NodeId string // node id returned by job distributor after node is registered with it + JDId string // job distributor id returned by node after Job distributor is created in node + Name string // name of the node + AccountAddr map[uint64]string // chain selector to node's account address mapping for supported chains + gqlClient client.Client // graphql client to interact with the node + restClient *clclient.ChainlinkClient // rest client to interact with the node + labels []*ptypes.Label // labels with which the node is registered with the job distributor + adminAddr string // admin address to send payments to, applicable only for non-bootstrap nodes + multiAddr string // multi address denoting node's FQN (needed for deriving P2PBootstrappers in OCR), applicable only for bootstrap nodes } // CreateCCIPOCRSupportedChains creates a JobDistributorChainConfig for the node. @@ -156,10 +150,6 @@ type Node struct { func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainConfig) error { for _, chain := range chains { chainId := strconv.FormatUint(chain.ChainID, 10) - selector, err := chainselectors.SelectorFromChainId(chain.ChainID) - if err != nil { - return fmt.Errorf("failed to get selector from chain id %d: %w", chain.ChainID, err) - } accountAddr, err := n.gqlClient.FetchAccountAddress(ctx, chainId) if err != nil { return fmt.Errorf("failed to fetch account address for node %s: %w", n.Name, err) @@ -170,7 +160,7 @@ func (n *Node) CreateCCIPOCRSupportedChains(ctx context.Context, chains []ChainC if n.AccountAddr == nil { n.AccountAddr = make(map[uint64]string) } - n.AccountAddr[selector] = *accountAddr + n.AccountAddr[chain.ChainID] = *accountAddr peerID, err := n.gqlClient.FetchP2PPeerID(ctx) if err != nil { return fmt.Errorf("failed to fetch peer id for node %s: %w", n.Name, err) @@ -286,3 +276,7 @@ func (n *Node) SetUpAndLinkJobDistributor(ctx context.Context, jd JobDistributor n.JDId = id return nil } + +func (n *Node) ExportEVMKeysForChain(chainId string) ([]*clclient.ExportedEVMKey, error) { + return n.restClient.ExportEVMKeysForChain(chainId) +} diff --git a/integration-tests/deployment/devenv/environment.go b/integration-tests/deployment/devenv/environment.go index a62f7f5e84f..f7513798e37 100644 --- a/integration-tests/deployment/devenv/environment.go +++ b/integration-tests/deployment/devenv/environment.go @@ -14,9 +14,10 @@ const ( ) type EnvironmentConfig struct { - Chains []ChainConfig - nodeInfo []NodeInfo - JDConfig JDConfig + Chains []ChainConfig + HomeChainSelector uint64 + nodeInfo []NodeInfo + JDConfig JDConfig } func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentConfig) (*deployment.Environment, *DON, error) { diff --git a/integration-tests/deployment/environment.go b/integration-tests/deployment/environment.go index 8d8fc909a93..0f3c85a3627 100644 --- a/integration-tests/deployment/environment.go +++ b/integration-tests/deployment/environment.go @@ -45,9 +45,8 @@ type Chain struct { Selector uint64 Client OnchainClient // Note the Sign function can be abstract supporting a variety of key storage mechanisms (e.g. KMS etc). - DeployerKey *bind.TransactOpts - LatestBlockNum func(ctx context.Context) (uint64, error) - Confirm func(tx *types.Transaction) (uint64, error) + DeployerKey *bind.TransactOpts + Confirm func(tx *types.Transaction) (uint64, error) } type Environment struct { diff --git a/integration-tests/deployment/memory/environment.go b/integration-tests/deployment/memory/environment.go index 5ae94464940..409e8d3a816 100644 --- a/integration-tests/deployment/memory/environment.go +++ b/integration-tests/deployment/memory/environment.go @@ -41,9 +41,6 @@ func NewMemoryChains(t *testing.T, numChains int) map[uint64]deployment.Chain { Selector: sel, Client: chain.Backend, DeployerKey: chain.DeployerKey, - LatestBlockNum: func(ctx context.Context) (uint64, error) { - return chain.Backend.Blockchain().CurrentBlock().Number.Uint64(), nil - }, Confirm: func(tx *types.Transaction) (uint64, error) { if tx == nil { return 0, fmt.Errorf("tx was nil, nothing to confirm") diff --git a/integration-tests/docker/test_env/test_env_builder.go b/integration-tests/docker/test_env/test_env_builder.go index a26c72a853b..bd3a458165c 100644 --- a/integration-tests/docker/test_env/test_env_builder.go +++ b/integration-tests/docker/test_env/test_env_builder.go @@ -24,6 +24,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/testreporters" "github.com/smartcontractkit/chainlink-testing-framework/lib/testsummary" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" + "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" ) @@ -409,29 +410,29 @@ func (b *CLTestEnvBuilder) Build() (*CLClusterTestEnv, error) { b.te.rpcProviders[networkConfig.ChainID] = &rpcProvider b.te.EVMNetworks = append(b.te.EVMNetworks, &networkConfig) } + if b.clNodesCount > 0 { + dereferrencedEvms := make([]blockchain.EVMNetwork, 0) + for _, en := range b.te.EVMNetworks { + dereferrencedEvms = append(dereferrencedEvms, *en) + } - dereferrencedEvms := make([]blockchain.EVMNetwork, 0) - for _, en := range b.te.EVMNetworks { - dereferrencedEvms = append(dereferrencedEvms, *en) - } - - nodeConfigInToml := b.testConfig.GetNodeConfig() + nodeConfigInToml := b.testConfig.GetNodeConfig() - nodeConfig, _, err := node.BuildChainlinkNodeConfig( - dereferrencedEvms, - nodeConfigInToml.BaseConfigTOML, - nodeConfigInToml.CommonChainConfigTOML, - nodeConfigInToml.ChainConfigTOMLByChainID, - ) - if err != nil { - return nil, err - } + nodeConfig, _, err := node.BuildChainlinkNodeConfig( + dereferrencedEvms, + nodeConfigInToml.BaseConfigTOML, + nodeConfigInToml.CommonChainConfigTOML, + nodeConfigInToml.ChainConfigTOMLByChainID, + ) + if err != nil { + return nil, err + } - err = b.te.StartClCluster(nodeConfig, b.clNodesCount, b.secretsConfig, b.testConfig, b.clNodesOpts...) - if err != nil { - return nil, err + err = b.te.StartClCluster(nodeConfig, b.clNodesCount, b.secretsConfig, b.testConfig, b.clNodesOpts...) + if err != nil { + return nil, err + } } - b.te.isSimulatedNetwork = true return b.te, nil diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index d6c65f3c0e6..759e8eac1ec 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -73,8 +73,9 @@ func Test0002_InitialDeployOnLocal(t *testing.T) { if src == dest { continue } - block, err := destChain.LatestBlockNum(testcontext.Get(t)) + latesthdr, err := destChain.Client.HeaderByNumber(testcontext.Get(t), nil) require.NoError(t, err) + block := latesthdr.Number.Uint64() startBlocks[dest] = &block seqNum := ccipdeployment.SendRequest(t, e, state, src, dest, false) expectedSeqNum[dest] = seqNum diff --git a/integration-tests/testconfig/ccip/ccip.toml b/integration-tests/testconfig/ccip/ccip.toml index cc8d82f2468..3a27d0cd54d 100644 --- a/integration-tests/testconfig/ccip/ccip.toml +++ b/integration-tests/testconfig/ccip/ccip.toml @@ -1,3 +1,7 @@ +[Common] +# chainlink node funding in native token +chainlink_node_funding = 1 + [Network] selected_networks = ['SIMULATED_1', 'SIMULATED_2'] @@ -86,6 +90,8 @@ DeltaReconcile = '5s' """ [CCIP] +HomeChainSelector = '12922642891491394802' # for chain-2337 + [CCIP.CLNode] NoOfPluginNodes = 4 NoOfBootstraps = 1 diff --git a/integration-tests/testconfig/ccip/config.go b/integration-tests/testconfig/ccip/config.go index a5b168bec2f..b9d969fed1f 100644 --- a/integration-tests/testconfig/ccip/config.go +++ b/integration-tests/testconfig/ccip/config.go @@ -1,6 +1,8 @@ package ccip import ( + "strconv" + "github.com/AlekSi/pointer" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" @@ -21,6 +23,7 @@ type Config struct { PrivateEthereumNetworks map[string]*ctfconfig.EthereumNetworkConfig `toml:",omitempty"` CLNode *NodeConfig `toml:",omitempty"` JobDistributorConfig JDConfig `toml:",omitempty"` + HomeChainSelector *string `toml:",omitempty"` } type NodeConfig struct { @@ -90,3 +93,7 @@ func (o *Config) GetJDDBVersion() string { } return dbversion } + +func (o *Config) GetHomeChainSelector() (uint64, error) { + return strconv.ParseUint(pointer.GetString(o.HomeChainSelector), 10, 64) +} diff --git a/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml new file mode 100644 index 00000000000..06af64d5d91 --- /dev/null +++ b/integration-tests/testconfig/ccip/overrides/sepolia_avax_binance.toml @@ -0,0 +1,55 @@ +[Common] +# chainlink node funding in native token +chainlink_node_funding = 2 + +[Logging] +test_log_collect = true + +[Logging.LogStream] +# supported targets: file, loki, in-memory. if empty no logs will be persisted +log_targets = ["loki"] + +[Network] +selected_networks = ['SEPOLIA', 'AVALANCHE_FUJI', 'BSC_TESTNET'] + +[Network.EVMNetworks.SEPOLIA] +evm_name = 'Sepolia Testnet' +evm_chain_id = 11155111 +evm_simulated = false +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '5m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[Network.EVMNetworks.AVALANCHE_FUJI] +evm_name = 'Avalanche Fuji' +evm_chain_id = 43113 +evm_simulated = false +client_implementation = 'Ethereum' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 1 +evm_gas_estimation_buffer = 1000 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[Network.EVMNetworks.BSC_TESTNET] +evm_name = 'BSC Testnet' +evm_chain_id = 97 +evm_simulated = false +client_implementation = 'BSC' +evm_chainlink_transaction_limit = 5000 +evm_transaction_timeout = '2m' +evm_minimum_confirmations = 3 +evm_gas_estimation_buffer = 0 +evm_supports_eip1559 = true +evm_default_gas_limit = 6000000 +evm_finality_tag = true + +[CCIP] +HomeChainSelector = '16015286601757825753' # for sepolia \ No newline at end of file