diff --git a/integration-tests/deployment/ccip/add_chain.go b/integration-tests/deployment/ccip/add_chain.go index 72455dfdd19..cd4296ad004 100644 --- a/integration-tests/deployment/ccip/add_chain.go +++ b/integration-tests/deployment/ccip/add_chain.go @@ -1,6 +1,7 @@ package ccipdeployment import ( + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -15,7 +16,7 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/integration-tests/deployment" - + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" @@ -25,19 +26,14 @@ import ( // to connect the new chain to the existing chains. func NewChainInboundProposal( e deployment.Environment, - ocrSecrets deployment.OCRSecrets, state CCIPOnChainState, homeChainSel uint64, - feedChainSel uint64, newChainSel uint64, sources []uint64, - tokenConfig TokenConfig, - rmnHomeAddress []byte, ) (*timelock.MCMSWithTimelockProposal, error) { // Generate proposal which enables new destination (from test router) on all source chains. var batches []timelock.BatchChainOperation - metaDataPerChain := make(map[mcms.ChainIdentifier]mcms.ChainMetadata) - timelockAddresses := make(map[mcms.ChainIdentifier]common.Address) + var chains []uint64 for _, source := range sources { chain, _ := chainsel.ChainBySelector(source) enableOnRampDest, err := state.Chains[source].OnRamp.ApplyDestChainConfigUpdates(deployment.SimTransactOpts(), []onramp.OnRampDestChainConfigArgs{ @@ -96,19 +92,10 @@ func NewChainInboundProposal( }, }, }) - opCount, err := state.Chains[source].ProposerMcm.GetOpCount(nil) - if err != nil { - return nil, err - } - metaDataPerChain[mcms.ChainIdentifier(chain.Selector)] = mcms.ChainMetadata{ - StartingOpCount: opCount.Uint64(), - MCMAddress: state.Chains[source].ProposerMcm.Address(), - } - timelockAddresses[mcms.ChainIdentifier(chain.Selector)] = state.Chains[source].Timelock.Address() + chains = append(chains, source) } - // Home chain new don. - // - Add new DONs for destination to home chain + homeChain, _ := chainsel.ChainBySelector(homeChainSel) nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) if err != nil { return nil, err @@ -131,7 +118,47 @@ func NewChainInboundProposal( return nil, err } - newDONArgs, err := BuildAddDONArgs( + timelockAddresses, metaDataPerChain, err := BuildProposalMetadata(state, append(chains, homeChainSel)) + if err != nil { + return nil, err + } + batches = append(batches, timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(homeChain.Selector), + Batch: []mcms.Operation{ + { + // Add the chain first, don needs it to be there. + To: state.Chains[homeChainSel].CCIPHome.Address(), + Data: addChain.Data(), + Value: big.NewInt(0), + }, + }, + }) + return timelock.NewMCMSWithTimelockProposal( + "1", + 2004259681, // TODO: should be parameterized and based on current block timestamp. + []mcms.Signature{}, + false, + metaDataPerChain, + timelockAddresses, + "blah", // TODO + batches, + timelock.Schedule, + "0s", // TODO: Should be parameterized. + ) +} + +// AddDonAndSetCandidateForCommitProposal adds new DON for destination to home chain +// and sets the commit plugin config as candidateConfig for the don. +func AddDonAndSetCandidateForCommitProposal( + state CCIPOnChainState, + e deployment.Environment, + nodes deployment.Nodes, + ocrSecrets deployment.OCRSecrets, + homeChainSel, feedChainSel, newChainSel uint64, + tokenConfig TokenConfig, + rmnHomeAddress common.Address, +) (*timelock.MCMSWithTimelockProposal, error) { + newDONArgs, err := BuildOCR3ConfigForCCIPHome( e.Logger, ocrSecrets, state.Chains[newChainSel].OffRamp, @@ -144,45 +171,27 @@ func NewChainInboundProposal( if err != nil { return nil, err } - mcmsOps, err := CreateDON( - e.Logger, + latestDon, err := LatestCCIPDON(state.Chains[homeChainSel].CapabilityRegistry) + if err != nil { + return nil, err + } + commitConfig, ok := newDONArgs[types.PluginTypeCCIPCommit] + if !ok { + return nil, fmt.Errorf("missing commit plugin in ocr3Configs") + } + donID := latestDon.Id + 1 + addDonOp, err := SetCandidateCommitPluginWithAddDonOps( + donID, commitConfig, state.Chains[homeChainSel].CapabilityRegistry, - state.Chains[homeChainSel].CCIPHome, - newDONArgs, - e.Chains[homeChainSel], - nodes, + nodes.NonBootstraps(), ) - //addDON, err := state.Chains[homeChainSel].CapabilityRegistry.AddDON(SimTransactOpts(), - // nodes.NonBootstraps().PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ - // { - // CapabilityId: CCIPCapabilityID, - // Config: newDONArgs, - // }, - // }, false, false, nodes.NonBootstraps().DefaultF()) if err != nil { return nil, err } - homeChain, _ := chainsel.ChainBySelector(homeChainSel) - opCount, err := state.Chains[homeChainSel].ProposerMcm.GetOpCount(nil) + timelockAddresses, metaDataPerChain, err := BuildProposalMetadata(state, []uint64{homeChainSel}) if err != nil { return nil, err } - metaDataPerChain[mcms.ChainIdentifier(homeChain.Selector)] = mcms.ChainMetadata{ - StartingOpCount: opCount.Uint64(), - MCMAddress: state.Chains[homeChainSel].ProposerMcm.Address(), - } - timelockAddresses[mcms.ChainIdentifier(homeChain.Selector)] = state.Chains[homeChainSel].Timelock.Address() - batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: mcms.ChainIdentifier(homeChain.Selector), - Batch: append([]mcms.Operation{ - { - // Add the chain first, don needs it to be there. - To: state.Chains[homeChainSel].CCIPHome.Address(), - Data: addChain.Data(), - Value: big.NewInt(0), - }, - }, mcmsOps...), - }) return timelock.NewMCMSWithTimelockProposal( "1", 2004259681, // TODO: should be parameterized and based on current block timestamp. @@ -190,8 +199,11 @@ func NewChainInboundProposal( false, metaDataPerChain, timelockAddresses, - "blah", // TODO - batches, + "SetCandidate for commit And AddDon for new chain", + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: []mcms.Operation{addDonOp}, + }}, timelock.Schedule, "0s", // TODO: Should be parameterized. ) diff --git a/integration-tests/deployment/ccip/add_chain_test.go b/integration-tests/deployment/ccip/add_chain_test.go index 5e05fff59ff..776bf6fd74d 100644 --- a/integration-tests/deployment/ccip/add_chain_test.go +++ b/integration-tests/deployment/ccip/add_chain_test.go @@ -24,9 +24,6 @@ import ( ) func TestAddChainInbound(t *testing.T) { - // TODO: fix - t.Skip("Not currently working, need to fix the addChain proposal") - // 4 chains where the 4th is added after initial deployment. e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 4) state, err := LoadOnchainState(e.Env, e.Ab) @@ -129,15 +126,45 @@ func TestAddChainInbound(t *testing.T) { require.Equal(t, state.Chains[e.HomeChainSel].Timelock.Address(), cfgOwner) require.Equal(t, state.Chains[e.HomeChainSel].Timelock.Address(), crOwner) + nodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) + require.NoError(t, err) + // Generate and sign inbound proposal to new 4th chain. - rmnHomeAddressBytes := common.HexToAddress(rmnHomeAddress).Bytes() - chainInboundProposal, err := NewChainInboundProposal(e.Env, deployment.XXXGenerateTestOCRSecrets(), state, e.HomeChainSel, e.FeedChainSel, newChain, initialDeploy, tokenConfig, rmnHomeAddressBytes) + chainInboundProposal, err := NewChainInboundProposal(e.Env, state, e.HomeChainSel, newChain, initialDeploy) require.NoError(t, err) chainInboundExec := SignProposal(t, e.Env, chainInboundProposal) for _, sel := range initialDeploy { ExecuteProposal(t, e.Env, chainInboundExec, state, sel) } + // TODO This currently is not working - Able to send the request here but request gets stuck in execution + // Send a new message and expect that this is delivered once the chain is completely set up as inbound + //SendRequest(t, e.Env, state, initialDeploy[0], newChain, true) + + t.Logf("Executing add don and set candidate proposal for commit plugin on chain %d", newChain) + addDonProp, err := AddDonAndSetCandidateForCommitProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, common.HexToAddress(rmnHomeAddress)) + require.NoError(t, err) + + addDonExec := SignProposal(t, e.Env, addDonProp) + ExecuteProposal(t, e.Env, addDonExec, state, e.HomeChainSel) + t.Logf("Executing promote candidate proposal for exec plugin on chain %d", newChain) + setCandidateForExecProposal, err := SetCandidateExecPluginProposal(state, e.Env, nodes, deployment.XXXGenerateTestOCRSecrets(), e.HomeChainSel, e.FeedChainSel, newChain, tokenConfig, common.HexToAddress(rmnHomeAddress)) + require.NoError(t, err) + setCandidateForExecExec := SignProposal(t, e.Env, setCandidateForExecProposal) + ExecuteProposal(t, e.Env, setCandidateForExecExec, state, e.HomeChainSel) + + t.Logf("Executing promote candidate proposal for both commit and exec plugins on chain %d", newChain) + donPromoteProposal, err := PromoteCandidateProposal(state, e.HomeChainSel, newChain, nodes) + require.NoError(t, err) + donPromoteExec := SignProposal(t, e.Env, donPromoteProposal) + ExecuteProposal(t, e.Env, donPromoteExec, state, e.HomeChainSel) + + // verify if the configs are updated + require.NoError(t, ValidateCCIPHomeConfigSetUp( + state.Chains[e.HomeChainSel].CapabilityRegistry, + state.Chains[e.HomeChainSel].CCIPHome, + newChain, + )) replayBlocks, err := LatestBlocksByChain(testcontext.Get(t), e.Env.Chains) require.NoError(t, err) @@ -189,6 +216,11 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) startBlock := latesthdr.Number.Uint64() seqNr := SendRequest(t, e.Env, state, initialDeploy[0], newChain, true) + require.NoError(t, + ConfirmCommitWithExpectedSeqNumRange(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, cciptypes.SeqNumRange{ + cciptypes.SeqNum(1), + cciptypes.SeqNum(seqNr), + })) 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/deploy.go b/integration-tests/deployment/ccip/deploy.go index cd7464cbe5f..fdc443006ce 100644 --- a/integration-tests/deployment/ccip/deploy.go +++ b/integration-tests/deployment/ccip/deploy.go @@ -235,7 +235,7 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c c.OCRSecrets, capReg, ccipHome, - common.HexToAddress(rmnHomeAddress).Bytes(), + common.HexToAddress(rmnHomeAddress), chainState.OffRamp, c.FeedChainSel, tokenInfo, diff --git a/integration-tests/deployment/ccip/deploy_home_chain.go b/integration-tests/deployment/ccip/deploy_home_chain.go index fa50a54878c..2aa893458f5 100644 --- a/integration-tests/deployment/ccip/deploy_home_chain.go +++ b/integration-tests/deployment/ccip/deploy_home_chain.go @@ -5,6 +5,7 @@ import ( "context" "encoding/hex" "fmt" + "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi" @@ -65,8 +66,17 @@ const ( var ( CCIPCapabilityID = utils.Keccak256Fixed(MustABIEncode(`[{"type": "string"}, {"type": "string"}]`, CapabilityLabelledName, CapabilityVersion)) + CCIPHomeABI *abi.ABI ) +func init() { + var err error + CCIPHomeABI, err = ccip_home.CCIPHomeMetaData.GetAbi() + if err != nil { + panic(err) + } +} + func MustABIEncode(abiString string, args ...interface{}) []byte { encoded, err := utils.ABIEncode(abiString, args...) if err != nil { @@ -261,7 +271,7 @@ func AddChainConfig( return chainConfig, nil } -func BuildAddDONArgs( +func BuildOCR3ConfigForCCIPHome( lggr logger.Logger, ocrSecrets deployment.OCRSecrets, offRamp *offramp.OffRamp, @@ -270,7 +280,7 @@ func BuildAddDONArgs( // Token address on Dest chain to aggregate address on feed chain tokenInfo map[ocrtypes.Account]pluginconfig.TokenInfo, nodes deployment.Nodes, - rmnHomeAddress []byte, + rmnHomeAddress common.Address, ) (map[cctypes.PluginType]ccip_home.CCIPHomeOCR3Config, error) { p2pIDs := nodes.PeerIDs() // Get OCR3 Config from helper @@ -380,7 +390,7 @@ func BuildAddDONArgs( OfframpAddress: offRamp.Address().Bytes(), Nodes: ocrNodes, OffchainConfig: offchainConfig, - RmnHomeAddress: rmnHomeAddress, + RmnHomeAddress: rmnHomeAddress.Bytes(), } } @@ -403,6 +413,28 @@ func LatestCCIPDON(registry *capabilities_registry.CapabilitiesRegistry) (*capab return &ccipDON, nil } +// DonIDForChain returns the DON ID for the chain with the given selector +// It looks up with the CCIPHome contract to find the OCR3 configs for the DONs, and returns the DON ID for the chain matching with the given selector from the OCR3 configs +func DonIDForChain(registry *capabilities_registry.CapabilitiesRegistry, ccipHome *ccip_home.CCIPHome, chainSelector uint64) (uint32, error) { + dons, err := registry.GetDONs(nil) + if err != nil { + return 0, err + } + for _, don := range dons { + if len(don.CapabilityConfigurations) == 1 && + don.CapabilityConfigurations[0].CapabilityId == CCIPCapabilityID { + configs, err := ccipHome.GetAllConfigs(nil, don.Id, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return 0, err + } + if configs.ActiveConfig.Config.ChainSelector == chainSelector || configs.CandidateConfig.Config.ChainSelector == chainSelector { + return don.Id, nil + } + } + } + return 0, fmt.Errorf("no DON found for chain %d", chainSelector) +} + func BuildSetOCR3ConfigArgs( donID uint32, ccipHome *ccip_home.CCIPHome, @@ -455,83 +487,40 @@ func CreateDON( ccipHome *ccip_home.CCIPHome, ocr3Configs map[cctypes.PluginType]ccip_home.CCIPHomeOCR3Config, home deployment.Chain, + newChainSel uint64, nodes deployment.Nodes, -) ([]mcms.Operation, error) { +) error { commitConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPCommit] if !ok { - return nil, fmt.Errorf("missing commit plugin in ocr3Configs") + return fmt.Errorf("missing commit plugin in ocr3Configs") } execConfig, ok := ocr3Configs[cctypes.PluginTypeCCIPExec] if !ok { - return nil, fmt.Errorf("missing exec plugin in ocr3Configs") + return fmt.Errorf("missing exec plugin in ocr3Configs") } - tabi, err := ccip_home.CCIPHomeMetaData.GetAbi() - if err != nil { - return nil, err - } latestDon, err := LatestCCIPDON(capReg) if err != nil { - return nil, err + return err } donID := latestDon.Id + 1 - mcmsOps := []mcms.Operation{} - err = setupCommitDON(tabi, donID, commitConfig, capReg, home, nodes, ccipHome) + err = setupCommitDON(donID, commitConfig, capReg, home, nodes, ccipHome) if err != nil { - return nil, fmt.Errorf("setup commit don: %w", err) + return fmt.Errorf("setup commit don: %w", err) } // TODO: bug in contract causing this to not work as expected. - err = setupExecDON(tabi, donID, execConfig, capReg, home, nodes, ccipHome) + err = setupExecDON(donID, execConfig, capReg, home, nodes, ccipHome) if err != nil { - return nil, fmt.Errorf("setup exec don: %w", err) + return fmt.Errorf("setup exec don: %w", err) } - - // final sanity checks on configs. - commitConfigs, err := ccipHome.GetAllConfigs(&bind.CallOpts{ - Pending: true, - }, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return nil, fmt.Errorf("get all commit configs: %w", err) - } - commitActiveDigest, err := ccipHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return nil, fmt.Errorf("get active commit digest: %w", err) - } - commitCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) - if err != nil { - return nil, fmt.Errorf("get commit candidate digest: %w", err) - } - if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} { - return nil, fmt.Errorf( - "active config digest is empty for commit, expected nonempty, donID: %d, cfg: %+v, config digest from GetActiveDigest call: %x, config digest from GetCandidateDigest call: %x", - donID, commitConfigs.ActiveConfig, commitActiveDigest, commitCandidateDigest) - } - if commitConfigs.CandidateConfig.ConfigDigest != [32]byte{} { - return nil, fmt.Errorf( - "candidate config digest is nonempty for commit, expected empty, donID: %d, cfg: %+v, config digest from GetCandidateDigest call: %x, config digest from GetActiveDigest call: %x", - donID, commitConfigs.CandidateConfig, commitCandidateDigest, commitActiveDigest) - } - - execConfigs, err := ccipHome.GetAllConfigs(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) - if err != nil { - return nil, fmt.Errorf("get all exec configs: %w", err) - } - if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} { - return nil, fmt.Errorf("active config digest is empty for exec, expected nonempty, cfg: %v", execConfigs.ActiveConfig) - } - if execConfigs.CandidateConfig.ConfigDigest != [32]byte{} { - return nil, fmt.Errorf("candidate config digest is nonempty for exec, expected empty, cfg: %v", execConfigs.CandidateConfig) - } - - return mcmsOps, nil + return ValidateCCIPHomeConfigSetUp(capReg, ccipHome, newChainSel) } func setupExecDON( - tabi *abi.ABI, donID uint32, execConfig ccip_home.CCIPHomeOCR3Config, capReg *capabilities_registry.CapabilitiesRegistry, @@ -539,7 +528,7 @@ func setupExecDON( nodes deployment.Nodes, ccipHome *ccip_home.CCIPHome, ) error { - encodedSetCandidateCall, err := tabi.Pack( + encodedSetCandidateCall, err := CCIPHomeABI.Pack( "setCandidate", donID, execConfig.PluginType, @@ -582,7 +571,7 @@ func setupExecDON( } // promote candidate call - encodedPromotionCall, err := tabi.Pack( + encodedPromotionCall, err := CCIPHomeABI.Pack( "promoteCandidateAndRevokeActive", donID, execConfig.PluginType, @@ -659,8 +648,246 @@ func setupExecDON( return nil } +// SetCandidateExecPluginOps calls setCandidate on CCIPHome contract through the UpdateDON call on CapReg contract +// This proposes to set up OCR3 config for the exec plugin for the DON +func SetCandidateExecPluginOps( + execConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + encodedSetCandidateCall, err := CCIPHomeABI.Pack( + "setCandidate", + donID, + execConfig.PluginType, + execConfig, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack set candidate call: %w", err) + } + + // set candidate call + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ exec config: %w", err) + } + + return []mcms.Operation{{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }}, nil +} + +// SetCandidateCommitPluginWithAddDonOps sets the candidate commit config by calling setCandidate on CCIPHome contract through the AddDON call on CapReg contract +// This should be done first before calling any other UpdateDON calls +// This proposes to set up OCR3 config for the commit plugin for the DON +func SetCandidateCommitPluginWithAddDonOps( + donID uint32, + commitConfig ccip_home.CCIPHomeOCR3Config, + capReg *capabilities_registry.CapabilitiesRegistry, + nodes deployment.Nodes, +) (mcms.Operation, error) { + encodedSetCandidateCall, err := CCIPHomeABI.Pack( + "setCandidate", + donID, + commitConfig.PluginType, + commitConfig, + [32]byte{}, + ) + if err != nil { + return mcms.Operation{}, fmt.Errorf("pack set candidate call: %w", err) + } + addDonTx, err := capReg.AddDON(deployment.SimTransactOpts(), nodes.PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedSetCandidateCall, + }, + }, false, false, nodes.DefaultF()) + if err != nil { + return mcms.Operation{}, fmt.Errorf("could not generate add don tx w/ commit config: %w", err) + } + return mcms.Operation{ + To: capReg.Address(), + Data: addDonTx.Data(), + Value: big.NewInt(0), + }, nil +} + +// PromoteCandidateOps promotes the candidate commit and exec configs to active by calling promoteCandidateAndRevokeActive on CCIPHome through the UpdateDON call on CapReg contract +func PromoteCandidateOps( + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSelector uint64, + nodes deployment.Nodes, +) ([]mcms.Operation, error) { + // fetch DON ID for the chain + donID, err := DonIDForChain(capReg, ccipHome, chainSelector) + if err != nil { + return nil, fmt.Errorf("fetch don id for chain: %w", err) + } + + mcmsOps := []mcms.Operation{} + + commitCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return nil, fmt.Errorf("get commit candidate digest: %w", err) + } + + if commitCandidateDigest == [32]byte{} { + return nil, fmt.Errorf("candidate digest is empty, expected nonempty") + } + fmt.Printf("commit candidate digest after setCandidate: %x\n", commitCandidateDigest) + + encodedPromotionCall, err := CCIPHomeABI.Pack( + "promoteCandidateAndRevokeActive", + donID, + uint8(cctypes.PluginTypeCCIPCommit), + commitCandidateDigest, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack promotion call: %w", err) + } + + updateDonTx, err := capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedPromotionCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ commit config: %w", err) + } + mcmsOps = append(mcmsOps, mcms.Operation{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }) + + // check that candidate digest is empty. + execCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) + if err != nil { + return nil, fmt.Errorf("get exec candidate digest 1st time: %w", err) + } + + if execCandidateDigest == [32]byte{} { + return nil, fmt.Errorf("candidate digest is empty, expected nonempty") + } + + // promote candidate call + encodedPromotionCall, err = CCIPHomeABI.Pack( + "promoteCandidateAndRevokeActive", + donID, + uint8(cctypes.PluginTypeCCIPExec), + execCandidateDigest, + [32]byte{}, + ) + if err != nil { + return nil, fmt.Errorf("pack promotion call: %w", err) + } + + updateDonTx, err = capReg.UpdateDON( + deployment.SimTransactOpts(), + donID, + nodes.PeerIDs(), + []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ + { + CapabilityId: CCIPCapabilityID, + Config: encodedPromotionCall, + }, + }, + false, + nodes.DefaultF(), + ) + if err != nil { + return nil, fmt.Errorf("update don w/ exec config: %w", err) + } + mcmsOps = append(mcmsOps, mcms.Operation{ + To: capReg.Address(), + Data: updateDonTx.Data(), + Value: big.NewInt(0), + }) + return mcmsOps, nil +} + +// ValidateCCIPHomeConfigSetUp checks that the commit and exec active and candidate configs are set up correctly +func ValidateCCIPHomeConfigSetUp( + capReg *capabilities_registry.CapabilitiesRegistry, + ccipHome *ccip_home.CCIPHome, + chainSel uint64, +) error { + // fetch DONID + donID, err := DonIDForChain(capReg, ccipHome, chainSel) + if err != nil { + return fmt.Errorf("fetch don id for chain: %w", err) + } + // final sanity checks on configs. + commitConfigs, err := ccipHome.GetAllConfigs(&bind.CallOpts{ + Pending: true, + }, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get all commit configs: %w", err) + } + commitActiveDigest, err := ccipHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get active commit digest: %w", err) + } + commitCandidateDigest, err := ccipHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + if err != nil { + return fmt.Errorf("get commit candidate digest: %w", err) + } + if commitConfigs.ActiveConfig.ConfigDigest == [32]byte{} { + return fmt.Errorf( + "active config digest is empty for commit, expected nonempty, donID: %d, cfg: %+v, config digest from GetActiveDigest call: %x, config digest from GetCandidateDigest call: %x", + donID, commitConfigs.ActiveConfig, commitActiveDigest, commitCandidateDigest) + } + if commitConfigs.CandidateConfig.ConfigDigest != [32]byte{} { + return fmt.Errorf( + "candidate config digest is nonempty for commit, expected empty, donID: %d, cfg: %+v, config digest from GetCandidateDigest call: %x, config digest from GetActiveDigest call: %x", + donID, commitConfigs.CandidateConfig, commitCandidateDigest, commitActiveDigest) + } + + execConfigs, err := ccipHome.GetAllConfigs(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) + if err != nil { + return fmt.Errorf("get all exec configs: %w", err) + } + if execConfigs.ActiveConfig.ConfigDigest == [32]byte{} { + return fmt.Errorf("active config digest is empty for exec, expected nonempty, cfg: %v", execConfigs.ActiveConfig) + } + if execConfigs.CandidateConfig.ConfigDigest != [32]byte{} { + return fmt.Errorf("candidate config digest is nonempty for exec, expected empty, cfg: %v", execConfigs.CandidateConfig) + } + return nil +} + func setupCommitDON( - tabi *abi.ABI, donID uint32, commitConfig ccip_home.CCIPHomeOCR3Config, capReg *capabilities_registry.CapabilitiesRegistry, @@ -668,7 +895,7 @@ func setupCommitDON( nodes deployment.Nodes, ccipHome *ccip_home.CCIPHome, ) error { - encodedSetCandidateCall, err := tabi.Pack( + encodedSetCandidateCall, err := CCIPHomeABI.Pack( "setCandidate", donID, commitConfig.PluginType, @@ -678,7 +905,6 @@ func setupCommitDON( if err != nil { return fmt.Errorf("pack set candidate call: %w", err) } - tx, err := capReg.AddDON(home.DeployerKey, nodes.PeerIDs(), []capabilities_registry.CapabilitiesRegistryCapabilityConfiguration{ { CapabilityId: CCIPCapabilityID, @@ -703,7 +929,7 @@ func setupCommitDON( } fmt.Printf("commit candidate digest after setCandidate: %x\n", commitCandidateDigest) - encodedPromotionCall, err := tabi.Pack( + encodedPromotionCall, err := CCIPHomeABI.Pack( "promoteCandidateAndRevokeActive", donID, commitConfig.PluginType, @@ -772,7 +998,7 @@ func AddDON( ocrSecrets deployment.OCRSecrets, capReg *capabilities_registry.CapabilitiesRegistry, ccipHome *ccip_home.CCIPHome, - rmnHomeAddress []byte, + rmnHomeAddress common.Address, offRamp *offramp.OffRamp, feedChainSel uint64, // Token address on Dest chain to aggregate address on feed chain @@ -781,11 +1007,11 @@ func AddDON( home deployment.Chain, nodes deployment.Nodes, ) error { - ocrConfigs, err := BuildAddDONArgs(lggr, ocrSecrets, offRamp, dest, feedChainSel, tokenInfo, nodes, rmnHomeAddress) + ocrConfigs, err := BuildOCR3ConfigForCCIPHome(lggr, ocrSecrets, offRamp, dest, feedChainSel, tokenInfo, nodes, rmnHomeAddress) if err != nil { return err } - _, err = CreateDON(lggr, capReg, ccipHome, ocrConfigs, home, nodes) + err = CreateDON(lggr, capReg, ccipHome, ocrConfigs, home, dest.Selector, nodes) if err != nil { return err } diff --git a/integration-tests/deployment/ccip/propose.go b/integration-tests/deployment/ccip/propose.go index 87931a4f8f3..33d073c5e67 100644 --- a/integration-tests/deployment/ccip/propose.go +++ b/integration-tests/deployment/ccip/propose.go @@ -81,6 +81,7 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, state CCIPOnChainState, sel uint64) { + t.Log("Executing proposal on chain", sel) // Set the root. tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) require.NoError(t, err2) @@ -217,3 +218,22 @@ func GenerateAcceptOwnershipProposal( batches, timelock.Schedule, "0s") } + +func BuildProposalMetadata(state CCIPOnChainState, chains []uint64) (map[mcms.ChainIdentifier]common.Address, map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { + tlAddressMap := make(map[mcms.ChainIdentifier]common.Address) + metaDataPerChain := make(map[mcms.ChainIdentifier]mcms.ChainMetadata) + for _, sel := range chains { + chainId := mcms.ChainIdentifier(sel) + tlAddressMap[chainId] = state.Chains[sel].Timelock.Address() + mcm := state.Chains[sel].ProposerMcm + opCount, err := mcm.GetOpCount(nil) + if err != nil { + return nil, nil, err + } + metaDataPerChain[chainId] = mcms.ChainMetadata{ + StartingOpCount: opCount.Uint64(), + MCMAddress: mcm.Address(), + } + } + return tlAddressMap, metaDataPerChain, nil +} diff --git a/integration-tests/deployment/ccip/propose_home_chain.go b/integration-tests/deployment/ccip/propose_home_chain.go new file mode 100644 index 00000000000..18b0e9deebe --- /dev/null +++ b/integration-tests/deployment/ccip/propose_home_chain.go @@ -0,0 +1,111 @@ +package ccipdeployment + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + + "github.com/smartcontractkit/chainlink/integration-tests/deployment" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" +) + +// SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. +func SetCandidateExecPluginProposal( + state CCIPOnChainState, + e deployment.Environment, + nodes deployment.Nodes, + ocrSecrets deployment.OCRSecrets, + homeChainSel, feedChainSel, newChainSel uint64, + tokenConfig TokenConfig, + rmnHomeAddress common.Address, +) (*timelock.MCMSWithTimelockProposal, error) { + newDONArgs, err := BuildOCR3ConfigForCCIPHome( + e.Logger, + ocrSecrets, + state.Chains[newChainSel].OffRamp, + e.Chains[newChainSel], + feedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[newChainSel].LinkToken), + nodes.NonBootstraps(), + rmnHomeAddress, + ) + if err != nil { + return nil, err + } + + execConfig, ok := newDONArgs[types.PluginTypeCCIPExec] + if !ok { + return nil, fmt.Errorf("missing exec plugin in ocr3Configs") + } + + setCandidateMCMSOps, err := SetCandidateExecPluginOps( + execConfig, + state.Chains[homeChainSel].CapabilityRegistry, + state.Chains[homeChainSel].CCIPHome, + newChainSel, + nodes.NonBootstraps(), + ) + + if err != nil { + return nil, err + } + timelockAddresses, metaDataPerChain, err := BuildProposalMetadata(state, []uint64{homeChainSel}) + if err != nil { + return nil, err + } + return timelock.NewMCMSWithTimelockProposal( + "1", + 2004259681, // TODO: should be parameterized and based on current block timestamp. + []mcms.Signature{}, + false, + metaDataPerChain, + timelockAddresses, + "SetCandidate for execution", + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: setCandidateMCMSOps, + }}, + timelock.Schedule, + "0s", // TODO: Should be parameterized. + ) +} + +// PromoteCandidateProposal generates a proposal to call promoteCandidate on the CCIPHome through CapReg. +// This needs to be called after SetCandidateProposal is executed. +func PromoteCandidateProposal( + state CCIPOnChainState, + homeChainSel, newChainSel uint64, + nodes deployment.Nodes, +) (*timelock.MCMSWithTimelockProposal, error) { + promoteCandidateOps, err := PromoteCandidateOps( + state.Chains[homeChainSel].CapabilityRegistry, + state.Chains[homeChainSel].CCIPHome, + newChainSel, + nodes.NonBootstraps(), + ) + if err != nil { + return nil, err + } + + timelockAddresses, metaDataPerChain, err := BuildProposalMetadata(state, []uint64{homeChainSel}) + if err != nil { + return nil, err + } + return timelock.NewMCMSWithTimelockProposal( + "1", + 2004259681, // TODO: should be parameterized and based on current block timestamp. + []mcms.Signature{}, + false, + metaDataPerChain, + timelockAddresses, + "promoteCandidate for commit and execution", + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: promoteCandidateOps, + }}, + timelock.Schedule, + "0s", // TODO: Should be parameterized. + ) +} diff --git a/integration-tests/deployment/ccip/test_assertions.go b/integration-tests/deployment/ccip/test_assertions.go index a37b862fd7e..91bd0550545 100644 --- a/integration-tests/deployment/ccip/test_assertions.go +++ b/integration-tests/deployment/ccip/test_assertions.go @@ -113,10 +113,10 @@ func ConfirmCommitWithExpectedSeqNumRange( // the expected range. for _, mr := range report.MerkleRoots { if mr.SourceChainSelector == src.Selector && - uint64(expectedSeqNumRange.Start()) == mr.MinSeqNr && - uint64(expectedSeqNumRange.End()) == mr.MaxSeqNr { - t.Logf("Received commit report on selector %d from source selector %d expected seq nr range %s, token prices: %v", - dest.Selector, src.Selector, expectedSeqNumRange.String(), report.PriceUpdates.TokenPriceUpdates) + uint64(expectedSeqNumRange.Start()) >= mr.MinSeqNr && + uint64(expectedSeqNumRange.End()) <= mr.MaxSeqNr { + t.Logf("Received commit report for [%d, %d] on selector %d from source selector %d expected seq nr range %s, token prices: %v", + mr.MinSeqNr, mr.MaxSeqNr, dest.Selector, src.Selector, expectedSeqNumRange.String(), report.PriceUpdates.TokenPriceUpdates) return nil } } @@ -213,6 +213,8 @@ func ConfirmExecWithSeqNr( return nil } case execEvent := <-sink: + t.Logf("Received ExecutionStateChanged for seqNum %d on chain %d (offramp %s) from chain %d", + execEvent.SequenceNumber, dest.Selector, offRamp.Address().String(), source.Selector) if execEvent.SequenceNumber == expectedSeqNr && execEvent.SourceChainSelector == source.Selector { t.Logf("Received ExecutionStateChanged on chain %d (offramp %s) from chain %d with expected sequence number %d", dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index 57c1366e494..4159a35ed65 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -216,6 +216,7 @@ func SendRequest(t *testing.T, e deployment.Environment, state CCIPOnChainState, dest, msg) require.NoError(t, err) + e.Chains[src].DeployerKey.Value = nil blockNum, err := e.Chains[src].Confirm(tx) require.NoError(t, err) it, err := state.Chains[src].OnRamp.FilterCCIPMessageSent(&bind.FilterOpts{