From a38ca7880afbbc4f323d66d2c4aa610293293b85 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Tue, 26 Nov 2024 17:06:45 +0200 Subject: [PATCH 01/14] enable rmn tests --- .github/e2e-tests.yml | 60 +++++++++---------- integration-tests/smoke/ccip/ccip_rmn_test.go | 22 +++---- 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index ba08c4029e7..ad27e6d5977 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -1030,21 +1030,20 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e -# Enable after flaking issue is resolved -# - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughObservers$ -# path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_NotEnoughObservers$ -timeout 12m -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 -# E2E_JD_VERSION: 0.6.0 -# E2E_RMN_RAGEPROXY_VERSION: master-f461a9e -# E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughObservers$ + path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_NotEnoughObservers$ -timeout 12m -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 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentSigners$ path: integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1061,22 +1060,20 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e -# Enable after flaking issue is resolved -# - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughSigners$ -# path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_NotEnoughSigners$ -timeout 12m -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 -# E2E_JD_VERSION: 0.6.0 -# E2E_RMN_RAGEPROXY_VERSION: master-f461a9e -# E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_NotEnoughSigners$ + path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_NotEnoughSigners$ -timeout 12m -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 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_DifferentRmnNodesForDifferentChains$ path: integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1093,7 +1090,6 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e - # END: CCIPv1.6 tests # START: CCIP tests diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index d3b7205e5e5..30c849a8f8f 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -74,7 +74,7 @@ func TestRMN_MultipleMessagesOnOneLaneNoWaitForExec(t *testing.T) { func TestRMN_NotEnoughObservers(t *testing.T) { runRmnTestCase(t, rmnTestCase{ name: "one message but not enough observers, should not get a commit report", - passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + passIfNoCommitAfter: 15 * time.Second, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -120,7 +120,7 @@ func TestRMN_DifferentSigners(t *testing.T) { func TestRMN_NotEnoughSigners(t *testing.T) { runRmnTestCase(t, rmnTestCase{ name: "different signers and different observers", - passIfNoCommitAfter: time.Minute, // wait for a minute and assert that commit report was not delivered + passIfNoCommitAfter: 15 * time.Second, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -176,6 +176,8 @@ const ( func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, os.Setenv("ENABLE_RMN", "true")) + ctx := testcontext.Get(t) + envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), len(tc.rmnNodes)) t.Logf("envWithRmn: %#v", envWithRMN) @@ -241,9 +243,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { homeChainState, ok := onChainState.Chains[envWithRMN.HomeChainSel] require.True(t, ok) - allDigests, err := homeChainState.RMNHome.GetConfigDigests(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + allDigests, err := homeChainState.RMNHome.GetConfigDigests(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("RMNHome candidateDigest before setting new candidate: %x, activeDigest: %x", @@ -265,9 +265,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { _, err = deployment.ConfirmIfNoError(homeChain, tx, err) require.NoError(t, err) - candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + candidateDigest, err := homeChainState.RMNHome.GetCandidateDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("RMNHome candidateDigest after setting new candidate: %x", candidateDigest[:]) @@ -281,9 +279,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, err) // check the active digest is the same as the candidate digest - activeDigest, err := homeChainState.RMNHome.GetActiveDigest(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + activeDigest, err := homeChainState.RMNHome.GetActiveDigest(&bind.CallOpts{Context: ctx}) require.NoError(t, err) require.Equalf(t, candidateDigest, activeDigest, "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", @@ -312,9 +308,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, err2) // confirm the config is set correctly - config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{ - Context: testcontext.Get(t), - }) + config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{Context: ctx}) require.NoError(t, err2) require.Equalf(t, activeDigest, From 4ed28e339f906cc61d00d46d2a9add0602b0ebde Mon Sep 17 00:00:00 2001 From: dimkouv Date: Tue, 26 Nov 2024 17:37:44 +0200 Subject: [PATCH 02/14] provide double fee to the msgs --- deployment/ccip/changeset/test_helpers.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 5e8705d4757..1018129f542 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" @@ -31,11 +32,12 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" - "github.com/smartcontractkit/chainlink-ccip/pkg/reader" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-common/pkg/logger" commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" @@ -401,7 +403,7 @@ func CCIPSendRequest( return nil, 0, errors.Wrap(deployment.MaybeDataErr(err), "failed to get fee") } if msg.FeeToken == common.HexToAddress("0x0") { - e.Chains[src].DeployerKey.Value = fee + e.Chains[src].DeployerKey.Value = fee.Mul(fee, big.NewInt(2)) // 2x fee to prevent flaky tests defer func() { e.Chains[src].DeployerKey.Value = nil }() } tx, err := r.CcipSend( From 03edaa6c5262ee732e5bd69a10ae58dac93d90de Mon Sep 17 00:00:00 2001 From: dimkouv Date: Tue, 26 Nov 2024 18:04:02 +0200 Subject: [PATCH 03/14] 10x the fee --- deployment/ccip/changeset/test_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 1018129f542..7682a0d1ed9 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -403,7 +403,7 @@ func CCIPSendRequest( return nil, 0, errors.Wrap(deployment.MaybeDataErr(err), "failed to get fee") } if msg.FeeToken == common.HexToAddress("0x0") { - e.Chains[src].DeployerKey.Value = fee.Mul(fee, big.NewInt(2)) // 2x fee to prevent flaky tests + e.Chains[src].DeployerKey.Value = fee.Mul(fee, big.NewInt(10)) // 10x the fee to prevent flaky tests defer func() { e.Chains[src].DeployerKey.Value = nil }() } tx, err := r.CcipSend( From bd8e392e82edda409f7c8a3c410ac39a3cfabef1 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Tue, 26 Nov 2024 18:20:53 +0200 Subject: [PATCH 04/14] print fees --- deployment/ccip/changeset/test_helpers.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 7682a0d1ed9..bfad9e3950d 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -380,6 +380,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } func CCIPSendRequest( + t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, @@ -415,6 +416,10 @@ func CCIPSendRequest( } blockNum, err := e.Chains[src].Confirm(tx) if err != nil { + currentFee, err := r.GetFee(&bind.CallOpts{Context: context.Background()}, dest, msg) + require.NoError(t, err) + t.Logf("failed to confirm ccip message with fee %s originalFee=%s currentFee=%s", + e.Chains[src].DeployerKey.Value, fee, currentFee) return tx, 0, errors.Wrap(err, "failed to confirm CCIP message") } return tx, blockNum, nil @@ -450,6 +455,7 @@ func TestSendRequest( t.Logf("Sending CCIP request from chain selector %d to chain selector %d", src, dest) tx, blockNum, err := CCIPSendRequest( + t, e, state, src, dest, From d37e2712deacaa9d2e61f1f12cd35948c1ed74c0 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Tue, 26 Nov 2024 18:54:08 +0200 Subject: [PATCH 05/14] revert --- deployment/ccip/changeset/test_helpers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index bfad9e3950d..d135465fda7 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -404,7 +404,7 @@ func CCIPSendRequest( return nil, 0, errors.Wrap(deployment.MaybeDataErr(err), "failed to get fee") } if msg.FeeToken == common.HexToAddress("0x0") { - e.Chains[src].DeployerKey.Value = fee.Mul(fee, big.NewInt(10)) // 10x the fee to prevent flaky tests + e.Chains[src].DeployerKey.Value = fee defer func() { e.Chains[src].DeployerKey.Value = nil }() } tx, err := r.CcipSend( From 449cf6aca7eeee77c75dd06eee753850c8027798 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Wed, 27 Nov 2024 17:24:00 +0200 Subject: [PATCH 06/14] revert flaky test related changes --- deployment/ccip/changeset/test_helpers.go | 6 ------ integration-tests/smoke/ccip/ccip_rmn_test.go | 12 ++++++++++++ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 3eb02065f35..eed5623de5c 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -384,7 +384,6 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } func CCIPSendRequest( - t *testing.T, e deployment.Environment, state CCIPOnChainState, src, dest uint64, @@ -413,10 +412,6 @@ func CCIPSendRequest( } blockNum, err := e.Chains[src].Confirm(tx) if err != nil { - currentFee, err := r.GetFee(&bind.CallOpts{Context: context.Background()}, dest, msg) - require.NoError(t, err) - t.Logf("failed to confirm ccip message with fee %s originalFee=%s currentFee=%s", - e.Chains[src].DeployerKey.Value, fee, currentFee) return tx, 0, errors.Wrap(err, "failed to confirm CCIP message") } return tx, blockNum, nil @@ -490,7 +485,6 @@ func TestSendRequest( t.Logf("Sending CCIP request from chain selector %d to chain selector %d", src, dest) tx, blockNum, err := CCIPSendRequest( - t, e, state, src, dest, diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index a2333828cdd..7b635f9bcea 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -169,6 +169,18 @@ func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { }) } +/* + + if some source chain is cursed then we don't expect messages from that chain + but we still expect messages from the others. + + 1. we curse some chains by calling the rmn remote. + 2. we send msgs from multiple source chains + 3. we wait for those from non-cursed source chains for both commit and exec + 4. we wait to make sure messages from cursed chains are not received + +*/ + const ( chain0 = 0 chain1 = 1 From fbf1c7e13f32516e6fdf98227e9afb964459fb27 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 12:19:04 +0200 Subject: [PATCH 07/14] e2e test with cursed source chain --- integration-tests/smoke/ccip/ccip_rmn_test.go | 162 ++++++++++++++++-- 1 file changed, 144 insertions(+), 18 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 7b635f9bcea..a53b98e8033 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1,9 +1,12 @@ package smoke import ( + "encoding/binary" + "errors" "math/big" "os" "strconv" + "strings" "testing" "time" @@ -13,6 +16,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -169,15 +173,37 @@ func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { }) } -/* +func TestRMN_TwoMessagesOneSourceChainCursed(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "two messages, one source chain is cursed", + passIfNoCommitAfter: 15 * time.Second, + cursedSourceChainIdxs: []int{chain0}, // <---- chain0 is cursed (only as source) + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, // <----- this message should not be committed + {fromChainIdx: chain1, toChainIdx: chain0, count: 1}, + }, + }) +} - if some source chain is cursed then we don't expect messages from that chain - but we still expect messages from the others. +/* - 1. we curse some chains by calling the rmn remote. - 2. we send msgs from multiple source chains - 3. we wait for those from non-cursed source chains for both commit and exec - 4. we wait to make sure messages from cursed chains are not received + 1. nodes are running + 2. stop nodes + 3. call ccipSend (must be called before curse) + 4. curse + 5. nodes start */ @@ -188,6 +214,7 @@ const ( func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, os.Setenv("ENABLE_RMN", "true")) + require.NoError(t, tc.validate()) ctx := testcontext.Get(t) t.Logf("Running RMN test case: %s", tc.name) @@ -299,6 +326,11 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", candidateDigest[:], activeDigest[:]) + cursedSourceChains := mapset.NewSet[uint64]() + for _, chainIdx := range tc.cursedSourceChainIdxs { + cursedSourceChains.Add(chainSelectors[chainIdx]) + } + // Set RMN remote config appropriately for _, remoteCfg := range tc.remoteChainsConfig { remoteSel := chainSelectors[remoteCfg.chainIdx] @@ -347,10 +379,28 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { // Add all lanes require.NoError(t, changeset.AddLanesForAll(envWithRMN.Env, onChainState)) + // disable nodes + disabledNodes := make([]string, 0) + if len(tc.cursedSourceChainIdxs) > 0 { + listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) + require.NoError(t, err) + + for _, n := range listNodesResp.Nodes { + if strings.HasPrefix(n.Name, "bootstrap") { + continue + } + _, err := envWithRMN.Env.Offchain.DisableNode(ctx, &node.DisableNodeRequest{Id: n.Id}) + require.NoError(t, err) + disabledNodes = append(disabledNodes, n.Id) + t.Logf("node %s disabled", n.Id) + } + } + // Need to keep track of the block number for each chain so that event subscription can be done from that block. + // send msgs startBlocks := make(map[uint64]*uint64) - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) + seqNumCommit := make(map[changeset.SourceDestPair]uint64) + seqNumExec := make(map[changeset.SourceDestPair][]uint64) for _, msg := range tc.messagesToSend { fromChain := chainSelectors[msg.fromChainIdx] toChain := chainSelectors[msg.toChainIdx] @@ -363,11 +413,11 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { FeeToken: common.HexToAddress("0x0"), ExtraArgs: nil, }) - expectedSeqNum[changeset.SourceDestPair{ + seqNumCommit[changeset.SourceDestPair{ SourceChainSelector: fromChain, DestChainSelector: toChain, }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ + seqNumExec[changeset.SourceDestPair{ SourceChainSelector: fromChain, DestChainSelector: toChain, }] = []uint64{msgSentEvent.SequenceNumber} @@ -377,17 +427,69 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { zero := uint64(0) startBlocks[toChain] = &zero } - t.Logf("Sent all messages, expectedSeqNum: %v", expectedSeqNum) + t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) + + // curse + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + chain, ok := envWithRMN.Env.Chains[remoteSel] + require.True(t, ok) + for _, chainSel := range cursedSourceChains.ToSlice() { + txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, chainSelectorToBytes16(chainSel)) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) + require.NoError(t, errConfirm) + } + + cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) + require.NoError(t, err) + t.Logf("Cursed subjects: %v", cs) + } + + // re-enable oracles + for _, n := range disabledNodes { + _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) + require.NoError(t, err) + t.Logf("node %s re-enabled", n) + } + + expectedSeqNum := make(map[changeset.SourceDestPair]uint64) + for k, v := range seqNumCommit { + if !cursedSourceChains.Contains(k.SourceChainSelector) { + expectedSeqNum[k] = v + } + } + + t.Logf("expectedSeqNums: %v", expectedSeqNum) + t.Logf("expectedSeqNums including cursed chains: %v", seqNumCommit) + + if len(tc.cursedSourceChainIdxs) > 0 && len(seqNumCommit) == len(expectedSeqNum) { + t.Fatalf("test case is wrong: no message was sent to non-cursed chains") + } commitReportReceived := make(chan struct{}) go func() { changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) commitReportReceived <- struct{}{} + + if cursedSourceChains.Cardinality() > 0 { + // wait for a duration and assert that commit reports were not delivered for cursed source chains + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, seqNumCommit, startBlocks) + commitReportReceived <- struct{}{} + } }() if tc.passIfNoCommitAfter > 0 { // wait for a duration and assert that commit reports were not delivered + if len(tc.cursedSourceChainIdxs) > 0 && len(expectedSeqNum) > 0 { + t.Logf("⌛ Waiting for commit reports of non-cursed chains...") + <-commitReportReceived + t.Logf("✅ Commit reports of non-cursed chains received") + } + tim := time.NewTimer(tc.passIfNoCommitAfter) t.Logf("waiting for %s before asserting that commit report was not received", tc.passIfNoCommitAfter) + select { case <-commitReportReceived: t.Errorf("Commit report was received while it was not expected") @@ -403,7 +505,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { if tc.waitForExec { t.Logf("⌛ Waiting for exec reports...") - changeset.ConfirmExecWithSeqNrsForAll(t, envWithRMN.Env, onChainState, expectedSeqNumExec, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, envWithRMN.Env, onChainState, seqNumExec, startBlocks) t.Logf("✅ Exec report") } } @@ -453,9 +555,33 @@ type rmnTestCase struct { // If set to 0, the test will wait for commit reports. // If set to a positive value, the test will wait for that duration and will assert that commit report was not delivered. passIfNoCommitAfter time.Duration - waitForExec bool - homeChainConfig homeChainConfig - remoteChainsConfig []remoteChainConfig - rmnNodes []rmnNode - messagesToSend []messageToSend + // If set to true, the test will only wait for non-cursed chain msgs. + // And then wait for passIfNoCommitAfter (must be set) to assert that msgs from cursed sources are not transmitted. + // At the moment, it does not support waitForExec=true since only commit plugin has cursing checks. + cursedSourceChainIdxs []int + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend +} + +func (tc rmnTestCase) validate() error { + if len(tc.cursedSourceChainIdxs) > 0 { + if tc.waitForExec { + return errors.New("cursedSourceChainIdxs is set but waitForExec is true which is not supported") + } + if tc.passIfNoCommitAfter == 0 { + return errors.New("cursedSourceChainIdxs is set but passIfNoCommitAfter is not set") + } + } + + return nil +} + +func chainSelectorToBytes16(chainSel uint64) [16]byte { + var result [16]byte + // Convert the uint64 to bytes and place it in the last 8 bytes of the array + binary.BigEndian.PutUint64(result[8:], chainSel) + return result } From 4d46f861716fe27ea5e3c2eefa504767b312a37a Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 12:37:55 +0200 Subject: [PATCH 08/14] include global curse test and enable tests in ci --- .github/e2e-tests.yml | 30 +++++++++ integration-tests/smoke/ccip/ccip_rmn_test.go | 63 ++++++++++++++----- 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index ad27e6d5977..6b98d74bfd8 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -1090,6 +1090,36 @@ runner-test-matrix: E2E_RMN_RAGEPROXY_VERSION: master-f461a9e E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOneSourceChainCursed$ + path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_TwoMessagesOneSourceChainCursed$ -timeout 12m -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 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + + - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_GlobalCurseTwoMessagesOnTwoLanes$ + path: integration-tests/smoke/ccip/ccip_rmn_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 -test.run ^TestRMN_GlobalCurseTwoMessagesOnTwoLanes$ -timeout 12m -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 + E2E_JD_VERSION: 0.6.0 + E2E_RMN_RAGEPROXY_VERSION: master-f461a9e + E2E_RMN_AFN2PROXY_VERSION: master-f461a9e + # END: CCIPv1.6 tests # START: CCIP tests diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index a53b98e8033..3c26c75139a 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -16,6 +16,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" + "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/types" "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -197,15 +198,30 @@ func TestRMN_TwoMessagesOneSourceChainCursed(t *testing.T) { }) } -/* - - 1. nodes are running - 2. stop nodes - 3. call ccipSend (must be called before curse) - 4. curse - 5. nodes start - -*/ +func TestRMN_GlobalCurseTwoMessagesOnTwoLanes(t *testing.T) { + runRmnTestCase(t, rmnTestCase{ + name: "global curse messages on two lanes", + waitForExec: false, + homeChainConfig: homeChainConfig{ + f: map[int]int{chain0: 1, chain1: 1}, + }, + remoteChainsConfig: []remoteChainConfig{ + {chainIdx: chain0, f: 1}, + {chainIdx: chain1, f: 1}, + }, + rmnNodes: []rmnNode{ + {id: 0, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 1, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + {id: 2, isSigner: true, observedChainIdxs: []int{chain0, chain1}}, + }, + messagesToSend: []messageToSend{ + {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, + {fromChainIdx: chain1, toChainIdx: chain0, count: 5}, + }, + globalCurse: true, + passIfNoCommitAfter: 15 * time.Second, + }) +} const ( chain0 = 0 @@ -381,7 +397,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { // disable nodes disabledNodes := make([]string, 0) - if len(tc.cursedSourceChainIdxs) > 0 { + if len(tc.cursedSourceChainIdxs) > 0 || tc.globalCurse { listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) require.NoError(t, err) @@ -442,6 +458,12 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { require.NoError(t, errConfirm) } + if tc.globalCurse { + txCurseGlobal, errCurseGlobal := chState.RMNRemote.Curse(chain.DeployerKey, types.GlobalCurseSubject) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurseGlobal, errCurseGlobal) + require.NoError(t, errConfirm) + } + cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) require.NoError(t, err) t.Logf("Cursed subjects: %v", cs) @@ -559,11 +581,13 @@ type rmnTestCase struct { // And then wait for passIfNoCommitAfter (must be set) to assert that msgs from cursed sources are not transmitted. // At the moment, it does not support waitForExec=true since only commit plugin has cursing checks. cursedSourceChainIdxs []int - waitForExec bool - homeChainConfig homeChainConfig - remoteChainsConfig []remoteChainConfig - rmnNodes []rmnNode - messagesToSend []messageToSend + // globalCurse marks every chain as cursed by setting the global curse subject on each rmnRemote + globalCurse bool + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend } func (tc rmnTestCase) validate() error { @@ -576,6 +600,15 @@ func (tc rmnTestCase) validate() error { } } + if tc.globalCurse { + if tc.passIfNoCommitAfter == 0 { + return errors.New("globalCurse is set but passIfNoCommitAfter is not set") + } + if len(tc.cursedSourceChainIdxs) > 0 { + return errors.New("globalCurse is set but cursedSourceChainIdxs is not empty, this is not supported") + } + } + return nil } From 378dc598da7a740541f61f58f465a4a73a6b3a82 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 13:07:35 +0200 Subject: [PATCH 09/14] refactor rmn integration test case runner --- integration-tests/smoke/ccip/ccip_rmn_test.go | 396 ++++++++++-------- 1 file changed, 210 insertions(+), 186 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 3c26c75139a..7fa412dabe3 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -1,6 +1,7 @@ package smoke import ( + "context" "encoding/binary" "errors" "math/big" @@ -20,6 +21,7 @@ import ( "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/node" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment/environment/devenv" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -238,57 +240,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { envWithRMN, rmnCluster := testsetups.NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t), len(tc.rmnNodes)) t.Logf("envWithRmn: %#v", envWithRMN) - var chainSelectors []uint64 - for _, chain := range envWithRMN.Env.Chains { - chainSelectors = append(chainSelectors, chain.Selector) - } - require.Greater(t, len(chainSelectors), 1, "There should be at least two chains") - - remoteChainSelectors := make([]uint64, 0, len(envWithRMN.Env.Chains)-1) - for _, chain := range envWithRMN.Env.Chains { - remoteChainSelectors = append(remoteChainSelectors, chain.Selector) - } - require.Greater(t, len(remoteChainSelectors), 0, "There should be at least one remote chain") - - var ( - rmnHomeNodes []rmn_home.RMNHomeNode - rmnRemoteSigners []rmn_remote.RMNRemoteSigner - ) - - for _, rmnNodeInfo := range tc.rmnNodes { - rmn := rmnCluster.Nodes["rmn_"+strconv.Itoa(rmnNodeInfo.id)] - - var offchainPublicKey [32]byte - copy(offchainPublicKey[:], rmn.RMN.OffchainPublicKey) - - rmnHomeNodes = append(rmnHomeNodes, rmn_home.RMNHomeNode{ - PeerId: rmn.Proxy.PeerID, - OffchainPublicKey: offchainPublicKey, - }) - - if rmnNodeInfo.isSigner { - if rmnNodeInfo.id < 0 { - t.Fatalf("node id is negative: %d", rmnNodeInfo.id) - } - rmnRemoteSigners = append(rmnRemoteSigners, rmn_remote.RMNRemoteSigner{ - OnchainPublicKey: rmn.RMN.EVMOnchainPublicKey, - NodeIndex: uint64(rmnNodeInfo.id), - }) - } - } - - var rmnHomeSourceChains []rmn_home.RMNHomeSourceChain - for remoteChainIdx, remoteF := range tc.homeChainConfig.f { - if remoteF < 0 { - t.Fatalf("negative remote F: %d", remoteF) - } - // configure remote chain details on the home contract - rmnHomeSourceChains = append(rmnHomeSourceChains, rmn_home.RMNHomeSourceChain{ - ChainSelector: chainSelectors[remoteChainIdx], - F: uint64(remoteF), - ObserverNodesBitmap: createObserverNodesBitmap(chainSelectors[remoteChainIdx], tc.rmnNodes, chainSelectors), - }) - } + tc.populateFields(t, envWithRMN, rmnCluster) onChainState, err := changeset.LoadOnchainState(envWithRMN.Env) require.NoError(t, err) @@ -306,14 +258,8 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { t.Logf("RMNHome candidateDigest before setting new candidate: %x, activeDigest: %x", allDigests.CandidateConfigDigest[:], allDigests.ActiveConfigDigest[:]) - staticConfig := rmn_home.RMNHomeStaticConfig{ - Nodes: rmnHomeNodes, - OffchainConfig: []byte{}, - } - dynamicConfig := rmn_home.RMNHomeDynamicConfig{ - SourceChains: rmnHomeSourceChains, - OffchainConfig: []byte{}, - } + staticConfig := rmn_home.RMNHomeStaticConfig{Nodes: tc.pf.rmnHomeNodes, OffchainConfig: []byte{}} + dynamicConfig := rmn_home.RMNHomeDynamicConfig{SourceChains: tc.pf.rmnHomeSourceChains, OffchainConfig: []byte{}} t.Logf("Setting RMNHome candidate with staticConfig: %+v, dynamicConfig: %+v, current candidateDigest: %x", staticConfig, dynamicConfig, allDigests.CandidateConfigDigest[:]) tx, err := homeChainState.RMNHome.SetCandidate(homeChain.DeployerKey, staticConfig, dynamicConfig, allDigests.CandidateConfigDigest) @@ -342,143 +288,24 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", candidateDigest[:], activeDigest[:]) - cursedSourceChains := mapset.NewSet[uint64]() - for _, chainIdx := range tc.cursedSourceChainIdxs { - cursedSourceChains.Add(chainSelectors[chainIdx]) - } - - // Set RMN remote config appropriately - for _, remoteCfg := range tc.remoteChainsConfig { - remoteSel := chainSelectors[remoteCfg.chainIdx] - chState, ok := onChainState.Chains[remoteSel] - require.True(t, ok) - if remoteCfg.f < 0 { - t.Fatalf("negative F: %d", remoteCfg.f) - } - rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ - RmnHomeContractConfigDigest: activeDigest, - Signers: rmnRemoteSigners, - F: uint64(remoteCfg.f), - } - - chain := envWithRMN.Env.Chains[chainSelectors[remoteCfg.chainIdx]] - - t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) - tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) - require.NoError(t, err2) - _, err2 = deployment.ConfirmIfNoError(chain, tx2, err2) - require.NoError(t, err2) + tc.setRmnRemoteConfig(t, ctx, onChainState, activeDigest, envWithRMN) - // confirm the config is set correctly - config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{Context: ctx}) - require.NoError(t, err2) - require.Equalf(t, - activeDigest, - config.Config.RmnHomeContractConfigDigest, - "RMNRemote config digest should be the same as the active digest of RMNHome after setting, RMNHome active: %x, RMNRemote config: %x", - activeDigest[:], config.Config.RmnHomeContractConfigDigest[:]) - - t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) - } - - // Kill the RMN nodes that are marked for force exit - for _, n := range tc.rmnNodes { - if n.forceExit { - t.Logf("Pausing RMN node %d", n.id) - rmnN := rmnCluster.Nodes["rmn_"+strconv.Itoa(n.id)] - require.NoError(t, osutil.ExecCmd(zerolog.Nop(), "docker kill "+rmnN.Proxy.ContainerName)) - t.Logf("Paused RMN node %d", n.id) - } - } + tc.killMarkedRmnNodes(t, rmnCluster) changeset.ReplayLogs(t, envWithRMN.Env.Offchain, envWithRMN.ReplayBlocks) - // Add all lanes require.NoError(t, changeset.AddLanesForAll(envWithRMN.Env, onChainState)) + disabledNodes := tc.disableOraclesIfThisIsACursingTestCase(t, ctx, envWithRMN) - // disable nodes - disabledNodes := make([]string, 0) - if len(tc.cursedSourceChainIdxs) > 0 || tc.globalCurse { - listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) - require.NoError(t, err) - - for _, n := range listNodesResp.Nodes { - if strings.HasPrefix(n.Name, "bootstrap") { - continue - } - _, err := envWithRMN.Env.Offchain.DisableNode(ctx, &node.DisableNodeRequest{Id: n.Id}) - require.NoError(t, err) - disabledNodes = append(disabledNodes, n.Id) - t.Logf("node %s disabled", n.Id) - } - } - - // Need to keep track of the block number for each chain so that event subscription can be done from that block. - // send msgs - startBlocks := make(map[uint64]*uint64) - seqNumCommit := make(map[changeset.SourceDestPair]uint64) - seqNumExec := make(map[changeset.SourceDestPair][]uint64) - for _, msg := range tc.messagesToSend { - fromChain := chainSelectors[msg.fromChainIdx] - toChain := chainSelectors[msg.toChainIdx] - - for i := 0; i < msg.count; i++ { - msgSentEvent := changeset.TestSendRequest(t, envWithRMN.Env, onChainState, fromChain, toChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(onChainState.Chains[toChain].Receiver.Address().Bytes(), 32), - Data: []byte("hello world"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - seqNumCommit[changeset.SourceDestPair{ - SourceChainSelector: fromChain, - DestChainSelector: toChain, - }] = msgSentEvent.SequenceNumber - seqNumExec[changeset.SourceDestPair{ - SourceChainSelector: fromChain, - DestChainSelector: toChain, - }] = []uint64{msgSentEvent.SequenceNumber} - t.Logf("Sent message from chain %d to chain %d with seqNum %d", fromChain, toChain, msgSentEvent.SequenceNumber) - } - - zero := uint64(0) - startBlocks[toChain] = &zero - } + startBlocks, seqNumCommit, seqNumExec := tc.sendMessages(t, onChainState, envWithRMN) t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) - // curse - for _, remoteCfg := range tc.remoteChainsConfig { - remoteSel := chainSelectors[remoteCfg.chainIdx] - chState, ok := onChainState.Chains[remoteSel] - require.True(t, ok) - chain, ok := envWithRMN.Env.Chains[remoteSel] - require.True(t, ok) - for _, chainSel := range cursedSourceChains.ToSlice() { - txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, chainSelectorToBytes16(chainSel)) - _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) - require.NoError(t, errConfirm) - } + tc.callContractsToCurseChains(t, ctx, onChainState, envWithRMN) - if tc.globalCurse { - txCurseGlobal, errCurseGlobal := chState.RMNRemote.Curse(chain.DeployerKey, types.GlobalCurseSubject) - _, errConfirm := deployment.ConfirmIfNoError(chain, txCurseGlobal, errCurseGlobal) - require.NoError(t, errConfirm) - } - - cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) - require.NoError(t, err) - t.Logf("Cursed subjects: %v", cs) - } - - // re-enable oracles - for _, n := range disabledNodes { - _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) - require.NoError(t, err) - t.Logf("node %s re-enabled", n) - } + tc.enableOracles(t, ctx, envWithRMN, disabledNodes) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) for k, v := range seqNumCommit { - if !cursedSourceChains.Contains(k.SourceChainSelector) { + if !tc.pf.cursedSourceChains.Contains(k.SourceChainSelector) { expectedSeqNum[k] = v } } @@ -495,7 +322,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) commitReportReceived <- struct{}{} - if cursedSourceChains.Cardinality() > 0 { + if tc.pf.cursedSourceChains.Cardinality() > 0 { // wait for a duration and assert that commit reports were not delivered for cursed source chains changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, seqNumCommit, startBlocks) commitReportReceived <- struct{}{} @@ -588,6 +415,63 @@ type rmnTestCase struct { remoteChainsConfig []remoteChainConfig rmnNodes []rmnNode messagesToSend []messageToSend + + // populated fields after environment setup + pf testCasePopulatedFields +} + +type testCasePopulatedFields struct { + chainSelectors []uint64 + rmnHomeNodes []rmn_home.RMNHomeNode + rmnRemoteSigners []rmn_remote.RMNRemoteSigner + rmnHomeSourceChains []rmn_home.RMNHomeSourceChain + cursedSourceChains mapset.Set[uint64] +} + +func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.DeployedEnv, rmnCluster devenv.RMNCluster) { + require.GreaterOrEqual(t, len(envWithRMN.Env.Chains), 2, "test assumes at least two chains") + for _, chain := range envWithRMN.Env.Chains { + tc.pf.chainSelectors = append(tc.pf.chainSelectors, chain.Selector) + } + + for _, rmnNodeInfo := range tc.rmnNodes { + rmn := rmnCluster.Nodes["rmn_"+strconv.Itoa(rmnNodeInfo.id)] + + var offchainPublicKey [32]byte + copy(offchainPublicKey[:], rmn.RMN.OffchainPublicKey) + + tc.pf.rmnHomeNodes = append(tc.pf.rmnHomeNodes, rmn_home.RMNHomeNode{ + PeerId: rmn.Proxy.PeerID, + OffchainPublicKey: offchainPublicKey, + }) + + if rmnNodeInfo.isSigner { + if rmnNodeInfo.id < 0 { + t.Fatalf("node id is negative: %d", rmnNodeInfo.id) + } + tc.pf.rmnRemoteSigners = append(tc.pf.rmnRemoteSigners, rmn_remote.RMNRemoteSigner{ + OnchainPublicKey: rmn.RMN.EVMOnchainPublicKey, + NodeIndex: uint64(rmnNodeInfo.id), + }) + } + } + + for remoteChainIdx, remoteF := range tc.homeChainConfig.f { + if remoteF < 0 { + t.Fatalf("negative remote F: %d", remoteF) + } + // configure remote chain details on the home contract + tc.pf.rmnHomeSourceChains = append(tc.pf.rmnHomeSourceChains, rmn_home.RMNHomeSourceChain{ + ChainSelector: tc.pf.chainSelectors[remoteChainIdx], + F: uint64(remoteF), + ObserverNodesBitmap: createObserverNodesBitmap(tc.pf.chainSelectors[remoteChainIdx], tc.rmnNodes, tc.pf.chainSelectors), + }) + } + + tc.pf.cursedSourceChains = mapset.NewSet[uint64]() + for _, chainIdx := range tc.cursedSourceChainIdxs { + tc.pf.cursedSourceChains.Add(tc.pf.chainSelectors[chainIdx]) + } } func (tc rmnTestCase) validate() error { @@ -612,6 +496,146 @@ func (tc rmnTestCase) validate() error { return nil } +func (tc rmnTestCase) setRmnRemoteConfig( + t *testing.T, + ctx context.Context, + onChainState changeset.CCIPOnChainState, + activeDigest [32]byte, + envWithRMN changeset.DeployedEnv) { + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + if remoteCfg.f < 0 { + t.Fatalf("negative F: %d", remoteCfg.f) + } + rmnRemoteConfig := rmn_remote.RMNRemoteConfig{ + RmnHomeContractConfigDigest: activeDigest, + Signers: tc.pf.rmnRemoteSigners, + F: uint64(remoteCfg.f), + } + + chain := envWithRMN.Env.Chains[tc.pf.chainSelectors[remoteCfg.chainIdx]] + + t.Logf("Setting RMNRemote config with RMNHome active digest: %x, cfg: %+v", activeDigest[:], rmnRemoteConfig) + tx2, err2 := chState.RMNRemote.SetConfig(chain.DeployerKey, rmnRemoteConfig) + require.NoError(t, err2) + _, err2 = deployment.ConfirmIfNoError(chain, tx2, err2) + require.NoError(t, err2) + + // confirm the config is set correctly + config, err2 := chState.RMNRemote.GetVersionedConfig(&bind.CallOpts{Context: ctx}) + require.NoError(t, err2) + require.Equalf(t, + activeDigest, + config.Config.RmnHomeContractConfigDigest, + "RMNRemote config digest should be the same as the active digest of RMNHome after setting, RMNHome active: %x, RMNRemote config: %x", + activeDigest[:], config.Config.RmnHomeContractConfigDigest[:]) + + t.Logf("RMNRemote config digest after setting: %x", config.Config.RmnHomeContractConfigDigest[:]) + } +} + +func (tc rmnTestCase) killMarkedRmnNodes(t *testing.T, rmnCluster devenv.RMNCluster) { + for _, n := range tc.rmnNodes { + if n.forceExit { + t.Logf("Pausing RMN node %d", n.id) + rmnN := rmnCluster.Nodes["rmn_"+strconv.Itoa(n.id)] + require.NoError(t, osutil.ExecCmd(zerolog.Nop(), "docker kill "+rmnN.Proxy.ContainerName)) + t.Logf("Paused RMN node %d", n.id) + } + } +} + +func (tc rmnTestCase) disableOraclesIfThisIsACursingTestCase(t *testing.T, ctx context.Context, envWithRMN changeset.DeployedEnv) []string { + disabledNodes := make([]string, 0) + + if len(tc.cursedSourceChainIdxs) > 0 || tc.globalCurse { + listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) + require.NoError(t, err) + + for _, n := range listNodesResp.Nodes { + if strings.HasPrefix(n.Name, "bootstrap") { + continue + } + _, err := envWithRMN.Env.Offchain.DisableNode(ctx, &node.DisableNodeRequest{Id: n.Id}) + require.NoError(t, err) + disabledNodes = append(disabledNodes, n.Id) + t.Logf("node %s disabled", n.Id) + } + } + + return disabledNodes +} + +func (tc rmnTestCase) sendMessages(t *testing.T, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) (map[uint64]*uint64, map[changeset.SourceDestPair]uint64, map[changeset.SourceDestPair][]uint64) { + startBlocks := make(map[uint64]*uint64) + seqNumCommit := make(map[changeset.SourceDestPair]uint64) + seqNumExec := make(map[changeset.SourceDestPair][]uint64) + + for _, msg := range tc.messagesToSend { + fromChain := tc.pf.chainSelectors[msg.fromChainIdx] + toChain := tc.pf.chainSelectors[msg.toChainIdx] + + for i := 0; i < msg.count; i++ { + msgSentEvent := changeset.TestSendRequest(t, envWithRMN.Env, onChainState, fromChain, toChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(onChainState.Chains[toChain].Receiver.Address().Bytes(), 32), + Data: []byte("hello world"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + seqNumCommit[changeset.SourceDestPair{ + SourceChainSelector: fromChain, + DestChainSelector: toChain, + }] = msgSentEvent.SequenceNumber + seqNumExec[changeset.SourceDestPair{ + SourceChainSelector: fromChain, + DestChainSelector: toChain, + }] = []uint64{msgSentEvent.SequenceNumber} + t.Logf("Sent message from chain %d to chain %d with seqNum %d", fromChain, toChain, msgSentEvent.SequenceNumber) + } + + zero := uint64(0) + startBlocks[toChain] = &zero + } + + return startBlocks, seqNumCommit, seqNumExec +} + +func (tc rmnTestCase) callContractsToCurseChains(t *testing.T, ctx context.Context, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) { + for _, remoteCfg := range tc.remoteChainsConfig { + remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] + chState, ok := onChainState.Chains[remoteSel] + require.True(t, ok) + chain, ok := envWithRMN.Env.Chains[remoteSel] + require.True(t, ok) + for _, chainSel := range tc.pf.cursedSourceChains.ToSlice() { + txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, chainSelectorToBytes16(chainSel)) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) + require.NoError(t, errConfirm) + } + + if tc.globalCurse { + txCurseGlobal, errCurseGlobal := chState.RMNRemote.Curse(chain.DeployerKey, types.GlobalCurseSubject) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurseGlobal, errCurseGlobal) + require.NoError(t, errConfirm) + } + + cs, err := chState.RMNRemote.GetCursedSubjects(&bind.CallOpts{Context: ctx}) + require.NoError(t, err) + t.Logf("Cursed subjects: %v", cs) + } +} + +func (tc rmnTestCase) enableOracles(t *testing.T, ctx context.Context, envWithRMN changeset.DeployedEnv, nodeIDs []string) { + for _, n := range nodeIDs { + _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) + require.NoError(t, err) + t.Logf("node %s enabled", n) + } +} + func chainSelectorToBytes16(chainSel uint64) [16]byte { var result [16]byte // Convert the uint64 to bytes and place it in the last 8 bytes of the array From add126e68d8af51579711a942f80775704d421a5 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 13:12:38 +0200 Subject: [PATCH 10/14] gomodtidy --- integration-tests/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index a0d585a0a14..ae6dbe919f3 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -39,6 +39,7 @@ require ( github.com/smartcontractkit/chainlink-automation v0.8.1 github.com/smartcontractkit/chainlink-ccip v0.0.0-20241125151847-c63f5f567fcd github.com/smartcontractkit/chainlink-common v0.3.1-0.20241125150608-97ceadb2072d + github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.17 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 @@ -416,7 +417,6 @@ require ( github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect - github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241118190857-e2db20a6a969 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect From 1554c41ff69184e7803894c822e95f6e3ad06e5b Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 13:26:07 +0200 Subject: [PATCH 11/14] lint fix --- integration-tests/smoke/ccip/ccip_rmn_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 7fa412dabe3..c29f25ea2b5 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -288,7 +288,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { "active digest should be the same as the previously candidate digest after promotion, previous candidate: %x, active: %x", candidateDigest[:], activeDigest[:]) - tc.setRmnRemoteConfig(t, ctx, onChainState, activeDigest, envWithRMN) + tc.setRmnRemoteConfig(ctx, t, onChainState, activeDigest, envWithRMN) tc.killMarkedRmnNodes(t, rmnCluster) @@ -299,9 +299,9 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { startBlocks, seqNumCommit, seqNumExec := tc.sendMessages(t, onChainState, envWithRMN) t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) - tc.callContractsToCurseChains(t, ctx, onChainState, envWithRMN) + tc.callContractsToCurseChains(ctx, t, onChainState, envWithRMN) - tc.enableOracles(t, ctx, envWithRMN, disabledNodes) + tc.enableOracles(ctx, t, envWithRMN, disabledNodes) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) for k, v := range seqNumCommit { @@ -497,8 +497,8 @@ func (tc rmnTestCase) validate() error { } func (tc rmnTestCase) setRmnRemoteConfig( - t *testing.T, ctx context.Context, + t *testing.T, onChainState changeset.CCIPOnChainState, activeDigest [32]byte, envWithRMN changeset.DeployedEnv) { @@ -603,7 +603,7 @@ func (tc rmnTestCase) sendMessages(t *testing.T, onChainState changeset.CCIPOnCh return startBlocks, seqNumCommit, seqNumExec } -func (tc rmnTestCase) callContractsToCurseChains(t *testing.T, ctx context.Context, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) { +func (tc rmnTestCase) callContractsToCurseChains(ctx context.Context, t *testing.T, onChainState changeset.CCIPOnChainState, envWithRMN changeset.DeployedEnv) { for _, remoteCfg := range tc.remoteChainsConfig { remoteSel := tc.pf.chainSelectors[remoteCfg.chainIdx] chState, ok := onChainState.Chains[remoteSel] @@ -628,7 +628,7 @@ func (tc rmnTestCase) callContractsToCurseChains(t *testing.T, ctx context.Conte } } -func (tc rmnTestCase) enableOracles(t *testing.T, ctx context.Context, envWithRMN changeset.DeployedEnv, nodeIDs []string) { +func (tc rmnTestCase) enableOracles(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv, nodeIDs []string) { for _, n := range nodeIDs { _, err := envWithRMN.Env.Offchain.EnableNode(ctx, &node.EnableNodeRequest{Id: n}) require.NoError(t, err) From abc59505e1276c4128ffd44396cf227992779c20 Mon Sep 17 00:00:00 2001 From: dimkouv Date: Thu, 28 Nov 2024 13:39:55 +0200 Subject: [PATCH 12/14] lint fix --- integration-tests/smoke/ccip/ccip_rmn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index c29f25ea2b5..16213cd81cd 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -294,7 +294,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { changeset.ReplayLogs(t, envWithRMN.Env.Offchain, envWithRMN.ReplayBlocks) require.NoError(t, changeset.AddLanesForAll(envWithRMN.Env, onChainState)) - disabledNodes := tc.disableOraclesIfThisIsACursingTestCase(t, ctx, envWithRMN) + disabledNodes := tc.disableOraclesIfThisIsACursingTestCase(ctx, t, envWithRMN) startBlocks, seqNumCommit, seqNumExec := tc.sendMessages(t, onChainState, envWithRMN) t.Logf("Sent all messages, seqNumCommit: %v seqNumExec: %v", seqNumCommit, seqNumExec) @@ -547,7 +547,7 @@ func (tc rmnTestCase) killMarkedRmnNodes(t *testing.T, rmnCluster devenv.RMNClus } } -func (tc rmnTestCase) disableOraclesIfThisIsACursingTestCase(t *testing.T, ctx context.Context, envWithRMN changeset.DeployedEnv) []string { +func (tc rmnTestCase) disableOraclesIfThisIsACursingTestCase(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv) []string { disabledNodes := make([]string, 0) if len(tc.cursedSourceChainIdxs) > 0 || tc.globalCurse { From 9b12955cf6a88ef9f05b420fa12e099fd7f9469b Mon Sep 17 00:00:00 2001 From: dimkouv Date: Fri, 29 Nov 2024 16:05:27 +0200 Subject: [PATCH 13/14] explicitly defined subjects --- integration-tests/smoke/ccip/ccip_rmn_test.go | 126 ++++++++++-------- 1 file changed, 68 insertions(+), 58 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index 16213cd81cd..ea6c1b63c30 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -6,6 +6,7 @@ import ( "errors" "math/big" "os" + "slices" "strconv" "strings" "testing" @@ -178,9 +179,11 @@ func TestRMN_DifferentRmnNodesForDifferentChains(t *testing.T) { func TestRMN_TwoMessagesOneSourceChainCursed(t *testing.T) { runRmnTestCase(t, rmnTestCase{ - name: "two messages, one source chain is cursed", - passIfNoCommitAfter: 15 * time.Second, - cursedSourceChainIdxs: []int{chain0}, // <---- chain0 is cursed (only as source) + name: "two messages, one source chain is cursed", + passIfNoCommitAfter: 15 * time.Second, + cursedSubjectsPerChain: map[int][]int{ + chain1: {chain0}, + }, homeChainConfig: homeChainConfig{ f: map[int]int{chain0: 1, chain1: 1}, }, @@ -220,14 +223,18 @@ func TestRMN_GlobalCurseTwoMessagesOnTwoLanes(t *testing.T) { {fromChainIdx: chain0, toChainIdx: chain1, count: 1}, {fromChainIdx: chain1, toChainIdx: chain0, count: 5}, }, - globalCurse: true, + cursedSubjectsPerChain: map[int][]int{ + chain1: {globalCurse}, + chain0: {globalCurse}, + }, passIfNoCommitAfter: 15 * time.Second, }) } const ( - chain0 = 0 - chain1 = 1 + chain0 = 0 + chain1 = 1 + globalCurse = 1000 ) func runRmnTestCase(t *testing.T, tc rmnTestCase) { @@ -305,7 +312,11 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { expectedSeqNum := make(map[changeset.SourceDestPair]uint64) for k, v := range seqNumCommit { - if !tc.pf.cursedSourceChains.Contains(k.SourceChainSelector) { + cursedSubjectsOfDest, exists := tc.pf.cursedSubjectsPerChainSel[k.DestChainSelector] + shouldSkip := exists && (slices.Contains(cursedSubjectsOfDest, globalCurse) || + slices.Contains(cursedSubjectsOfDest, k.SourceChainSelector)) + + if !shouldSkip { expectedSeqNum[k] = v } } @@ -313,16 +324,19 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { t.Logf("expectedSeqNums: %v", expectedSeqNum) t.Logf("expectedSeqNums including cursed chains: %v", seqNumCommit) - if len(tc.cursedSourceChainIdxs) > 0 && len(seqNumCommit) == len(expectedSeqNum) { - t.Fatalf("test case is wrong: no message was sent to non-cursed chains") + if len(tc.cursedSubjectsPerChain) > 0 && len(seqNumCommit) == len(expectedSeqNum) { + t.Fatalf("test case is wrong: no message was sent to non-cursed chains when you " + + "define curse subjects, your test case should have at least one message not expected to be delivered") } commitReportReceived := make(chan struct{}) go func() { - changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) - commitReportReceived <- struct{}{} + if len(expectedSeqNum) > 0 { + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, expectedSeqNum, startBlocks) + commitReportReceived <- struct{}{} + } - if tc.pf.cursedSourceChains.Cardinality() > 0 { + if len(seqNumCommit) > 0 && len(seqNumCommit) > len(expectedSeqNum) { // wait for a duration and assert that commit reports were not delivered for cursed source chains changeset.ConfirmCommitForAllWithExpectedSeqNums(t, envWithRMN.Env, onChainState, seqNumCommit, startBlocks) commitReportReceived <- struct{}{} @@ -330,7 +344,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { }() if tc.passIfNoCommitAfter > 0 { // wait for a duration and assert that commit reports were not delivered - if len(tc.cursedSourceChainIdxs) > 0 && len(expectedSeqNum) > 0 { + if len(expectedSeqNum) > 0 && len(seqNumCommit) > len(expectedSeqNum) { t.Logf("⌛ Waiting for commit reports of non-cursed chains...") <-commitReportReceived t.Logf("✅ Commit reports of non-cursed chains received") @@ -403,29 +417,24 @@ type rmnTestCase struct { name string // If set to 0, the test will wait for commit reports. // If set to a positive value, the test will wait for that duration and will assert that commit report was not delivered. - passIfNoCommitAfter time.Duration - // If set to true, the test will only wait for non-cursed chain msgs. - // And then wait for passIfNoCommitAfter (must be set) to assert that msgs from cursed sources are not transmitted. - // At the moment, it does not support waitForExec=true since only commit plugin has cursing checks. - cursedSourceChainIdxs []int - // globalCurse marks every chain as cursed by setting the global curse subject on each rmnRemote - globalCurse bool - waitForExec bool - homeChainConfig homeChainConfig - remoteChainsConfig []remoteChainConfig - rmnNodes []rmnNode - messagesToSend []messageToSend + passIfNoCommitAfter time.Duration + cursedSubjectsPerChain map[int][]int + waitForExec bool + homeChainConfig homeChainConfig + remoteChainsConfig []remoteChainConfig + rmnNodes []rmnNode + messagesToSend []messageToSend // populated fields after environment setup pf testCasePopulatedFields } type testCasePopulatedFields struct { - chainSelectors []uint64 - rmnHomeNodes []rmn_home.RMNHomeNode - rmnRemoteSigners []rmn_remote.RMNRemoteSigner - rmnHomeSourceChains []rmn_home.RMNHomeSourceChain - cursedSourceChains mapset.Set[uint64] + chainSelectors []uint64 + rmnHomeNodes []rmn_home.RMNHomeNode + rmnRemoteSigners []rmn_remote.RMNRemoteSigner + rmnHomeSourceChains []rmn_home.RMNHomeSourceChain + cursedSubjectsPerChainSel map[uint64][]uint64 } func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.DeployedEnv, rmnCluster devenv.RMNCluster) { @@ -468,31 +477,25 @@ func (tc *rmnTestCase) populateFields(t *testing.T, envWithRMN changeset.Deploye }) } - tc.pf.cursedSourceChains = mapset.NewSet[uint64]() - for _, chainIdx := range tc.cursedSourceChainIdxs { - tc.pf.cursedSourceChains.Add(tc.pf.chainSelectors[chainIdx]) + // populate cursed subjects with actual chain selectors + tc.pf.cursedSubjectsPerChainSel = make(map[uint64][]uint64) + for chainIdx, subjects := range tc.cursedSubjectsPerChain { + chainSel := tc.pf.chainSelectors[chainIdx] + for _, subject := range subjects { + subjSel := uint64(globalCurse) + if subject != globalCurse { + subjSel = tc.pf.chainSelectors[subject] + } + tc.pf.cursedSubjectsPerChainSel[chainSel] = append(tc.pf.cursedSubjectsPerChainSel[chainSel], subjSel) + } } } func (tc rmnTestCase) validate() error { - if len(tc.cursedSourceChainIdxs) > 0 { - if tc.waitForExec { - return errors.New("cursedSourceChainIdxs is set but waitForExec is true which is not supported") - } - if tc.passIfNoCommitAfter == 0 { - return errors.New("cursedSourceChainIdxs is set but passIfNoCommitAfter is not set") - } + if len(tc.cursedSubjectsPerChain) > 0 && tc.passIfNoCommitAfter == 0 { + return errors.New("when you define cursed subjects you also need to define the duration that the " + + "test will wait for non-transmitted roots") } - - if tc.globalCurse { - if tc.passIfNoCommitAfter == 0 { - return errors.New("globalCurse is set but passIfNoCommitAfter is not set") - } - if len(tc.cursedSourceChainIdxs) > 0 { - return errors.New("globalCurse is set but cursedSourceChainIdxs is not empty, this is not supported") - } - } - return nil } @@ -550,7 +553,7 @@ func (tc rmnTestCase) killMarkedRmnNodes(t *testing.T, rmnCluster devenv.RMNClus func (tc rmnTestCase) disableOraclesIfThisIsACursingTestCase(ctx context.Context, t *testing.T, envWithRMN changeset.DeployedEnv) []string { disabledNodes := make([]string, 0) - if len(tc.cursedSourceChainIdxs) > 0 || tc.globalCurse { + if len(tc.cursedSubjectsPerChain) > 0 { listNodesResp, err := envWithRMN.Env.Offchain.ListNodes(ctx, &node.ListNodesRequest{}) require.NoError(t, err) @@ -610,15 +613,22 @@ func (tc rmnTestCase) callContractsToCurseChains(ctx context.Context, t *testing require.True(t, ok) chain, ok := envWithRMN.Env.Chains[remoteSel] require.True(t, ok) - for _, chainSel := range tc.pf.cursedSourceChains.ToSlice() { - txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, chainSelectorToBytes16(chainSel)) - _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) - require.NoError(t, errConfirm) + + cursedSubjects, ok := tc.cursedSubjectsPerChain[remoteCfg.chainIdx] + if !ok { + continue // nothing to curse on this chain } - if tc.globalCurse { - txCurseGlobal, errCurseGlobal := chState.RMNRemote.Curse(chain.DeployerKey, types.GlobalCurseSubject) - _, errConfirm := deployment.ConfirmIfNoError(chain, txCurseGlobal, errCurseGlobal) + for _, subjectDescription := range cursedSubjects { + subj := [16]byte{} + if subjectDescription == globalCurse { + subj = types.GlobalCurseSubject + } else { + subj = chainSelectorToBytes16(tc.pf.chainSelectors[subjectDescription]) + } + t.Logf("cursing subject %d (%d)", subj, subjectDescription) + txCurse, errCurse := chState.RMNRemote.Curse(chain.DeployerKey, subj) + _, errConfirm := deployment.ConfirmIfNoError(chain, txCurse, errCurse) require.NoError(t, errConfirm) } From aac809e182b6a2d491b7c19242710e11c80e778f Mon Sep 17 00:00:00 2001 From: dimkouv Date: Fri, 29 Nov 2024 16:15:56 +0200 Subject: [PATCH 14/14] fix lint --- integration-tests/smoke/ccip/ccip_rmn_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/integration-tests/smoke/ccip/ccip_rmn_test.go b/integration-tests/smoke/ccip/ccip_rmn_test.go index ea6c1b63c30..201982e19d1 100644 --- a/integration-tests/smoke/ccip/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip/ccip_rmn_test.go @@ -620,10 +620,8 @@ func (tc rmnTestCase) callContractsToCurseChains(ctx context.Context, t *testing } for _, subjectDescription := range cursedSubjects { - subj := [16]byte{} - if subjectDescription == globalCurse { - subj = types.GlobalCurseSubject - } else { + subj := types.GlobalCurseSubject + if subjectDescription != globalCurse { subj = chainSelectorToBytes16(tc.pf.chainSelectors[subjectDescription]) } t.Logf("cursing subject %d (%d)", subj, subjectDescription)