From fa1a1e2706fc0d1529c022fcd75085d2e9b4f972 Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Wed, 9 Oct 2024 10:07:47 -0400 Subject: [PATCH 1/2] Support RMN in tooling (#14682) * RMN init * Scaffolding * Local only * Fix import cycle * Improve readability * Simplifying more * Fixes * More readability * Drop always pull, images working * change to evmNetworks * Fix key extraction * Lint and RMN names * Comments * More comments * Fix test --------- Co-authored-by: AnieeG Co-authored-by: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> --- integration-tests/deployment/ccip/rmn_test.go | 18 + .../deployment/ccip/test_helpers.go | 134 +++++- .../deployment/devenv/build_env.go | 30 +- integration-tests/deployment/devenv/rmn.go | 384 ++++++++++++++++++ .../deployment/devenv/rmn_config.go | 137 +++++++ .../deployment/devenv/rmn_test.go | 15 + integration-tests/smoke/ccip_test.go | 2 +- integration-tests/testconfig/ccip/config.go | 9 + 8 files changed, 717 insertions(+), 12 deletions(-) create mode 100644 integration-tests/deployment/ccip/rmn_test.go create mode 100644 integration-tests/deployment/devenv/rmn.go create mode 100644 integration-tests/deployment/devenv/rmn_config.go create mode 100644 integration-tests/deployment/devenv/rmn_test.go diff --git a/integration-tests/deployment/ccip/rmn_test.go b/integration-tests/deployment/ccip/rmn_test.go new file mode 100644 index 00000000000..0ece72eadd3 --- /dev/null +++ b/integration-tests/deployment/ccip/rmn_test.go @@ -0,0 +1,18 @@ +package ccipdeployment + +import ( + "testing" + + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestRMN(t *testing.T) { + t.Skip("Local only") + // TODO: needs to return RMN peerIDs. + _, rmnCluster := NewLocalDevEnvironmentWithRMN(t, logger.TestLogger(t)) + for rmnNode, rmn := range rmnCluster.Nodes { + t.Log(rmnNode, rmn.Proxy.PeerID, rmn.RMN.OffchainPublicKey, rmn.RMN.EVMOnchainPublicKey) + } + // Use peerIDs to set RMN config. + // Add a lane, send a message. +} diff --git a/integration-tests/deployment/ccip/test_helpers.go b/integration-tests/deployment/ccip/test_helpers.go index f4c80f095bc..3b351c1b84f 100644 --- a/integration-tests/deployment/ccip/test_helpers.go +++ b/integration-tests/deployment/ccip/test_helpers.go @@ -12,6 +12,10 @@ import ( "github.com/pkg/errors" "golang.org/x/sync/errgroup" + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" + + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" @@ -28,6 +32,7 @@ import ( jobv1 "github.com/smartcontractkit/chainlink/integration-tests/deployment/jd/job/v1" "github.com/smartcontractkit/chainlink/integration-tests/deployment/memory" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink/integration-tests/testconfig" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -251,7 +256,7 @@ func (d DeployedLocalDevEnvironment) RestartChainlinkNodes(t *testing.T) error { return errGrp.Wait() } -func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedEnv { +func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) (DeployedEnv, *test_env.CLClusterTestEnv, testconfig.TestConfig) { ctx := testcontext.Get(t) // create a local docker environment with simulated chains and job-distributor // we cannot create the chainlink nodes yet as we need to deploy the capability registry first @@ -292,7 +297,134 @@ func NewLocalDevEnvironment(t *testing.T, lggr logger.Logger) DeployedEnv { FeedChainSel: feedSel, ReplayBlocks: replayBlocks, FeeTokenContracts: feeContracts, + }, testEnv, cfg +} + +func NewLocalDevEnvironmentWithRMN(t *testing.T, lggr logger.Logger) (DeployedEnv, devenv.RMNCluster) { + tenv, dockerenv, _ := NewLocalDevEnvironment(t, lggr) + state, err := LoadOnchainState(tenv.Env, tenv.Ab) + require.NoError(t, err) + + feeds := state.Chains[tenv.FeedChainSel].USDFeeds + tokenConfig := NewTokenConfig() + tokenConfig.UpsertTokenInfo(LinkSymbol, + pluginconfig.TokenInfo{ + AggregatorAddress: feeds[LinkSymbol].Address().String(), + Decimals: LinkDecimals, + DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), + }, + ) + // Deploy CCIP contracts. + err = DeployCCIPContracts(tenv.Env, tenv.Ab, DeployCCIPContractConfig{ + HomeChainSel: tenv.HomeChainSel, + FeedChainSel: tenv.FeedChainSel, + ChainsToDeploy: tenv.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + MCMSConfig: NewTestMCMSConfig(t, tenv.Env), + CapabilityRegistry: state.Chains[tenv.HomeChainSel].CapabilityRegistry.Address(), + FeeTokenContracts: tenv.FeeTokenContracts, + }) + require.NoError(t, err) + l := logging.GetTestLogger(t) + config := GenerateTestRMNConfig(t, 1, tenv, MustNetworksToRPCMap(dockerenv.EVMNetworks)) + rmnCluster, err := devenv.NewRMNCluster( + t, l, + []string{dockerenv.DockerNetwork.Name}, + config, + "rageproxy", + "latest", + "afn2proxy", + "latest", + dockerenv.LogStream, + ) + require.NoError(t, err) + return tenv, *rmnCluster +} + +func MustNetworksToRPCMap(evmNetworks []*blockchain.EVMNetwork) map[uint64]string { + rpcs := make(map[uint64]string) + for _, network := range evmNetworks { + sel, err := chainsel.SelectorFromChainId(uint64(network.ChainID)) + if err != nil { + panic(err) + } + rpcs[sel] = network.HTTPURLs[0] + } + return rpcs +} + +func MustCCIPNameToRMNName(a string) string { + m := map[string]string{ + chainsel.GETH_TESTNET.Name: "DevnetAlpha", + chainsel.GETH_DEVNET_2.Name: "DevnetBeta", + // TODO: Add more as needed. + } + v, ok := m[a] + if !ok { + panic(fmt.Sprintf("no mapping for %s", a)) + } + return v +} + +func GenerateTestRMNConfig(t *testing.T, nRMNNodes int, tenv DeployedEnv, rpcMap map[uint64]string) map[string]devenv.RMNConfig { + // Find the bootstrappers. + nodes, err := deployment.NodeInfo(tenv.Env.NodeIDs, tenv.Env.Offchain) + require.NoError(t, err) + bootstrappers := nodes.BootstrapLocators() + + // Just set all RMN nodes to support all chains. + state, err := LoadOnchainState(tenv.Env, tenv.Ab) + require.NoError(t, err) + var remoteChains []devenv.RemoteChain + var rpcs []devenv.Chain + for chainSel, chain := range state.Chains { + c, _ := chainsel.ChainBySelector(chainSel) + rmnName := MustCCIPNameToRMNName(c.Name) + remoteChains = append(remoteChains, devenv.RemoteChain{ + Name: rmnName, + Stability: devenv.Stability{Type: "stable"}, + StartBlockNumber: 0, + OffRamp: chain.OffRamp.Address().String(), + RMNRemote: chain.RMNRemote.Address().String(), + }) + rpcs = append(rpcs, devenv.Chain{ + Name: rmnName, + RPC: rpcMap[chainSel], + }) + } + hc, _ := chainsel.ChainBySelector(tenv.HomeChainSel) + shared := devenv.SharedConfig{ + Networking: devenv.Networking{ + RageProxy: devenv.DefaultRageProxy, + Bootstrappers: bootstrappers, + }, + HomeChain: devenv.HomeChain{ + Name: MustCCIPNameToRMNName(hc.Name), + CapabilitiesRegistry: state.Chains[tenv.HomeChainSel].CapabilityRegistry.Address().String(), + CCIPConfig: state.Chains[tenv.HomeChainSel].CCIPConfig.Address().String(), + // TODO: RMNHome + }, + RemoteChains: remoteChains, + } + + rmnConfig := make(map[string]devenv.RMNConfig) + for i := 0; i < nRMNNodes; i++ { + // Listen addresses _should_ be able to operator on the same port since + // they are inside the docker network. + proxyLocal := devenv.ProxyLocalConfig{ + ListenAddresses: []string{devenv.DefaultProxyListenAddress}, + AnnounceAddresses: []string{}, + ProxyAddress: devenv.DefaultRageProxy, + DiscovererDbPath: devenv.DefaultDiscovererDbPath, + } + rmnConfig[fmt.Sprintf("rmn_%d", i)] = devenv.RMNConfig{ + Shared: shared, + Local: devenv.LocalConfig{Chains: rpcs}, + ProxyShared: devenv.DefaultRageProxySharedConfig, + ProxyLocal: proxyLocal, + } } + return rmnConfig } // AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain diff --git a/integration-tests/deployment/devenv/build_env.go b/integration-tests/deployment/devenv/build_env.go index 881c49ce65f..c564c4e068a 100644 --- a/integration-tests/deployment/devenv/build_env.go +++ b/integration-tests/deployment/devenv/build_env.go @@ -19,6 +19,8 @@ import ( "golang.org/x/sync/errgroup" "google.golang.org/grpc/credentials/insecure" + "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/conversions" @@ -80,6 +82,21 @@ func CreateDockerEnv(t *testing.T) ( env, err := builder.Build() require.NoError(t, err, "Error building test environment") + // we need to update the URLs for the simulated networks to the private chain RPCs in the docker test environment + // so that the chainlink nodes and rmn nodes can internally connect to the chain + env.EVMNetworks = []*blockchain.EVMNetwork{} + for i, net := range evmNetworks { + // if network is simulated, update the URLs with private chain RPCs in the docker test environment + // so that nodes can internally connect to the chain + if net.Simulated { + rpcProvider, err := env.GetRpcProvider(net.ChainID) + require.NoError(t, err, "Error getting rpc provider") + evmNetworks[i].HTTPURLs = rpcProvider.PrivateHttpUrls() + evmNetworks[i].URLs = rpcProvider.PrivateWsUrsl() + } + env.EVMNetworks = append(env.EVMNetworks, &evmNetworks[i]) + } + chains := CreateChainConfigFromNetworks(t, env, privateEthereumNetworks, cfg.GetNetworkConfig()) jdConfig := JDConfig{ @@ -123,16 +140,9 @@ func StartChainlinkNodes( env *test_env.CLClusterTestEnv, cfg tc.TestConfig, ) error { - evmNetworks := networks.MustGetSelectedNetworkConfig(cfg.GetNetworkConfig()) - for i, net := range evmNetworks { - // if network is simulated, update the URLs with private chain RPCs in the docker test environment - // so that nodes can internally connect to the chain - if net.Simulated { - rpcProvider, err := env.GetRpcProvider(net.ChainID) - require.NoError(t, err, "Error getting rpc provider") - evmNetworks[i].HTTPURLs = rpcProvider.PrivateHttpUrls() - evmNetworks[i].URLs = rpcProvider.PrivateWsUrsl() - } + var evmNetworks []blockchain.EVMNetwork + for i := range env.EVMNetworks { + evmNetworks = append(evmNetworks, *env.EVMNetworks[i]) } noOfNodes := pointer.GetInt(cfg.CCIP.CLNode.NoOfPluginNodes) + pointer.GetInt(cfg.CCIP.CLNode.NoOfBootstraps) if env.ClCluster == nil { diff --git a/integration-tests/deployment/devenv/rmn.go b/integration-tests/deployment/devenv/rmn.go new file mode 100644 index 00000000000..a877c4fdfab --- /dev/null +++ b/integration-tests/deployment/devenv/rmn.go @@ -0,0 +1,384 @@ +package devenv + +import ( + "context" + "crypto/ed25519" + "encoding/json" + "fmt" + "io" + "net" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/google/uuid" + "github.com/pkg/errors" + "github.com/rs/zerolog" + tc "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/exec" + tcwait "github.com/testcontainers/testcontainers-go/wait" + + "github.com/smartcontractkit/chainlink-testing-framework/lib/docker" + "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" + "github.com/smartcontractkit/chainlink-testing-framework/lib/logging" + "github.com/smartcontractkit/chainlink-testing-framework/lib/logstream" + p2ptypes "github.com/smartcontractkit/chainlink/v2/core/services/p2p/types" +) + +const ( + RMNKeyStore = "keystore/afn2proxy-keystore.json" + ProxyKeyStore = "keystore/rageproxy-keystore.json" +) + +type RageProxy struct { + test_env.EnvComponent + proxyListenerPort string + proxyPort string + Passphrase string + Local ProxyLocalConfig + Shared ProxySharedConfig + + // Generated on first time boot. + // Needed for RMHHome. + PeerID p2ptypes.PeerID +} + +func NewRage2ProxyComponent( + networks []string, + name, + imageName, + imageVersion string, + local ProxyLocalConfig, + shared ProxySharedConfig, + logStream *logstream.LogStream, +) (*RageProxy, error) { + rageName := fmt.Sprintf("%s-proxy-%s", name, uuid.NewString()[0:8]) + + // TODO support multiple listeners + _, listenPort, err := net.SplitHostPort(local.ListenAddresses[0]) + if err != nil { + return nil, err + } + _, proxyPort, err := net.SplitHostPort(local.ProxyAddress) + if err != nil { + return nil, err + } + + rmn := &RageProxy{ + EnvComponent: test_env.EnvComponent{ + ContainerName: rageName, + ContainerImage: imageName, + ContainerVersion: imageVersion, + Networks: networks, + LogStream: logStream, + }, + Passphrase: DefaultAFNPasphrase, + proxyListenerPort: listenPort, + proxyPort: proxyPort, + Local: local, + Shared: shared, + } + return rmn, nil +} + +func extractPeerID(b []byte) (p2ptypes.PeerID, error) { + var keystore struct { + AdditionalData string `json:"additionalData"` + } + if err := json.Unmarshal(b, &keystore); err != nil { + return p2ptypes.PeerID{}, err + } + var additionalData struct { + PeerID string `json:"PeerID"` + } + if err := json.Unmarshal([]byte(keystore.AdditionalData), &additionalData); err != nil { + return p2ptypes.PeerID{}, err + } + var peerID p2ptypes.PeerID + if err := peerID.UnmarshalText([]byte(additionalData.PeerID)); err != nil { + return p2ptypes.PeerID{}, err + } + return peerID, nil +} + +func (proxy *RageProxy) Start(t *testing.T, lggr zerolog.Logger) (tc.Container, error) { + sharedRageProxy, err := proxy.Shared.rageProxyShared() + if err != nil { + return nil, err + } + localRageProxy, err := proxy.Local.rageProxyLocal() + if err != nil { + return nil, err + } + + l := tc.Logger + if t != nil { + l = logging.CustomT{ + T: t, + L: lggr, + } + } + container, err := docker.StartContainerWithRetry(lggr, tc.GenericContainerRequest{ + ContainerRequest: tc.ContainerRequest{ + Name: proxy.ContainerName, + Image: fmt.Sprintf("%s:%s", proxy.ContainerImage, proxy.ContainerVersion), + Env: map[string]string{ + "RAGEPROXY_PASSPHRASE": proxy.Passphrase, + }, + ExposedPorts: []string{ + test_env.NatPortFormat(proxy.proxyPort), + test_env.NatPortFormat(proxy.proxyListenerPort), + }, + Files: []tc.ContainerFile{ + { + HostFilePath: sharedRageProxy, + ContainerFilePath: "/app/cfg/rageproxy-shared.json", + FileMode: 0644, + }, + { + HostFilePath: localRageProxy, + ContainerFilePath: "/app/cfg/rageproxy-local.json", + FileMode: 0644, + }, + }, + WaitingFor: tcwait.ForExec([]string{"cat", ProxyKeyStore}), + LifecycleHooks: []tc.ContainerLifecycleHooks{ + { + PostStarts: proxy.PostStartsHooks, + PostStops: proxy.PostStopsHooks, + PreTerminates: proxy.PreTerminatesHooks, + }, + }, + }, + Started: true, + Logger: l, + }) + if err != nil { + return nil, err + } + _, reader, err := container.Exec(context.Background(), []string{ + "cat", ProxyKeyStore}, exec.Multiplexed()) + if err != nil { + return nil, errors.Wrapf(err, "Unable to cat keystore") + } + b, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + peerID, err := extractPeerID(b) + if err != nil { + return nil, errors.Wrapf(err, "Unable to extract peerID %s", string(b)) + } + proxy.PeerID = peerID + proxy.Container = container + return container, nil +} + +type AFN2Proxy struct { + test_env.EnvComponent + AFNPassphrase string + Shared SharedConfig + Local LocalConfig + + // Generated on boot + OffchainPublicKey ed25519.PublicKey // RMNHome + EVMOnchainPublicKey common.Address // RMNRemote +} + +func NewAFN2ProxyComponent( + networks []string, + name, + imageName, + imageVersion string, + shared SharedConfig, + local LocalConfig, + logStream *logstream.LogStream) (*AFN2Proxy, error) { + afnName := fmt.Sprintf("%s-%s", name, uuid.NewString()[0:8]) + rmn := &AFN2Proxy{ + EnvComponent: test_env.EnvComponent{ + ContainerName: afnName, + ContainerImage: imageName, + ContainerVersion: imageVersion, + Networks: networks, + LogStream: logStream, + }, + AFNPassphrase: DefaultAFNPasphrase, + Shared: shared, + Local: local, + } + + return rmn, nil +} + +func extractKeys(b []byte) (common.Address, ed25519.PublicKey, error) { + var keystore struct { + AssociatedData string `json:"associated_data"` + } + if err := json.Unmarshal(b, &keystore); err != nil { + return common.Address{}, ed25519.PublicKey{}, err + } + var associatedData struct { + OffchainPublicKey string `json:"offchain_public_key"` + EVMOnchainPublicKey string `json:"evm_onchain_public_key"` + } + if err := json.Unmarshal([]byte(keystore.AssociatedData), &associatedData); err != nil { + return common.Address{}, ed25519.PublicKey{}, err + } + offchainKey, err := hexutil.Decode(associatedData.OffchainPublicKey) + if err != nil { + return common.Address{}, ed25519.PublicKey{}, err + } + if len(offchainKey) != ed25519.PublicKeySize { + return common.Address{}, ed25519.PublicKey{}, fmt.Errorf("invalid offchain public key: %x", offchainKey) + } + return common.HexToAddress(associatedData.EVMOnchainPublicKey), offchainKey, nil +} + +func (rmn *AFN2Proxy) Start(t *testing.T, lggr zerolog.Logger, reuse bool) (tc.Container, error) { + localAFN2Proxy, err := rmn.Local.afn2ProxyLocalConfigFile() + if err != nil { + return nil, err + } + sharedAFN2Proxy, err := rmn.Shared.afn2ProxySharedConfigFile() + if err != nil { + return nil, err + } + + l := tc.Logger + if t != nil { + l = logging.CustomT{ + T: t, + L: lggr, + } + } + container, err := docker.StartContainerWithRetry(lggr, tc.GenericContainerRequest{ + ContainerRequest: tc.ContainerRequest{ + Name: rmn.ContainerName, + Image: fmt.Sprintf("%s:%s", rmn.ContainerImage, rmn.ContainerVersion), + Env: map[string]string{ + "AFN_PASSPHRASE": rmn.AFNPassphrase, + }, + Files: []tc.ContainerFile{ + { + HostFilePath: sharedAFN2Proxy, + ContainerFilePath: "/app/cfg/afn2proxy-shared.toml", + FileMode: 0644, + }, + { + HostFilePath: localAFN2Proxy, + ContainerFilePath: "/app/cfg/afn2proxy-local.toml", + FileMode: 0644, + }, + }, + WaitingFor: tcwait.ForExec([]string{"cat", RMNKeyStore}), + LifecycleHooks: []tc.ContainerLifecycleHooks{ + { + PostStarts: rmn.PostStartsHooks, + PostStops: rmn.PostStopsHooks, + PreTerminates: rmn.PreTerminatesHooks, + }, + }, + }, + Started: true, + Reuse: reuse, + Logger: l, + }) + if err != nil { + return nil, err + } + _, reader, err := container.Exec(context.Background(), []string{ + "cat", RMNKeyStore}, exec.Multiplexed()) + if err != nil { + return nil, errors.Wrapf(err, "Unable to cat keystore") + } + b, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + onchainPubKey, offchainPubKey, err := extractKeys(b) + if err != nil { + return nil, errors.Wrapf(err, "Unable to extract peerID %s", string(b)) + } + rmn.OffchainPublicKey = offchainPubKey + rmn.EVMOnchainPublicKey = onchainPubKey + rmn.Container = container + return container, nil +} + +type RMNNode struct { + RMN AFN2Proxy + Proxy RageProxy +} + +func (n *RMNNode) Start(t *testing.T, lggr zerolog.Logger) error { + _, err := n.Proxy.Start(t, lggr) + if err != nil { + return err + } + _, err = n.RMN.Start(t, lggr, false) + if err != nil { + return err + } + return nil +} + +type RMNCluster struct { + Nodes map[string]RMNNode + t *testing.T + l zerolog.Logger +} + +// NewRMNCluster creates a new RMNCluster with the given configuration +// and starts it. +func NewRMNCluster( + t *testing.T, + l zerolog.Logger, + networks []string, + config map[string]RMNConfig, + proxyImage string, + proxyVersion string, + rmnImage string, + rmnVersion string, + logStream *logstream.LogStream, +) (*RMNCluster, error) { + rmn := &RMNCluster{ + t: t, + l: l, + Nodes: make(map[string]RMNNode), + } + for name, rmnConfig := range config { + proxy, err := NewRage2ProxyComponent(networks, name, proxyImage, proxyVersion, rmnConfig.ProxyLocal, rmnConfig.ProxyShared, logStream) + if err != nil { + return nil, err + } + _, err = proxy.Start(t, l) + if err != nil { + return nil, err + } + + // TODO: Hack here is we overwrite the host with the container name + // since the RMN node needs to be able to reach its own proxy container. + proxyName, err := proxy.Container.Name(context.Background()) + if err != nil { + return nil, err + } + _, port, err := net.SplitHostPort(rmnConfig.Shared.Networking.RageProxy) + if err != nil { + return nil, err + } + rmnConfig.Shared.Networking.RageProxy = fmt.Sprintf("%s:%s", proxyName, port) + afn, err := NewAFN2ProxyComponent(networks, name, rmnImage, rmnVersion, rmnConfig.Shared, rmnConfig.Local, logStream) + if err != nil { + return nil, err + } + _, err = afn.Start(t, l, false) + if err != nil { + return nil, err + } + rmn.Nodes[name] = RMNNode{ + RMN: *afn, + Proxy: *proxy, + } + } + return rmn, nil +} diff --git a/integration-tests/deployment/devenv/rmn_config.go b/integration-tests/deployment/devenv/rmn_config.go new file mode 100644 index 00000000000..798a988a5e8 --- /dev/null +++ b/integration-tests/deployment/devenv/rmn_config.go @@ -0,0 +1,137 @@ +package devenv + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/pelletier/go-toml/v2" +) + +const ( + DefaultAFNPasphrase = "my-not-so-secret-passphrase" + DefaultRageProxy = "127.0.0.1:8081" + DefaultProxyListenAddress = "127.0.0.1:8080" + DefaultDiscovererDbPath = "/app/rageproxy-discoverer-db.json" +) + +var ( + DefaultRageProxySharedConfig = ProxySharedConfig{ + Host: HostConfig{ + DurationBetweenDials: 1000000000, + }, + Discoverer: DiscovererConfig{ + DeltaReconcile: 1000000000, + }, + } +) + +type Networking struct { + RageProxy string `toml:"rageproxy"` + Bootstrappers []string `toml:"bootstrappers"` +} + +type HomeChain struct { + Name string `toml:"name"` + CapabilitiesRegistry string `toml:"capabilities_registry"` + CCIPConfig string `toml:"ccip_config"` + RMNHome string `toml:"rmn_home"` +} + +type Stability struct { + Type string `toml:"type"` +} + +type RemoteChain struct { + Name string `toml:"name"` + Stability Stability `toml:"stability"` + StartBlockNumber int `toml:"start_block_number"` + OffRamp string `toml:"off_ramp"` + RMNRemote string `toml:"rmn_remote"` +} + +type SharedConfig struct { + Networking Networking `toml:"networking"` + HomeChain HomeChain `toml:"home_chain"` + RemoteChains []RemoteChain `toml:"remote_chains"` +} + +func (s SharedConfig) afn2ProxySharedConfigFile() (string, error) { + data, err := toml.Marshal(s) + if err != nil { + return "", fmt.Errorf("failed to marshal afn2Proxy shared config: %w", err) + } + return CreateTempFile(data, "afn2proxy_shared") +} + +type LocalConfig struct { + Chains []Chain `toml:"chains"` +} + +func (l LocalConfig) afn2ProxyLocalConfigFile() (string, error) { + data, err := toml.Marshal(l) + if err != nil { + return "", fmt.Errorf("failed to marshal afn2Proxy local config: %w", err) + } + return CreateTempFile(data, "afn2proxy_local") +} + +type Chain struct { + Name string `toml:"name"` + RPC string `toml:"rpc"` +} + +type ProxyLocalConfig struct { + ListenAddresses []string `json:"ListenAddresses"` + AnnounceAddresses []string `json:"AnnounceAddresses"` + ProxyAddress string `json:"ProxyAddress"` + DiscovererDbPath string `json:"DiscovererDbPath"` +} + +func (l ProxyLocalConfig) rageProxyLocal() (string, error) { + data, err := json.Marshal(l) + if err != nil { + return "", fmt.Errorf("failed to marshal rageProxy local config: %w", err) + } + return CreateTempFile(data, "rageproxy_local") +} + +type HostConfig struct { + DurationBetweenDials int64 `json:"DurationBetweenDials"` +} + +type DiscovererConfig struct { + DeltaReconcile int64 `json:"DeltaReconcile"` +} + +type ProxySharedConfig struct { + Host HostConfig `json:"Host"` + Discoverer DiscovererConfig `json:"Discoverer"` +} + +func (s ProxySharedConfig) rageProxyShared() (string, error) { + data, err := json.Marshal(s) + if err != nil { + return "", fmt.Errorf("failed to marshal rageProxy shared config: %w", err) + } + return CreateTempFile(data, "rageproxy_shared") +} + +type RMNConfig struct { + Shared SharedConfig + Local LocalConfig + ProxyLocal ProxyLocalConfig + ProxyShared ProxySharedConfig +} + +func CreateTempFile(data []byte, pattern string) (string, error) { + file, err := os.CreateTemp("", pattern) + if err != nil { + return "", fmt.Errorf("failed to create temp file for %s: %w", pattern, err) + } + _, err = file.Write(data) + if err != nil { + return "", fmt.Errorf("failed to write %s: %w", pattern, err) + } + return file.Name(), nil +} diff --git a/integration-tests/deployment/devenv/rmn_test.go b/integration-tests/deployment/devenv/rmn_test.go new file mode 100644 index 00000000000..7d07670ff7d --- /dev/null +++ b/integration-tests/deployment/devenv/rmn_test.go @@ -0,0 +1,15 @@ +package devenv + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExtractPeerID(t *testing.T) { + data := []byte("{\"Salt\":\"yAUVSNGPnY78hJwJwKDBmA==\",\"Nonce\":\"8p0vNKpnQH1P+7/cg2dM3vNlc60tzPl0\",\"Ciphertext\":\"VboRchQzDx/+zIVDWyTXIEwo4Ej0s7O6kQqmgCqW+A+HEXl6bI02W1Y2T88XuY0m44eC9DvSpBPhs/VLtgymj6nBcl+nzfJHLMp2pBMPjKHxgNsEk34mDgnmqYKTtIkgzv7hEyy7j0CAcR/RxkjfQNKpfWOlNtAFryFO+w==\",\"AdditionalData\":\"{\\\"PeerID\\\":\\\"12D3KooWNqugYSJw9thwwu1PC3aEpPsBxMZM2EdzpJXGesRG4E8n\\\"}\"}") + peerID, err := extractPeerID(data) + require.NoError(t, err) + assert.Equal(t, "12D3KooWNqugYSJw9thwwu1PC3aEpPsBxMZM2EdzpJXGesRG4E8n", peerID.String()) +} diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 6108d3889d6..c4fd42a1bec 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -19,7 +19,7 @@ import ( func TestInitialDeployOnLocal(t *testing.T) { lggr := logger.TestLogger(t) ctx := ccdeploy.Context(t) - tenv := ccdeploy.NewLocalDevEnvironment(t, lggr) + tenv, _, _ := ccdeploy.NewLocalDevEnvironment(t, lggr) e := tenv.Env state, err := ccdeploy.LoadOnchainState(tenv.Env, tenv.Ab) diff --git a/integration-tests/testconfig/ccip/config.go b/integration-tests/testconfig/ccip/config.go index bf487ed3940..53c1afaeb0b 100644 --- a/integration-tests/testconfig/ccip/config.go +++ b/integration-tests/testconfig/ccip/config.go @@ -34,6 +34,15 @@ type Config struct { JobDistributorConfig JDConfig `toml:",omitempty"` HomeChainSelector *string `toml:",omitempty"` FeedChainSelector *string `toml:",omitempty"` + RMNConfig RMNConfig `toml:",omitempty"` +} + +type RMNConfig struct { + NoOfNodes *int `toml:",omitempty"` + ProxyImage *string `toml:",omitempty"` + ProxyVersion *string `toml:",omitempty"` + AFNImage *string `toml:",omitempty"` + AFNVersion *string `toml:",omitempty"` } type NodeConfig struct { From 4c3e7ec8c3b334a13be13780dbfe19dc1f2eacd1 Mon Sep 17 00:00:00 2001 From: Dimitris Grigoriou Date: Wed, 9 Oct 2024 17:20:17 +0300 Subject: [PATCH 2/2] Fix TXM flakey test (#14697) --- .changeset/old-humans-watch.md | 5 +++++ core/chains/evm/txmgr/txmgr_test.go | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/old-humans-watch.md diff --git a/.changeset/old-humans-watch.md b/.changeset/old-humans-watch.md new file mode 100644 index 00000000000..e58dedcd368 --- /dev/null +++ b/.changeset/old-humans-watch.md @@ -0,0 +1,5 @@ +--- +"chainlink": minor +--- + +Fix TXM flakey test #internal diff --git a/core/chains/evm/txmgr/txmgr_test.go b/core/chains/evm/txmgr/txmgr_test.go index 3bdf9ef5f2e..c47ca85737b 100644 --- a/core/chains/evm/txmgr/txmgr_test.go +++ b/core/chains/evm/txmgr/txmgr_test.go @@ -501,8 +501,8 @@ func TestTxm_Lifecycle(t *testing.T) { head := cltest.Head(42) finalizedHead := cltest.Head(0) - ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil).Once() - ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(finalizedHead, nil).Once() + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(head, nil) + ethClient.On("HeadByNumber", mock.Anything, mock.Anything).Return(finalizedHead, nil) keyChangeCh := make(chan struct{}) unsub := cltest.NewAwaiter()