From 70d8fe1130a71370319142a3e5a5ee9eeec5771a Mon Sep 17 00:00:00 2001 From: Damjan Smickovski Date: Tue, 27 Feb 2024 11:39:27 +0100 Subject: [PATCH] Move to toml and refactor --- .github/workflows/integration-tests-smoke.yml | 21 +- integration-tests/common/common.go | 219 ++++------- integration-tests/common/gauntlet_common.go | 48 ++- integration-tests/common/test_common.go | 284 ++++++++------ integration-tests/smoke/ocr2_test.go | 31 +- integration-tests/testconfig/configs_embed.go | 20 + .../testconfig/configs_noembed.go | 15 +- integration-tests/testconfig/default.toml | 27 +- integration-tests/testconfig/ocr2/ocr2.go | 125 +----- integration-tests/testconfig/ocr2/ocr2.toml | 3 + integration-tests/testconfig/testconfig.go | 359 ++++++++++++++++++ ops/gauntlet/gauntlet_starknet.go | 4 + 12 files changed, 716 insertions(+), 440 deletions(-) diff --git a/.github/workflows/integration-tests-smoke.yml b/.github/workflows/integration-tests-smoke.yml index 8ede0208a..78ff320cb 100644 --- a/.github/workflows/integration-tests-smoke.yml +++ b/.github/workflows/integration-tests-smoke.yml @@ -125,7 +125,9 @@ jobs: TEST_DURATION: 15m NODE_COUNT: 5 CHAINLINK_IMAGE: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink - CHAINLINK_VERSION: starknet.${{ github.sha }}${{ matrix.image.tag-suffix }} + CHAINLINK_VERSION: starknet.${{ github.sha }} + INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com + scarb_version: "v2.5.4" permissions: checks: write pull-requests: write @@ -151,19 +153,18 @@ jobs: id: install-scarb shell: bash run: | - wget https://github.com/software-mansion/scarb/releases/download/v2.5.4/scarb-v2.5.4-x86_64-unknown-linux-musl.tar.gz - tar -xvzf scarb-v2.5.4-x86_64-unknown-linux-musl.tar.gz - mv -vf scarb-v2.5.4-x86_64-unknown-linux-musl scarb-build + wget https://github.com/software-mansion/scarb/releases/download/${{ env.scarb_version }}/scarb-${{ env.scarb_version }}-x86_64-unknown-linux-musl.tar.gz + tar -xvzf scarb-${{ env.scarb_version }}-x86_64-unknown-linux-musl.tar.gz + mv -vf scarb-${{ env.scarb_version }}-x86_64-unknown-linux-musl scarb-build echo "$GITHUB_WORKSPACE/scarb-build/bin" >> $GITHUB_PATH - - name: Build contracts - run: | - cd contracts && scarb --profile release build - name: Generate config overrides run: | # https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/README.md cat << EOF > config.toml [ChainlinkImage] image="${{ env.CHAINLINK_IMAGE }}" - version="${{ github.sha }}" + version="${{ env.CHAINLINK_VERSION }}" + [Network] + selected_networks=["SIMULATED"] EOF # shellcheck disable=SC2002 BASE64_CONFIG_OVERRIDE=$(cat config.toml | base64 -w 0) @@ -174,14 +175,14 @@ jobs: - name: Run Tests ${{ matrix.image.name }} uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@ea889b3133bd7f16ab19ba4ba130de5d9162c669 # v2.3.4 with: + aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} test_command_to_run: nix develop -c helm repo update && make test-integration-smoke-ci test_download_vendor_packages_command: cd integration-tests && nix develop -c go mod download cl_repo: ${{ env.CL_ECR }} cl_image_tag: starknet.${{ github.sha }}${{ matrix.image.tag-suffix }} - aws_registries: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} token: ${{ secrets.GITHUB_TOKEN }} go_mod_path: ./integration-tests/go.mod QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} QA_KUBECONFIG: ${{ secrets.QA_KUBECONFIG }} - artifacts_location: /home/runner/work/chainlink-starknet/chainlink-starknet/integration-tests/smoke/logs + artifacts_location: /home/runner/work/chainlink-starknet/chainlink-starknet/integration-tests/smoke/logs \ No newline at end of file diff --git a/integration-tests/common/common.go b/integration-tests/common/common.go index b36225e96..b99c381eb 100644 --- a/integration-tests/common/common.go +++ b/integration-tests/common/common.go @@ -2,10 +2,9 @@ package common import ( "fmt" - test_env_starknet "github.com/smartcontractkit/chainlink-starknet/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink-starknet/integration-tests/testconfig" "github.com/smartcontractkit/chainlink-starknet/ops/devnet" "github.com/smartcontractkit/chainlink-starknet/relayer/pkg/chainlink/config" - ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/chainlink" mock_adapter "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/helm/mock-adapter" "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" @@ -26,7 +25,6 @@ import ( common_cfg "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/alias" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" cl "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" @@ -35,188 +33,100 @@ import ( ) var ( - chainName = "starknet" - chainId = "SN_GOERLI" - defaultNodeUrl = "http://starknet:5050" - testnetUrl = "https://starknet-testnet.public.blastapi.io" - DefaultNodeCount = 5 - DefaultTTL = "3h" - DefaultTestDuration = 50 * time.Minute + chainName = "starknet" + chainId = "SN_GOERLI" + DefaultL2RPCInternal = "http://starknet-dev:5000" ) type Common struct { - P2PPort string - ServiceKeyL1 string - ServiceKeyL2 string - ServiceKeyChainlink string - ChainName string - ChainId string - NodeCount int - TTL time.Duration - TestDuration time.Duration - Testnet bool - L2RPCUrl string - MockUrl string - PrivateKey string - Account string - ChainlinkConfig string - Env *environment.Environment - IsK8s bool - K8Config *environment.Config - NodeOpts []test_env.ClNodeOption - DockerEnv *StarknetClusterTestEnv + ChainDetails *ChainDetails + TestEnvDetails *TestEnvDetails + Env *environment.Environment + RPCDetails *RPCDetails + ChainlinkConfig string + TestConfig *testconfig.TestConfig } -type StarknetClusterTestEnv struct { - *test_env.CLClusterTestEnv - Starknet *test_env_starknet.Starknet - Killgrave *ctf_test_env.Killgrave +type ChainDetails struct { + ChainName string + ChainId string } -func New(env string, isK8s bool) *Common { - var err error +type TestEnvDetails struct { + TestDuration time.Duration + K8Config *environment.Config + NodeOpts []test_env.ClNodeOption +} + +type RPCDetails struct { + RPCL1Internal string + RPCL2Internal string + RPCL1External string + RPCL2External string + MockServerUrl string + MockServerEndpoint string + P2PPort string +} + +func New(testConfig *testconfig.TestConfig) *Common { var c *Common - if env == "testnet" { - c = &Common{ - IsK8s: isK8s, - ChainName: chainId, - ChainId: chainId, - L2RPCUrl: testnetUrl, - Testnet: true, - TestDuration: DefaultTestDuration, - } - } else { - c = &Common{ - IsK8s: isK8s, - ChainName: chainName, - ChainId: chainId, - L2RPCUrl: defaultNodeUrl, - TestDuration: DefaultTestDuration, - } - } - // Checking if count of OCR nodes is defined in ENV - nodeCountSet, nodeCountDefined := os.LookupEnv("NODE_COUNT") - if nodeCountDefined && nodeCountSet != "" { - c.NodeCount, err = strconv.Atoi(nodeCountSet) - if err != nil { - panic(fmt.Sprintf("Please define a proper node count for the test: %v", err)) - } - } else { - c.NodeCount = DefaultNodeCount + + duration, err := time.ParseDuration(*testConfig.OCR2.TestDuration) + if err != nil { + panic("Invalid test duration") } - // Checking if TTL env var is set in ENV - ttlValue, ttlDefined := os.LookupEnv("TTL") - if ttlDefined && ttlValue != "" { - duration, err := time.ParseDuration(ttlValue) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } - c.TTL, err = time.ParseDuration(*alias.ShortDur(duration)) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } - } else { - duration, err := time.ParseDuration(DefaultTTL) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } - c.TTL, err = time.ParseDuration(*alias.ShortDur(duration)) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } + c = &Common{ + TestConfig: testConfig, + ChainDetails: &ChainDetails{ + ChainName: chainName, + ChainId: chainId, + }, + TestEnvDetails: &TestEnvDetails{ + TestDuration: duration, + }, + RPCDetails: &RPCDetails{ + P2PPort: "6690", + RPCL2Internal: DefaultL2RPCInternal, + }, } return c } func (c *Common) Default(t *testing.T, namespacePrefix string) (*Common, error) { - c.K8Config = &environment.Config{ - NamespacePrefix: fmt.Sprintf("solana-%s", namespacePrefix), - TTL: c.TTL, + c.TestEnvDetails.K8Config = &environment.Config{ + NamespacePrefix: fmt.Sprintf("starknet-%s", namespacePrefix), + TTL: c.TestEnvDetails.TestDuration, Test: t, } - if c.IsK8s { + if *c.TestConfig.Common.InsideK8s { toml := c.DefaultNodeConfig() tomlString, err := toml.TOMLString() if err != nil { return nil, err } - c.Env = environment.New(c.K8Config). + c.Env = environment.New(c.TestEnvDetails.K8Config). AddHelm(devnet.New(nil)). AddHelm(mock_adapter.New(nil)). AddHelm(chainlink.New(0, map[string]interface{}{ "toml": tomlString, - "replicas": c.NodeCount, + "replicas": c.TestConfig.OCR2.NodeCount, })) } return c, nil } -// getEnv gets the environment variable if it exists and sets it for the remote runner -func getEnv(v string) string { - val := os.Getenv(v) - if val != "" { - os.Setenv(fmt.Sprintf("TEST_%s", v), val) - } - return val -} - -func getNodeCount() int { - // Checking if count of OCR nodes is defined in ENV - nodeCountSet := getEnv("NODE_COUNT") - if nodeCountSet == "" { - nodeCountSet = "4" - } - nodeCount, err := strconv.Atoi(nodeCountSet) - if err != nil { - panic(fmt.Sprintf("Please define a proper node count for the test: %v", err)) - } - return nodeCount -} - -func getTTL() time.Duration { - ttlValue := getEnv("TTL") - if ttlValue == "" { - ttlValue = "72h" - } - duration, err := time.ParseDuration(ttlValue) - if err != nil { - panic(fmt.Sprintf("Please define a proper TTL for the test: %v", err)) - } - t, err := time.ParseDuration(*alias.ShortDur(duration)) - if err != nil { - panic(fmt.Sprintf("Please define a proper TTL for the test: %v", err)) - } - return t -} - -func getTestDuration() time.Duration { - testDurationValue := getEnv("TEST_DURATION") - if testDurationValue == "" { - return time.Duration(time.Minute * 15) - } - duration, err := time.ParseDuration(testDurationValue) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } - t, err := time.ParseDuration(*alias.ShortDur(duration)) - if err != nil { - panic(fmt.Sprintf("Please define a proper duration for the test: %v", err)) - } - return t -} - func (c *Common) DefaultNodeConfig() *cl.Config { starkConfig := config.TOMLConfig{ Enabled: ptr.Ptr(true), - ChainID: ptr.Ptr(c.ChainId), + ChainID: ptr.Ptr(c.ChainDetails.ChainId), Nodes: []*config.Node{ { Name: ptr.Ptr("primary"), - URL: common_cfg.MustParseURL(defaultNodeUrl), + URL: common_cfg.MustParseURL(c.RPCDetails.RPCL2Internal), }, }, } @@ -250,8 +160,6 @@ func (c *Common) SetLocalEnvironment(t *testing.T) { log.Info().Msg("Starting core nodes...") cmd := exec.Command("../../scripts/core.sh") cmd.Env = append(os.Environ(), fmt.Sprintf("CL_CONFIG=%s", c.ChainlinkConfig)) - // out, err := cmd.Output() - // fmt.Println(string(out)) err = cmd.Run() require.NoError(t, err, "Could not start core nodes") log.Info().Msg("Set up local stack complete.") @@ -259,7 +167,7 @@ func (c *Common) SetLocalEnvironment(t *testing.T) { // Set ChainlinkNodeDetails var nodeDetails []*environment.ChainlinkNodeDetail var basePort = 50100 - for i := 0; i < c.NodeCount; i++ { + for i := 0; i < *c.TestConfig.OCR2.NodeCount; i++ { dbLocalIP := fmt.Sprintf("postgresql://postgres:postgres@chainlink.postgres:5432/starknet_test_%d?sslmode=disable", i+1) nodeDetails = append(nodeDetails, &environment.ChainlinkNodeDetail{ ChartName: "unused", @@ -329,7 +237,7 @@ func (c *Common) CreateNodeKeysBundle(nodes []*client.ChainlinkClient) ([]client } peerID := p2pkeys.Data[0].Attributes.PeerID - txKey, _, err := n.CreateTxKey(chainName, c.ChainId) + txKey, _, err := n.CreateTxKey(chainName, c.ChainDetails.ChainId) if err != nil { return nil, err } @@ -348,12 +256,12 @@ func (c *Common) CreateNodeKeysBundle(nodes []*client.ChainlinkClient) ([]client } // CreateJobsForContract Creates and sets up the boostrap jobs as well as OCR jobs -func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test_env.Killgrave, observationSource string, juelsPerFeeCoinSource string, ocrControllerAddress string, accountAddresses []string) error { +func (c *Common) CreateJobsForContract(cc *ChainlinkClient, observationSource string, juelsPerFeeCoinSource string, ocrControllerAddress string, accountAddresses []string) error { // Define node[0] as bootstrap node cc.bootstrapPeers = []client.P2PData{ { InternalIP: cc.ChainlinkNodes[0].InternalIP(), - InternalPort: c.P2PPort, + InternalPort: c.RPCDetails.P2PPort, PeerID: cc.NKeys[0].PeerID, }, } @@ -362,7 +270,7 @@ func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test bootstrapRelayConfig := job.JSONConfig{ "nodeName": fmt.Sprintf("starknet-OCRv2-%s-%s", "node", uuid.New().String()), "accountAddress": fmt.Sprintf("%s", accountAddresses[0]), - "chainID": fmt.Sprintf("%s", c.ChainId), + "chainID": fmt.Sprintf("%s", c.ChainDetails.ChainId), } oracleSpec := job.OCR2OracleSpec{ @@ -377,7 +285,7 @@ func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test JobType: "bootstrap", OCR2OracleSpec: oracleSpec, } - + fmt.Println(jobSpec.String()) _, _, err := cc.ChainlinkNodes[0].CreateJob(jobSpec) if err != nil { return err @@ -391,7 +299,7 @@ func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test sourceValueBridge := &client.BridgeTypeAttributes{ Name: "mockserver-bridge", - URL: fmt.Sprintf("%s/%s", mockserver.InternalEndpoint, strings.TrimPrefix("mockserver-bridge", "/")), + URL: fmt.Sprintf("%s/%s", c.RPCDetails.MockServerEndpoint, strings.TrimPrefix(c.RPCDetails.MockServerUrl, "/")), } // Setting up job specs @@ -399,7 +307,7 @@ func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test if nIdx == 0 { continue } - _, err := n.CreateBridge(sourceValueBridge) + err := n.MustCreateBridge(sourceValueBridge) if err != nil { return err } @@ -429,6 +337,7 @@ func (c *Common) CreateJobsForContract(cc *ChainlinkClient, mockserver *ctf_test OCR2OracleSpec: oracleSpec, ObservationSource: observationSource, } + fmt.Println(jobSpec.String()) _, err = n.MustCreateJob(jobSpec) if err != nil { return err diff --git a/integration-tests/common/gauntlet_common.go b/integration-tests/common/gauntlet_common.go index f4556b77c..5ed4479d2 100644 --- a/integration-tests/common/gauntlet_common.go +++ b/integration-tests/common/gauntlet_common.go @@ -14,14 +14,14 @@ var ( ) func (m *OCRv2TestState) fundNodes() ([]string, error) { - l := utils.GetTestLogger(m.T) + l := utils.GetTestLogger(m.TestConfig.T) var nAccounts []string var err error for _, key := range m.GetNodeKeys() { if key.TXKey.Data.Attributes.StarkKey == "" { return nil, errors.New("stark key can't be empty") } - nAccount, err = m.Sg.DeployAccountContract(100, key.TXKey.Data.Attributes.StarkKey) + nAccount, err = m.Clients.GauntletClient.DeployAccountContract(100, key.TXKey.Data.Attributes.StarkKey) if err != nil { return nil, err } @@ -32,27 +32,35 @@ func (m *OCRv2TestState) fundNodes() ([]string, error) { return nil, err } - if m.Common.Testnet { + if *m.Common.TestConfig.Common.Network == "testnet" { for _, key := range nAccounts { // We are not deploying in parallel here due to testnet limitations (429 too many requests) l.Debug().Msg(fmt.Sprintf("Funding node with address: %s", key)) - _, err = m.Sg.TransferToken(ethAddressGoerli, key, "100000000000000000") // Transferring 1 ETH to each node + _, err = m.Clients.GauntletClient.TransferToken(ethAddressGoerli, key, "100000000000000000") // Transferring 1 ETH to each node if err != nil { return nil, err } } - } else { // The starknet provided mint method does not work so we send a req directly for _, key := range nAccounts { - res, err := m.Resty.R().SetBody(map[string]any{ + res, err := m.TestConfig.Resty.R().SetBody(map[string]any{ + "address": key, + "amount": 900000000000000000, + }).Post("/mint") + if err != nil { + return nil, err + } + l.Info().Msg(fmt.Sprintf("Funding account (WEI): %s", string(res.Body()))) + res, err = m.TestConfig.Resty.R().SetBody(map[string]any{ "address": key, "amount": 900000000000000000, + "unit": "FRI", }).Post("/mint") - m.L.Info().Msg(fmt.Sprintf("Funding account: %s", string(res.Body()))) if err != nil { return nil, err } + l.Info().Msg(fmt.Sprintf("Funding account (FRI): %s", string(res.Body()))) } } @@ -61,11 +69,11 @@ func (m *OCRv2TestState) fundNodes() ([]string, error) { func (m *OCRv2TestState) deployLinkToken() error { var err error - m.LinkTokenAddr, err = m.Sg.DeployLinkTokenContract() + m.Contracts.LinkTokenAddr, err = m.Clients.GauntletClient.DeployLinkTokenContract() if err != nil { return err } - err = os.Setenv("LINK", m.LinkTokenAddr) + err = os.Setenv("LINK", m.Contracts.LinkTokenAddr) if err != nil { return err } @@ -74,11 +82,11 @@ func (m *OCRv2TestState) deployLinkToken() error { func (m *OCRv2TestState) deployAccessController() error { var err error - m.AccessControllerAddr, err = m.Sg.DeployAccessControllerContract() + m.Contracts.AccessControllerAddr, err = m.Clients.GauntletClient.DeployAccessControllerContract() if err != nil { return err } - err = os.Setenv("BILLING_ACCESS_CONTROLLER", m.AccessControllerAddr) + err = os.Setenv("BILLING_ACCESS_CONTROLLER", m.Contracts.AccessControllerAddr) if err != nil { return err } @@ -95,17 +103,17 @@ func (m *OCRv2TestState) setConfigDetails(ocrAddress string) error { if err != nil { return err } - _, err = m.Sg.SetConfigDetails(string(parsedConfig), ocrAddress) + _, err = m.Clients.GauntletClient.SetConfigDetails(string(parsedConfig), ocrAddress) return err } func (m *OCRv2TestState) DeployGauntlet(minSubmissionValue int64, maxSubmissionValue int64, decimals int, name string, observationPaymentGjuels int64, transmissionPaymentGjuels int64) error { - err := m.Sg.InstallDependencies() + err := m.Clients.GauntletClient.InstallDependencies() if err != nil { return err } - m.AccountAddresses, err = m.fundNodes() + m.Clients.ChainlinkClient.AccountAddresses, err = m.fundNodes() if err != nil { return err } @@ -120,29 +128,29 @@ func (m *OCRv2TestState) DeployGauntlet(minSubmissionValue int64, maxSubmissionV return err } - m.OCRAddr, err = m.Sg.DeployOCR2ControllerContract(minSubmissionValue, maxSubmissionValue, decimals, name, m.LinkTokenAddr) + m.Contracts.OCRAddr, err = m.Clients.GauntletClient.DeployOCR2ControllerContract(minSubmissionValue, maxSubmissionValue, decimals, name, m.Contracts.LinkTokenAddr) if err != nil { return err } - m.ProxyAddr, err = m.Sg.DeployOCR2ProxyContract(m.OCRAddr) + m.Contracts.ProxyAddr, err = m.Clients.GauntletClient.DeployOCR2ProxyContract(m.Contracts.OCRAddr) if err != nil { return err } - _, err = m.Sg.AddAccess(m.OCRAddr, m.ProxyAddr) + _, err = m.Clients.GauntletClient.AddAccess(m.Contracts.OCRAddr, m.Contracts.ProxyAddr) if err != nil { return err } - _, err = m.Sg.MintLinkToken(m.LinkTokenAddr, m.OCRAddr, "100000000000000000000") + _, err = m.Clients.GauntletClient.MintLinkToken(m.Contracts.LinkTokenAddr, m.Contracts.OCRAddr, "100000000000000000000") if err != nil { return err } - _, err = m.Sg.SetOCRBilling(observationPaymentGjuels, transmissionPaymentGjuels, m.OCRAddr) + _, err = m.Clients.GauntletClient.SetOCRBilling(observationPaymentGjuels, transmissionPaymentGjuels, m.Contracts.OCRAddr) if err != nil { return err } - err = m.setConfigDetails(m.OCRAddr) + err = m.setConfigDetails(m.Contracts.OCRAddr) return err } diff --git a/integration-tests/common/test_common.go b/integration-tests/common/test_common.go index 224c07f8e..a87d04a23 100644 --- a/integration-tests/common/test_common.go +++ b/integration-tests/common/test_common.go @@ -5,16 +5,16 @@ import ( "fmt" starknetdevnet "github.com/NethermindEth/starknet.go/devnet" "github.com/go-resty/resty/v2" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/smartcontractkit/chainlink-common/pkg/logger" test_env_ctf "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" - "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "net/http" - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" test_env_starknet "github.com/smartcontractkit/chainlink-starknet/integration-tests/docker/test_env" + "github.com/smartcontractkit/chainlink-starknet/integration-tests/testconfig" "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink/integration-tests/testconfig" + "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "math/big" "testing" "time" @@ -37,55 +37,93 @@ var ( dumpPath = "/dumps/dump.pkl" ) +// OCRv2TestState Main testing state struct type OCRv2TestState struct { - Account string - PrivateKey string - StarknetClient *starknet.Client - DevnetClient *starknetdevnet.DevNet - Killgrave *test_env_ctf.Killgrave - ChainlinkNodesK8s []*client.ChainlinkK8sClient - Cc *ChainlinkClient - OCR2Client *ocr2.Client - Sg *gauntlet.StarknetGauntlet - L1RPCUrl string - Common *Common - AccountAddresses []string + Account *AccountDetails + Clients *Clients + ChainlinkNodesK8s []*client.ChainlinkK8sClient + Common *Common + TestConfig *TestConfig + Contracts *Contracts +} + +// AccountDetails for deployment and funding +type AccountDetails struct { + Account string + PrivateKey string +} + +// Clients to access internal methods +type Clients struct { + StarknetClient *starknet.Client + DevnetClient *starknetdevnet.DevNet + KillgraveClient *test_env_ctf.Killgrave + OCR2Client *ocr2.Client + ChainlinkClient *ChainlinkClient + GauntletClient *gauntlet.StarknetGauntlet + DockerEnv *StarknetClusterTestEnv +} + +// Contracts to store current deployed contract state +type Contracts struct { LinkTokenAddr string OCRAddr string AccessControllerAddr string ProxyAddr string ObservationSource string JuelsPerFeeCoinSource string - T *testing.T - L zerolog.Logger - TestConfig *testconfig.TestConfig - Resty *resty.Client - err error } +// ChainlinkClient core node configs type ChainlinkClient struct { - NKeys []client.NodeKeysBundle - ChainlinkNodes []*client.ChainlinkClient - bTypeAttr *client.BridgeTypeAttributes - bootstrapPeers []client.P2PData + NKeys []client.NodeKeysBundle + ChainlinkNodes []*client.ChainlinkClient + bTypeAttr *client.BridgeTypeAttributes + bootstrapPeers []client.P2PData + AccountAddresses []string } -func NewOCRv2State(t *testing.T, env string, isK8s bool, namespacePrefix string, testConfig *testconfig.TestConfig) (*OCRv2TestState, error) { - c, err := New(env, isK8s).Default(t, namespacePrefix) +type StarknetClusterTestEnv struct { + *test_env.CLClusterTestEnv + Starknet *test_env_starknet.Starknet + Killgrave *test_env_ctf.Killgrave +} + +type TestConfig struct { + T *testing.T + L zerolog.Logger + TestConfig *testconfig.TestConfig + Resty *resty.Client + err error +} + +func NewOCRv2State(t *testing.T, namespacePrefix string, testConfig *testconfig.TestConfig) (*OCRv2TestState, error) { + c, err := New(testConfig).Default(t, namespacePrefix) if err != nil { return nil, err } state := &OCRv2TestState{ - Common: c, - T: t, - L: log.Logger, - TestConfig: testConfig, - Cc: &ChainlinkClient{}, - StarknetClient: &starknet.Client{}, + Account: &AccountDetails{}, + Clients: &Clients{ + ChainlinkClient: &ChainlinkClient{}, + }, + Common: c, + TestConfig: &TestConfig{ + T: t, + L: log.Logger, + TestConfig: testConfig, + Resty: nil, + err: nil, + }, + Contracts: &Contracts{}, } - if state.T != nil { - state.L = logging.GetTestLogger(state.T) + // Setting default job configs + state.Contracts.ObservationSource = state.GetDefaultObservationSource() + state.Contracts.JuelsPerFeeCoinSource = state.GetDefaultJuelsPerFeeCoinSource() + + if state.TestConfig.T != nil { + state.TestConfig.L = logging.GetTestLogger(state.TestConfig.T) } return state, nil @@ -93,76 +131,95 @@ func NewOCRv2State(t *testing.T, env string, isK8s bool, namespacePrefix string, // DeployCluster Deploys and sets up config of the environment and nodes func (m *OCRv2TestState) DeployCluster() { - if m.Common.IsK8s { + // When running soak we need to use K8S + if *m.Common.TestConfig.Common.InsideK8s { m.DeployEnv() - } else { + // Setting RPC details + m.Common.RPCDetails.RPCL2External = m.Common.Env.URLs["starknet-dev"][0] + m.Common.RPCDetails.MockServerEndpoint = m.Common.Env.URLs["qa_mock_adapter_internal"][0] + m.Common.RPCDetails.MockServerUrl = "five" + + } else { // Otherwise use docker env, err := test_env.NewTestEnv() - require.NoError(m.T, err) + require.NoError(m.TestConfig.T, err) stark := test_env_starknet.NewStarknet([]string{env.Network.Name}) err = stark.StartContainer() - require.NoError(m.T, err) - m.Common.L2RPCUrl = stark.ExternalHttpUrl - m.Resty = resty.New().SetBaseURL(m.Common.L2RPCUrl) + require.NoError(m.TestConfig.T, err) + + // Setting RPC details + m.Common.RPCDetails.RPCL2External = stark.ExternalHttpUrl + m.Common.RPCDetails.RPCL2Internal = stark.InternalHttpUrl + + // Creating docker containers b, err := test_env.NewCLTestEnvBuilder(). WithNonEVM(). - WithTestInstance(m.T). - WithTestConfig(m.TestConfig). + WithTestInstance(m.TestConfig.T). + WithTestConfig(m.TestConfig.TestConfig). WithMockAdapter(). WithCLNodeConfig(m.Common.DefaultNodeConfig()). - WithCLNodes(m.Common.NodeCount). - WithCLNodeOptions(m.Common.NodeOpts...). + WithCLNodes(*m.Common.TestConfig.OCR2.NodeCount). + WithCLNodeOptions(m.Common.TestEnvDetails.NodeOpts...). WithStandardCleanup(). WithTestEnv(env) - require.NoError(m.T, err) + require.NoError(m.TestConfig.T, err) env, err = b.Build() - require.NoError(m.T, err) - m.Common.DockerEnv = &StarknetClusterTestEnv{ + require.NoError(m.TestConfig.T, err) + m.Clients.DockerEnv = &StarknetClusterTestEnv{ CLClusterTestEnv: env, Starknet: stark, Killgrave: env.MockAdapter, } - m.Killgrave = env.MockAdapter + + // Setting up Mock adapter + m.Clients.KillgraveClient = env.MockAdapter + m.Common.RPCDetails.MockServerEndpoint = m.Clients.KillgraveClient.InternalEndpoint + m.Common.RPCDetails.MockServerUrl = "mockserver-bridge" + err = m.Clients.KillgraveClient.SetAdapterBasedIntValuePath("/mockserver-bridge", []string{http.MethodGet, http.MethodPost}, 10) + require.NoError(m.TestConfig.T, err, "Failed to set mock adapter value") } - m.ObservationSource = m.GetDefaultObservationSource() - m.JuelsPerFeeCoinSource = m.GetDefaultJuelsPerFeeCoinSource() + m.TestConfig.Resty = resty.New().SetBaseURL(m.Common.RPCDetails.RPCL2External) m.SetupClients() - if m.Common.IsK8s { - m.Cc.NKeys, m.err = m.Common.CreateNodeKeysBundle(m.GetChainlinkNodes()) - require.NoError(m.T, m.err) + if *m.Common.TestConfig.Common.InsideK8s { + m.Clients.ChainlinkClient.ChainlinkNodes = m.GetChainlinkNodes() + m.Clients.ChainlinkClient.NKeys, m.TestConfig.err = m.Common.CreateNodeKeysBundle(m.Clients.ChainlinkClient.ChainlinkNodes) + require.NoError(m.TestConfig.T, m.TestConfig.err) } else { - m.Cc.NKeys, m.err = m.Common.CreateNodeKeysBundle(m.Common.DockerEnv.ClCluster.NodeAPIs()) - require.NoError(m.T, m.err) + m.Clients.ChainlinkClient.NKeys, m.TestConfig.err = m.Common.CreateNodeKeysBundle(m.Clients.DockerEnv.ClCluster.NodeAPIs()) + require.NoError(m.TestConfig.T, m.TestConfig.err) } lggr := logger.Nop() - m.StarknetClient, m.err = starknet.NewClient(m.Common.ChainId, m.Common.L2RPCUrl, lggr, &rpcRequestTimeout) - require.NoError(m.T, m.err, "Creating starknet client should not fail") - m.OCR2Client, m.err = ocr2.NewClient(m.StarknetClient, lggr) - require.NoError(m.T, m.err, "Creating ocr2 client should not fail") - if !m.Common.Testnet { + m.Clients.StarknetClient, m.TestConfig.err = starknet.NewClient(m.Common.ChainDetails.ChainId, m.Common.RPCDetails.RPCL2External, lggr, &rpcRequestTimeout) + require.NoError(m.TestConfig.T, m.TestConfig.err, "Creating starknet client should not fail") + m.Clients.OCR2Client, m.TestConfig.err = ocr2.NewClient(m.Clients.StarknetClient, lggr) + require.NoError(m.TestConfig.T, m.TestConfig.err, "Creating ocr2 client should not fail") + + // If we are using devnet fetch the default keys + if *m.Common.TestConfig.Common.Network == "localnet" { // fetch predeployed account 0 to use as funder - m.DevnetClient = starknetdevnet.NewDevNet(m.Common.L2RPCUrl) - accounts, err := m.DevnetClient.Accounts() - require.NoError(m.T, err) + m.Clients.DevnetClient = starknetdevnet.NewDevNet(m.Common.RPCDetails.RPCL2External) + accounts, err := m.Clients.DevnetClient.Accounts() + require.NoError(m.TestConfig.T, err) account := accounts[0] - m.Account = account.Address - m.PrivateKey = account.PrivateKey + m.Account.Account = account.Address + m.Account.PrivateKey = account.PrivateKey } } // DeployEnv Deploys the environment func (m *OCRv2TestState) DeployEnv() { err := m.Common.Env.Run() - require.NoError(m.T, err) + require.NoError(m.TestConfig.T, err) } // SetupClients Sets up the starknet client func (m *OCRv2TestState) SetupClients() { - if m.Common.IsK8s { - m.ChainlinkNodesK8s, m.err = client.ConnectChainlinkNodes(m.Common.Env) - require.NoError(m.T, m.err) + + if *m.Common.TestConfig.Common.InsideK8s { + m.ChainlinkNodesK8s, m.TestConfig.err = client.ConnectChainlinkNodes(m.Common.Env) + require.NoError(m.TestConfig.T, m.TestConfig.err) } else { - m.Cc.ChainlinkNodes = m.Common.DockerEnv.ClCluster.NodeAPIs() + m.Clients.ChainlinkClient.ChainlinkNodes = m.Clients.DockerEnv.ClCluster.NodeAPIs() } } @@ -173,10 +230,10 @@ func (m *OCRv2TestState) LoadOCR2Config() (*ops.OCR2Config, error) { var peerIds []string var txKeys []string var cfgKeys []string - for i, key := range m.Cc.NKeys { + for i, key := range m.Clients.ChainlinkClient.NKeys { offChaiNKeys = append(offChaiNKeys, key.OCR2Key.Data.Attributes.OffChainPublicKey) peerIds = append(peerIds, key.PeerID) - txKeys = append(txKeys, m.AccountAddresses[i]) + txKeys = append(txKeys, m.Clients.ChainlinkClient.AccountAddresses[i]) onChaiNKeys = append(onChaiNKeys, key.OCR2Key.Data.Attributes.OnChainPublicKey) cfgKeys = append(cfgKeys, key.OCR2Key.Data.Attributes.ConfigPublicKey) } @@ -192,25 +249,30 @@ func (m *OCRv2TestState) LoadOCR2Config() (*ops.OCR2Config, error) { } func (m *OCRv2TestState) SetUpNodes() { - err := m.Common.CreateJobsForContract(m.GetChainlinkClient(), m.Killgrave, m.ObservationSource, m.JuelsPerFeeCoinSource, m.OCRAddr, m.AccountAddresses) - require.NoError(m.T, err, "Creating jobs should not fail") + err := m.Common.CreateJobsForContract(m.GetChainlinkClient(), m.Contracts.ObservationSource, m.Contracts.JuelsPerFeeCoinSource, m.Contracts.OCRAddr, m.Clients.ChainlinkClient.AccountAddresses) + require.NoError(m.TestConfig.T, err, "Creating jobs should not fail") } // GetNodeKeys Returns the node key bundles func (m *OCRv2TestState) GetNodeKeys() []client.NodeKeysBundle { - return m.Cc.NKeys + return m.Clients.ChainlinkClient.NKeys } func (m *OCRv2TestState) GetChainlinkNodes() []*client.ChainlinkClient { - return m.Cc.ChainlinkNodes + // retrieve client from K8s client + var chainlinkNodes []*client.ChainlinkClient + for i := range m.ChainlinkNodesK8s { + chainlinkNodes = append(chainlinkNodes, m.ChainlinkNodesK8s[i].ChainlinkClient) + } + return chainlinkNodes } func (m *OCRv2TestState) GetChainlinkClient() *ChainlinkClient { - return m.Cc + return m.Clients.ChainlinkClient } func (m *OCRv2TestState) SetBridgeTypeAttrs(attr *client.BridgeTypeAttributes) { - m.Cc.bTypeAttr = attr + m.Clients.ChainlinkClient.bTypeAttr = attr } func (m *OCRv2TestState) GetDefaultObservationSource() string { @@ -239,25 +301,25 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { var positive bool // validate balance in aggregator - linkContractAddress, err := starknetutils.HexToFelt(m.LinkTokenAddr) + linkContractAddress, err := starknetutils.HexToFelt(m.Contracts.LinkTokenAddr) if err != nil { return err } - contractAddress, err := starknetutils.HexToFelt(m.OCRAddr) + contractAddress, err := starknetutils.HexToFelt(m.Contracts.OCRAddr) if err != nil { return err } - resLINK, errLINK := m.StarknetClient.CallContract(ctx, starknet.CallOps{ + resLINK, errLINK := m.Clients.StarknetClient.CallContract(ctx, starknet.CallOps{ ContractAddress: linkContractAddress, Selector: starknetutils.GetSelectorFromNameFelt("balance_of"), Calldata: []*felt.Felt{contractAddress}, }) - require.NoError(m.T, errLINK, "Reader balance from LINK contract should not fail", "err", errLINK) - resAgg, errAgg := m.StarknetClient.CallContract(ctx, starknet.CallOps{ + require.NoError(m.TestConfig.T, errLINK, "Reader balance from LINK contract should not fail", "err", errLINK) + resAgg, errAgg := m.Clients.StarknetClient.CallContract(ctx, starknet.CallOps{ ContractAddress: contractAddress, Selector: starknetutils.GetSelectorFromNameFelt("link_available_for_payment"), }) - require.NoError(m.T, errAgg, "link_available_for_payment should not fail", "err", errAgg) + require.NoError(m.TestConfig.T, errAgg, "link_available_for_payment should not fail", "err", errAgg) balLINK := resLINK[0].BigInt(big.NewInt(0)) balAgg := resAgg[1].BigInt(big.NewInt(0)) isNegative := resAgg[0].BigInt(big.NewInt(0)) @@ -265,15 +327,13 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { balAgg = new(big.Int).Neg(balAgg) } - assert.Equal(m.T, balLINK.Cmp(big.NewInt(0)), 1, "Aggregator should have non-zero balance") - assert.GreaterOrEqual(m.T, balLINK.Cmp(balAgg), 0, "Aggregator payment balance should be <= actual LINK balance") + assert.Equal(m.TestConfig.T, balLINK.Cmp(big.NewInt(0)), 1, "Aggregator should have non-zero balance") + assert.GreaterOrEqual(m.TestConfig.T, balLINK.Cmp(balAgg), 0, "Aggregator payment balance should be <= actual LINK balance") - err = m.Killgrave.SetAdapterBasedIntValuePath("/mockserver-bridge", []string{http.MethodGet, http.MethodPost}, 10) - require.NoError(m.T, err, "Failed to set mock adapter value") - for start := time.Now(); time.Since(start) < m.Common.TestDuration; { - m.L.Info().Msg(fmt.Sprintf("Elapsed time: %s, Round wait: %s ", time.Since(start), m.Common.TestDuration)) - res, err2 := m.OCR2Client.LatestTransmissionDetails(ctx, contractAddress) - require.NoError(m.T, err2, "Failed to get latest transmission details") + for start := time.Now(); time.Since(start) < m.Common.TestEnvDetails.TestDuration; { + m.TestConfig.L.Info().Msg(fmt.Sprintf("Elapsed time: %s, Round wait: %s ", time.Since(start), m.Common.TestEnvDetails.TestDuration)) + res, err2 := m.Clients.OCR2Client.LatestTransmissionDetails(ctx, contractAddress) + require.NoError(m.TestConfig.T, err2, "Failed to get latest transmission details") // end condition: enough rounds have occurred if !isSoak && increasing >= rounds && positive { break @@ -281,7 +341,7 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { // end condition: rounds have been stuck if stuck && stuckCount > 50 { - m.L.Debug().Msg("failing to fetch transmissions means blockchain may have stopped") + m.TestConfig.L.Debug().Msg("failing to fetch transmissions means blockchain may have stopped") break } @@ -289,10 +349,10 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { time.Sleep(5 * time.Second) if err != nil { - m.L.Error().Msg(fmt.Sprintf("Transmission Error: %+v", err)) + m.TestConfig.L.Error().Msg(fmt.Sprintf("Transmission Error: %+v", err)) continue } - m.L.Info().Msg(fmt.Sprintf("Transmission Details: %+v", res)) + m.TestConfig.L.Info().Msg(fmt.Sprintf("Transmission Details: %+v", res)) // continue if no changes if res.Epoch == 0 && res.Round == 0 { @@ -305,21 +365,21 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { // if changes from zero values set (should only initially) if res.Epoch > 0 && details.Epoch == 0 { if !isSoak { - assert.Greater(m.T, res.Epoch, details.Epoch) - assert.GreaterOrEqual(m.T, res.Round, details.Round) - assert.NotEqual(m.T, ansCmp, 0) // assert changed from 0 - assert.NotEqual(m.T, res.Digest, details.Digest) - assert.Equal(m.T, details.LatestTimestamp.Before(res.LatestTimestamp), true) + assert.Greater(m.TestConfig.T, res.Epoch, details.Epoch) + assert.GreaterOrEqual(m.TestConfig.T, res.Round, details.Round) + assert.NotEqual(m.TestConfig.T, ansCmp, 0) // assert changed from 0 + assert.NotEqual(m.TestConfig.T, res.Digest, details.Digest) + assert.Equal(m.TestConfig.T, details.LatestTimestamp.Before(res.LatestTimestamp), true) } details = res continue } // check increasing rounds if !isSoak { - assert.Equal(m.T, res.Digest, details.Digest, "Config digest should not change") + assert.Equal(m.TestConfig.T, res.Digest, details.Digest, "Config digest should not change") } else { if res.Digest != details.Digest { - m.L.Error().Msg(fmt.Sprintf("Config digest should not change, expected %s got %s", details.Digest, res.Digest)) + m.TestConfig.L.Error().Msg(fmt.Sprintf("Config digest should not change, expected %s got %s", details.Digest, res.Digest)) } } if (res.Epoch > details.Epoch || (res.Epoch == details.Epoch && res.Round > details.Round)) && details.LatestTimestamp.Before(res.LatestTimestamp) { @@ -337,31 +397,31 @@ func (m *OCRv2TestState) ValidateRounds(rounds int, isSoak bool) error { } } if !isSoak { - assert.GreaterOrEqual(m.T, increasing, rounds, "Round + epochs should be increasing") - assert.Equal(m.T, positive, true, "Positive value should have been submitted") - assert.Equal(m.T, stuck, false, "Round + epochs should not be stuck") + assert.GreaterOrEqual(m.TestConfig.T, increasing, rounds, "Round + epochs should be increasing") + assert.Equal(m.TestConfig.T, positive, true, "Positive value should have been submitted") + assert.Equal(m.TestConfig.T, stuck, false, "Round + epochs should not be stuck") } // Test proxy reading // TODO: would be good to test proxy switching underlying feeds - proxyAddress, err := starknetutils.HexToFelt(m.ProxyAddr) + proxyAddress, err := starknetutils.HexToFelt(m.Contracts.ProxyAddr) if err != nil { return err } - roundDataRaw, err := m.StarknetClient.CallContract(ctx, starknet.CallOps{ + roundDataRaw, err := m.Clients.StarknetClient.CallContract(ctx, starknet.CallOps{ ContractAddress: proxyAddress, Selector: starknetutils.GetSelectorFromNameFelt("latest_round_data"), }) if !isSoak { - require.NoError(m.T, err, "Reading round data from proxy should not fail") - assert.Equal(m.T, len(roundDataRaw), 5, "Round data from proxy should match expected size") + require.NoError(m.TestConfig.T, err, "Reading round data from proxy should not fail") + assert.Equal(m.TestConfig.T, len(roundDataRaw), 5, "Round data from proxy should match expected size") } valueBig := roundDataRaw[1].BigInt(big.NewInt(0)) - require.NoError(m.T, err) + require.NoError(m.TestConfig.T, err) value := valueBig.Int64() if value < 0 { - assert.Equal(m.T, value, int64(5), "Reading from proxy should return correct value") + assert.Equal(m.TestConfig.T, value, int64(5), "Reading from proxy should return correct value") } return nil diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index fd7e91557..846dc782e 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -3,16 +3,17 @@ package smoke_test import ( "flag" "fmt" + "github.com/smartcontractkit/chainlink-starknet/integration-tests/common" + tc "github.com/smartcontractkit/chainlink-starknet/integration-tests/testconfig" "github.com/smartcontractkit/chainlink-starknet/ops/gauntlet" "github.com/smartcontractkit/chainlink-starknet/ops/utils" - "maps" - "testing" - - "github.com/smartcontractkit/chainlink-starknet/integration-tests/common" "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink/integration-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + "maps" + "testing" ) var ( @@ -35,6 +36,7 @@ func TestOCRBasicNew(t *testing.T) { // "CL_SOLANA_CMD": "chainlink-solana", //}}, } { + config, err := tc.GetConfig("Smoke", tc.OCR2) if err != nil { t.Fatal(err) @@ -43,10 +45,19 @@ func TestOCRBasicNew(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() logging.Init() - state, err := common.NewOCRv2State(t, "localnet", false, "test", &config) + state, err := common.NewOCRv2State(t, "test", &config) require.NoError(t, err, "Could not setup the ocrv2 state") + + // Cleanup K8s + if *config.Common.InsideK8s { + t.Cleanup(func() { + if err := actions.TeardownSuite(t, state.Common.Env, state.ChainlinkNodesK8s, nil, zapcore.PanicLevel, nil); err != nil { + state.TestConfig.L.Error().Err(err).Msg("Error tearing down environment") + } + }) + } if len(test.env) > 0 { - state.Common.NodeOpts = append(state.Common.NodeOpts, func(n *test_env.ClNode) { + state.Common.TestEnvDetails.NodeOpts = append(state.Common.TestEnvDetails.NodeOpts, func(n *test_env.ClNode) { if n.ContainerEnvs == nil { n.ContainerEnvs = map[string]string{} } @@ -54,15 +65,15 @@ func TestOCRBasicNew(t *testing.T) { }) } state.DeployCluster() - state.Sg, err = gauntlet.NewStarknetGauntlet(fmt.Sprintf("%s/", utils.ProjectRoot)) - err = state.Sg.SetupNetwork(state.Common.L2RPCUrl, state.Account, state.PrivateKey) + state.Clients.GauntletClient, err = gauntlet.NewStarknetGauntlet(fmt.Sprintf("%s/", utils.ProjectRoot)) + err = state.Clients.GauntletClient.SetupNetwork(state.Common.RPCDetails.RPCL2External, state.Account.Account, state.Account.PrivateKey) require.NoError(t, err, "Setting up gauntlet network should not fail") err = state.DeployGauntlet(0, 100000000000, decimals, "auto", 1, 1) require.NoError(t, err, "Deploying contracts should not fail") state.SetUpNodes() - err = state.ValidateRounds(10, false) + err = state.ValidateRounds(*config.OCR2.Smoke.NumberOfRounds, false) require.NoError(t, err, "Validating round should not fail") }) } diff --git a/integration-tests/testconfig/configs_embed.go b/integration-tests/testconfig/configs_embed.go index e6950abe2..67e954ff5 100644 --- a/integration-tests/testconfig/configs_embed.go +++ b/integration-tests/testconfig/configs_embed.go @@ -1 +1,21 @@ +//go:build embed +// +build embed + package testconfig + +import "embed" + +//go:embed default.toml +//go:embed automation/automation.toml +//go:embed functions/functions.toml +//go:embed keeper/keeper.toml +//go:embed log_poller/log_poller.toml +//go:embed node/node.toml +//go:embed ocr/ocr.toml +//go:embed vrfv2/vrfv2.toml +//go:embed vrfv2plus/vrfv2plus.toml +var embeddedConfigsFs embed.FS + +func init() { + areConfigsEmbedded = true +} diff --git a/integration-tests/testconfig/configs_noembed.go b/integration-tests/testconfig/configs_noembed.go index 67e954ff5..95572c4a0 100644 --- a/integration-tests/testconfig/configs_noembed.go +++ b/integration-tests/testconfig/configs_noembed.go @@ -1,21 +1,12 @@ -//go:build embed -// +build embed +//go:build !embed +// +build !embed package testconfig import "embed" -//go:embed default.toml -//go:embed automation/automation.toml -//go:embed functions/functions.toml -//go:embed keeper/keeper.toml -//go:embed log_poller/log_poller.toml -//go:embed node/node.toml -//go:embed ocr/ocr.toml -//go:embed vrfv2/vrfv2.toml -//go:embed vrfv2plus/vrfv2plus.toml var embeddedConfigsFs embed.FS func init() { - areConfigsEmbedded = true + areConfigsEmbedded = false } diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index a65c23d70..10eb6f84e 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -7,16 +7,21 @@ log_producer_timeout="10s" log_producer_retry_limit=10 [Network] -selected_networks=["simulated"] +selected_networks=["SIMULATED"] # Not needed for Starknet but mandatory from CTF -[PrivateEthereumNetwork] -consensus_type="pow" -execution_layer="geth" +[Network.RpcHttpUrls] +simulated = ["http://127.0.0.1"] # Not needed for Starknet but mandatory from CTF -[PrivateEthereumNetwork.EthereumChainConfig] -seconds_per_slot=3 -slots_per_epoch=2 -genesis_delay=15 -validator_count=4 -chain_id=1337 -addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] \ No newline at end of file +[Network.RpcWsUrls] +simulated = ["wss://127.0.0.1"] # Not needed for Starknet but mandatory from CTF + +[Common] +inside_k8 = false +network = "localnet" + +[OCR2] +node_count = 6 +test_duration = "30m" + +[OCR2.Smoke] +number_of_rounds = 10 diff --git a/integration-tests/testconfig/ocr2/ocr2.go b/integration-tests/testconfig/ocr2/ocr2.go index d5cc48ea5..30438ba38 100644 --- a/integration-tests/testconfig/ocr2/ocr2.go +++ b/integration-tests/testconfig/ocr2/ocr2.go @@ -2,135 +2,40 @@ package ocr2 import ( "errors" - - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" ) type Config struct { - Soak *SoakConfig `toml:"Soak"` - Load *Load `toml:"Load"` - Volume *Volume `toml:"Volume"` - Common *Common `toml:"Common"` + Smoke *SmokeConfig `toml:"Smoke"` + NodeCount *int `toml:"node_count"` + TestDuration *string `toml:"test_duration"` } func (o *Config) Validate() error { - if o.Common != nil { - if err := o.Common.Validate(); err != nil { - return err - } - } - if o.Soak != nil { - if err := o.Soak.Validate(); err != nil { - return err - } - } - if o.Volume != nil { - if err := o.Volume.Validate(); err != nil { - return err - } - } - return nil -} - -type Common struct { - ETHFunds *int `toml:"eth_funds"` - TestDuration *blockchain.StrDuration `toml:"test_duration"` -} - -func (o *Common) Validate() error { - if o.ETHFunds != nil && *o.ETHFunds < 0 { - return errors.New("eth_funds must be set and cannot be negative") + if o.NodeCount != nil && *o.NodeCount < 3 { + return errors.New("node_count must be set and cannot be less than 3") } - return nil -} - -type Load struct { - Rate *int64 `toml:"rate"` - RequestsPerUnit *int `toml:"requests_per_unit"` - RateLimitUnitDuration *blockchain.StrDuration `toml:"rate_limit_unit_duration"` - VerificationInterval *blockchain.StrDuration `toml:"verification_interval"` - VerificationTimeout *blockchain.StrDuration `toml:"verification_timeout"` - EAChangeInterval *blockchain.StrDuration `toml:"ea_change_interval"` - TestDuration *blockchain.StrDuration `toml:"test_duration"` -} -func (o *Load) Validate() error { if o.TestDuration == nil { - return errors.New("load test duration must be set") - } - if o.Rate == nil || *o.Rate <= 0 { - return errors.New("rate must be set and be a positive integer") - } - if o.RequestsPerUnit == nil || *o.RequestsPerUnit <= 0 { - return errors.New("vu_requests_per_unit must be set and be a positive integer") - } - if o.RateLimitUnitDuration == nil || o.RateLimitUnitDuration.Duration == 0 { - return errors.New("rate_limit_unit_duration must be set and be a positive integer") + return errors.New("test_duration must be set") } - if o.VerificationInterval == nil || o.VerificationInterval.Duration == 0 { - return errors.New("verification_interval must be set and be a positive integer") - } - if o.VerificationTimeout == nil || o.VerificationTimeout.Duration == 0 { - return errors.New("verification_timeout must be set and be a positive integer") - } - if o.EAChangeInterval == nil || o.EAChangeInterval.Duration == 0 { - return errors.New("ea_change_interval must be set and be a positive integer") - } - - return nil -} - -type Volume struct { - Rate *int64 `toml:"rate"` - VURequestsPerUnit *int `toml:"vu_requests_per_unit"` - RateLimitUnitDuration *blockchain.StrDuration `toml:"rate_limit_unit_duration"` - VerificationInterval *blockchain.StrDuration `toml:"verification_interval"` - VerificationTimeout *blockchain.StrDuration `toml:"verification_timeout"` - EAChangeInterval *blockchain.StrDuration `toml:"ea_change_interval"` - TestDuration *blockchain.StrDuration `toml:"test_duration"` -} -func (o *Volume) Validate() error { - if o.TestDuration == nil { - return errors.New("volume test duration must be set") - } - if o.Rate == nil || *o.Rate <= 0 { - return errors.New("rate must be set and be a positive integer") - } - if o.VURequestsPerUnit == nil || *o.VURequestsPerUnit <= 0 { - return errors.New("vu_requests_per_unit must be set and be a positive integer") - } - if o.RateLimitUnitDuration == nil || o.RateLimitUnitDuration.Duration == 0 { - return errors.New("rate_limit_unit_duration must be set and be a positive integer") + if o.Smoke == nil { + return errors.New("smoke must be defined") } - if o.VerificationInterval == nil || o.VerificationInterval.Duration == 0 { - return errors.New("verification_interval must be set and be a positive integer") - } - if o.VerificationTimeout == nil || o.VerificationTimeout.Duration == 0 { - return errors.New("verification_timeout must be set and be a positive integer") - } - if o.EAChangeInterval == nil || o.EAChangeInterval.Duration == 0 { - return errors.New("ea_change_interval must be set and be a positive integer") + if err := o.Smoke.Validate(); err != nil { + return err } return nil } -type SoakConfig struct { - OCRVersion *string `toml:"ocr_version"` - NumberOfContracts *int `toml:"number_of_contracts"` - TimeBetweenRounds *blockchain.StrDuration `toml:"time_between_rounds"` +type SmokeConfig struct { + NumberOfRounds *int `toml:"number_of_rounds"` } -func (o *SoakConfig) Validate() error { - if o.OCRVersion == nil || *o.OCRVersion == "" { - return errors.New("ocr_version must be set to either 1 or 2") - } - if o.NumberOfContracts == nil || *o.NumberOfContracts <= 1 { - return errors.New("number_of_contracts must be set and be greater than 1") - } - if o.TimeBetweenRounds == nil || o.TimeBetweenRounds.Duration == 0 { - return errors.New("time_between_rounds must be set and be a positive integer") +func (o *SmokeConfig) Validate() error { + if o.NumberOfRounds == nil { + return errors.New("number_of_rounds must be set") } return nil } diff --git a/integration-tests/testconfig/ocr2/ocr2.toml b/integration-tests/testconfig/ocr2/ocr2.toml index e69de29bb..9d1493a89 100644 --- a/integration-tests/testconfig/ocr2/ocr2.toml +++ b/integration-tests/testconfig/ocr2/ocr2.toml @@ -0,0 +1,3 @@ +[Common] +node_count = 6 +test_duration = "30m" \ No newline at end of file diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go index e6950abe2..603fa6ebf 100644 --- a/integration-tests/testconfig/testconfig.go +++ b/integration-tests/testconfig/testconfig.go @@ -1 +1,360 @@ package testconfig + +import ( + "embed" + "encoding/base64" + "fmt" + "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + "os" + "strings" + + "github.com/barkimedes/go-deepcopy" + "github.com/google/uuid" + "github.com/pelletier/go-toml/v2" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + ocr2_config "github.com/smartcontractkit/chainlink-starknet/integration-tests/testconfig/ocr2" + ctf_config "github.com/smartcontractkit/chainlink-testing-framework/config" + k8s_config "github.com/smartcontractkit/chainlink-testing-framework/k8s/config" + "github.com/smartcontractkit/chainlink-testing-framework/logging" + "github.com/smartcontractkit/chainlink-testing-framework/utils/osutil" +) + +type GlobalTestConfig interface { + GetChainlinkImageConfig() *ctf_config.ChainlinkImageConfig + GetLoggingConfig() *ctf_config.LoggingConfig + GetNetworkConfig() *ctf_config.NetworkConfig +} + +type UpgradeableChainlinkTestConfig interface { + GetChainlinkUpgradeImageConfig() *ctf_config.ChainlinkImageConfig +} + +type CommonTestConfig interface { + GetCommonConfig() *Common +} + +type Ocr2TestConfig interface { + GetOCR2Config() *ocr2_config.Config +} + +type NamedConfiguration interface { + GetConfigurationName() string +} + +type TestConfig struct { + ChainlinkImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkImage"` + Logging *ctf_config.LoggingConfig `toml:"Logging"` + ChainlinkUpgradeImage *ctf_config.ChainlinkImageConfig `toml:"ChainlinkUpgradeImage"` + Network *ctf_config.NetworkConfig `toml:"Network"` + Common *Common `toml:"Common"` + OCR2 *ocr2_config.Config `toml:"OCR2"` + ConfigurationName string `toml:"-"` +} + +func (c *TestConfig) GetLoggingConfig() *ctf_config.LoggingConfig { + return c.Logging +} + +func (c *TestConfig) GetPrivateEthereumNetworkConfig() *test_env.EthereumNetwork { + //TODO implement me + return nil +} + +func (c *TestConfig) GetPyroscopeConfig() *ctf_config.PyroscopeConfig { + //TODO implement me + return nil +} + +var embeddedConfigs embed.FS +var areConfigsEmbedded bool + +func init() { + embeddedConfigs = embeddedConfigsFs +} + +// Saves Test Config to a local file +func (c *TestConfig) Save() (string, error) { + filePath := fmt.Sprintf("test_config-%s.toml", uuid.New()) + + content, err := toml.Marshal(*c) + if err != nil { + return "", errors.Wrapf(err, "error marshaling test config") + } + + err = os.WriteFile(filePath, content, 0600) + if err != nil { + return "", errors.Wrapf(err, "error writing test config") + } + + return filePath, nil +} + +// MustCopy Returns a deep copy of the Test Config or panics on error +func (c TestConfig) MustCopy() any { + return deepcopy.MustAnything(c).(TestConfig) +} + +// MustCopy Returns a deep copy of struct passed to it and returns a typed copy (or panics on error) +func MustCopy[T any](c T) T { + return deepcopy.MustAnything(c).(T) +} + +func (c TestConfig) GetNetworkConfig() *ctf_config.NetworkConfig { + return c.Network +} + +func (c TestConfig) GetChainlinkImageConfig() *ctf_config.ChainlinkImageConfig { + return c.ChainlinkImage +} + +func (c TestConfig) GetCommonConfig() *Common { + return c.Common +} + +func (c TestConfig) GetChainlinkUpgradeImageConfig() *ctf_config.ChainlinkImageConfig { + return c.ChainlinkUpgradeImage +} + +func (c TestConfig) GetConfigurationName() string { + return c.ConfigurationName +} + +func (c *TestConfig) AsBase64() (string, error) { + content, err := toml.Marshal(*c) + if err != nil { + return "", errors.Wrapf(err, "error marshaling test config") + } + + return base64.StdEncoding.EncodeToString(content), nil +} + +type Common struct { + Network *string `toml:"network"` + InsideK8s *bool `toml:"inside_k8"` + L2RPCUrl *string `toml:"l2_rpc_url"` +} + +func (c *Common) Validate() error { + if c.Network == nil { + return fmt.Errorf("network must be set") + } + + switch *c.Network { + case "localnet": + break + case "testnet": + break + default: + return fmt.Errorf("network must be either 'localnet' or 'testnet'") + } + + if c.InsideK8s == nil { + return fmt.Errorf("inside_k8 must be set") + } + if c.L2RPCUrl == nil && *c.Network == "testnet" { + return fmt.Errorf("l2_rpc_url must be set") + } + + return nil +} + +type Product string + +const ( + OCR2 Product = "ocr2" +) + +var TestTypesWithLoki = []string{"Soak", "Smoke"} + +const TestTypeEnvVarName = "TEST_TYPE" + +func GetConfigurationNameFromEnv() (string, error) { + testType := os.Getenv(TestTypeEnvVarName) + if testType == "" { + return "", fmt.Errorf("%s env var not set", TestTypeEnvVarName) + } + + return cases.Title(language.English, cases.NoLower).String(testType), nil +} + +const ( + Base64OverrideEnvVarName = k8s_config.EnvBase64ConfigOverride + NoKey = "NO_KEY" +) + +func GetConfig(configurationName string, product Product) (TestConfig, error) { + logger := logging.GetTestLogger(nil) + + configurationName = strings.ReplaceAll(configurationName, "/", "_") + configurationName = strings.ReplaceAll(configurationName, " ", "_") + configurationName = cases.Title(language.English, cases.NoLower).String(configurationName) + fileNames := []string{ + "default.toml", + fmt.Sprintf("%s.toml", product), + "overrides.toml", + } + + testConfig := TestConfig{} + testConfig.ConfigurationName = configurationName + logger.Debug().Msgf("Will apply configuration named '%s' if it is found in any of the configs", configurationName) + + var handleSpecialOverrides = func(logger zerolog.Logger, filename, configurationName string, target *TestConfig, content []byte, product Product) error { + switch product { + default: + err := ctf_config.BytesToAnyTomlStruct(logger, filename, configurationName, &testConfig, content) + if err != nil { + return errors.Wrapf(err, "error reading file %s", filename) + } + + return nil + } + } + + // read embedded configs is build tag "embed" is set + // this makes our life much easier when using a binary + if areConfigsEmbedded { + logger.Info().Msg("Reading embedded configs") + embeddedFiles := []string{"default.toml", fmt.Sprintf("%s/%s.toml", product, product)} + for _, fileName := range embeddedFiles { + file, err := embeddedConfigs.ReadFile(fileName) + if err != nil && errors.Is(err, os.ErrNotExist) { + logger.Debug().Msgf("Embedded config file %s not found. Continuing", fileName) + continue + } else if err != nil { + return TestConfig{}, errors.Wrapf(err, "error reading embedded config") + } + + err = handleSpecialOverrides(logger, fileName, configurationName, &testConfig, file, product) + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error unmarshalling embedded config") + } + } + } + + logger.Info().Msg("Reading configs from file system") + for _, fileName := range fileNames { + logger.Debug().Msgf("Looking for config file %s", fileName) + filePath, err := osutil.FindFile(fileName, osutil.DEFAULT_STOP_FILE_NAME, 3) + + if err != nil && errors.Is(err, os.ErrNotExist) { + logger.Debug().Msgf("Config file %s not found", fileName) + continue + } else if err != nil { + return TestConfig{}, errors.Wrapf(err, "error looking for file %s", filePath) + } + logger.Debug().Str("location", filePath).Msgf("Found config file %s", fileName) + + content, err := readFile(filePath) + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error reading file %s", filePath) + } + + err = handleSpecialOverrides(logger, fileName, configurationName, &testConfig, content, product) + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error reading file %s", filePath) + } + } + + logger.Info().Msg("Reading configs from Base64 override env var") + configEncoded, isSet := os.LookupEnv(Base64OverrideEnvVarName) + if isSet && configEncoded != "" { + logger.Debug().Msgf("Found base64 config override environment variable '%s' found", Base64OverrideEnvVarName) + decoded, err := base64.StdEncoding.DecodeString(configEncoded) + if err != nil { + return TestConfig{}, err + } + + err = handleSpecialOverrides(logger, Base64OverrideEnvVarName, configurationName, &testConfig, decoded, product) + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error unmarshaling base64 config") + } + } else { + logger.Debug().Msg("Base64 config override from environment variable not found") + } + + // it neede some custom logic, so we do it separately + err := testConfig.readNetworkConfiguration() + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error reading network config") + } + + logger.Debug().Msg("Validating test config") + err = testConfig.Validate() + if err != nil { + return TestConfig{}, errors.Wrapf(err, "error validating test config") + } + + if testConfig.Common == nil { + testConfig.Common = &Common{} + } + + logger.Debug().Msg("Correct test config constructed successfully") + return testConfig, nil +} + +func (c *TestConfig) readNetworkConfiguration() error { + // currently we need to read that kind of secrets only for network configuration + if c == nil { + c.Network = &ctf_config.NetworkConfig{} + } + + c.Network.UpperCaseNetworkNames() + err := c.Network.Default() + if err != nil { + return errors.Wrapf(err, "error reading default network config") + } + + return nil +} + +func (c *TestConfig) Validate() error { + defer func() { + if r := recover(); r != nil { + panic(fmt.Errorf("Panic during test config validation: '%v'. Most probably due to presence of partial product config", r)) + } + }() + if c.ChainlinkImage == nil { + return fmt.Errorf("chainlink image config must be set") + } + if err := c.ChainlinkImage.Validate(); err != nil { + return errors.Wrapf(err, "chainlink image config validation failed") + } + if c.ChainlinkUpgradeImage != nil { + if err := c.ChainlinkUpgradeImage.Validate(); err != nil { + return errors.Wrapf(err, "chainlink upgrade image config validation failed") + } + } + if err := c.Network.Validate(); err != nil { + return errors.Wrapf(err, "network config validation failed") + } + + if c.Common == nil { + return fmt.Errorf("common config must be set") + } + + if err := c.Common.Validate(); err != nil { + return errors.Wrapf(err, "Common config validation failed") + } + + if c.OCR2 == nil { + return fmt.Errorf("OCR2 config must be set") + } + + if err := c.OCR2.Validate(); err != nil { + return errors.Wrapf(err, "OCR2 config validation failed") + } + return nil +} + +func readFile(filePath string) ([]byte, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return nil, errors.Wrapf(err, "error reading file %s", filePath) + } + + return content, nil +} diff --git a/ops/gauntlet/gauntlet_starknet.go b/ops/gauntlet/gauntlet_starknet.go index 2b11779ce..d2c1f4749 100644 --- a/ops/gauntlet/gauntlet_starknet.go +++ b/ops/gauntlet/gauntlet_starknet.go @@ -90,6 +90,10 @@ func (sg *StarknetGauntlet) InstallDependencies() error { if err != nil { return err } + _, err = sg.G.ExecCommand([]string{"build"}, *sg.options) + if err != nil { + return err + } sg.G.Command = "gauntlet" return nil }