From 3e5ef75ee5d065802f9ff9cd85206b427c6d4d4a Mon Sep 17 00:00:00 2001 From: Damjan Smickovski <32773226+smickovskid@users.noreply.github.com> Date: Tue, 21 May 2024 23:00:43 +0200 Subject: [PATCH 1/5] [BCI-2848] Added TOML support, moved tests to gauntlet, bumped deps (#705) * TOML migration, refactoring, local runs * Rebase * Removed outdated soak test * Fixing CI * Adding yarn install * Adding yarn install * Adding yarn install * Adding yarn install * Added yarn * Reverted yarn * Adding gauntlet dep install * Go mod bump * Bumped ctf * Checking path * Checking path * Linting and adding gauntlet action * Fixing CI * Fixing Ci * Fixing CI * Fixing CI * Changed default solana image * Docker env fix * Go mod bump * Checking contracts folder * Checking dir in CI * Checking dir in CI * Changing perms of artifacts due to mount issues * Changing perms of artifacts due to mount issues * Changing perms of artifacts due to mount issues * Changed the contracts docker path * Creating deploy folder prior to artifact upload * Testing mount fix * Bumped CTF actions * Testing CI... * Still testing CI * testing path * Added build contracts * Added contract download artifacts path * Added artifacts download * Fixed round check logic * Removed ls command * Fixed typo in workflow * Adjusted test duration logic * Rebase and go mod tidy * Added readme * Incorporated feedback * linter fixes * gomodtidy * linter issue --------- Co-authored-by: aalu1418 <50029043+aalu1418@users.noreply.github.com> --- .github/actions/build-gauntlet/action.yml | 29 + .github/workflows/e2e_custom_cl.yml | 17 +- .gitignore | 3 +- .tool-versions | 2 +- docs/RunningE2eTests.md | 73 +- .../networks/.env.devnet | 1 + .../src/commands/contracts/token/deploy.ts | 3 + .../src/commands/middlewares.ts | 9 +- integration-tests/common/common.go | 230 +++--- integration-tests/common/test_common.go | 734 ++++++------------ integration-tests/config/config.go | 41 + integration-tests/config/ocr2_config.go | 195 +++++ integration-tests/docker/testenv/sol.go | 31 +- integration-tests/gauntlet/gauntlet_solana.go | 104 +-- integration-tests/go.mod | 18 +- integration-tests/go.sum | 4 +- integration-tests/smoke/ocr2_test.go | 303 +++----- integration-tests/soak/ocr2_soak_test.go | 30 - integration-tests/testconfig/configs_embed.go | 15 + .../testconfig/configs_noembed.go | 12 + integration-tests/testconfig/default.toml | 52 +- .../testconfig/ocr2/example.toml | 96 --- integration-tests/testconfig/ocr2/ocr2.go | 131 +--- integration-tests/testconfig/ocr2/ocr2.toml | 3 + integration-tests/testconfig/testconfig.go | 411 ++++++++++ 25 files changed, 1347 insertions(+), 1200 deletions(-) create mode 100644 .github/actions/build-gauntlet/action.yml create mode 100644 integration-tests/config/config.go create mode 100644 integration-tests/config/ocr2_config.go delete mode 100644 integration-tests/soak/ocr2_soak_test.go create mode 100644 integration-tests/testconfig/configs_embed.go create mode 100644 integration-tests/testconfig/configs_noembed.go delete mode 100644 integration-tests/testconfig/ocr2/example.toml create mode 100644 integration-tests/testconfig/testconfig.go diff --git a/.github/actions/build-gauntlet/action.yml b/.github/actions/build-gauntlet/action.yml new file mode 100644 index 000000000..61c5e21db --- /dev/null +++ b/.github/actions/build-gauntlet/action.yml @@ -0,0 +1,29 @@ +name: Install gauntlet dependencies +description: A GitHub Action to get tool versions and build Gauntlet + +runs: + using: 'composite' + steps: + - name: Checkout Repository + uses: actions/checkout@v4.1.5 + + - name: Get Tool Versions + uses: smartcontractkit/tool-versions-to-env-action@v1.0.8 + id: tool-versions + + - name: Setup Node ${{ steps.tool-versions.outputs.nodejs_version }} + uses: actions/setup-node@v4.0.2 + with: + node-version: ${{ steps.tool-versions.outputs.nodejs_version }} + + - name: Install Dependencies + run: yarn --cwd ./gauntlet install --frozen-lockfile + shell: bash + + - name: Build Gauntlet + run: yarn --cwd ./gauntlet build + shell: bash + + - name: Run Gauntlet + run: yarn --cwd ./gauntlet gauntlet + shell: bash diff --git a/.github/workflows/e2e_custom_cl.yml b/.github/workflows/e2e_custom_cl.yml index a7c40bf2b..6036cfdc9 100644 --- a/.github/workflows/e2e_custom_cl.yml +++ b/.github/workflows/e2e_custom_cl.yml @@ -122,11 +122,7 @@ jobs: env: TEST_SUITE: smoke TEST_ARGS: -test.timeout 30m - CHAINLINK_COMMIT_SHA: ${{ github.sha }} - CHAINLINK_ENV_USER: ${{ github.actor }} TEST_LOG_LEVEL: debug - SELECTED_NETWORKS: SIMULATED - INTERNAL_DOCKER_REPO: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Collect Metrics @@ -150,19 +146,19 @@ jobs: fi - name: Checkout the repo uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - - name: Download Artifacts - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 - with: - name: artifacts - path: ${{ env.CONTRACT_ARTIFACTS_PATH }} - name: Install Solana CLI # required for ensuring the local test validator is configured correctly run: ./scripts/install-solana-ci.sh + - name: Install gauntlet + uses: ./.github/actions/build-gauntlet - name: Generate config overrides run: | # https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/config/README.md cat << EOF > config.toml [ChainlinkImage] image="${{ env.CL_ECR }}" version="solana.${{ env.CUSTOM_CORE_REF || github.event.inputs.cl_branch_ref || github.sha }}" + [Common] + user="${{ github.actor }}" + internal_docker_repo = "${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com" EOF # shellcheck disable=SC2002 @@ -172,10 +168,11 @@ jobs: # shellcheck disable=SC2086 echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@7f2d504e0b6ebd4aa5ece371d9c5eb9762803ca0 # v2.3.16 with: test_command_to_run: cd ./integration-tests && go test -timeout 24h -count=1 -run TestSolanaOCRV2Smoke -json $(args) ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt test_download_vendor_packages_command: cd ./integration-tests && go mod download + download_contract_artifacts_path: ${{ env.CONTRACT_ARTIFACTS_PATH }} go_mod_path: ./integration-tests/go.mod cl_repo: ${{ env.CL_ECR }} cl_image_tag: solana.${{ env.CUSTOM_CORE_REF || github.event.inputs.cl_branch_ref || github.sha }} diff --git a/.gitignore b/.gitignore index d55793c70..e87046e53 100644 --- a/.gitignore +++ b/.gitignore @@ -44,12 +44,13 @@ tests-smoke-report.xml overrides.toml # Test & linter reports +.test_summary/ *report.xml *report.json *.out *coverage* eslint-report.json .run.id - +override*.toml # go work files go.work* diff --git a/.tool-versions b/.tool-versions index 87bd4e87a..52ebfcbb2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,4 +1,4 @@ -nodejs 16.13.2 +nodejs 18.13.0 yarn 1.22.19 rust 1.59.0 golang 1.21.6 diff --git a/docs/RunningE2eTests.md b/docs/RunningE2eTests.md index 5e1e1e60d..babdf6b94 100644 --- a/docs/RunningE2eTests.md +++ b/docs/RunningE2eTests.md @@ -1,36 +1,37 @@ -# Running e2e tests - -The e2e tests run inside of a k8s cluster. They will run against whatever cluster your current kubectl context is set to. This can be an external k8s cluster or a local one (using something like minikube or k3d). - -Note: If running against a local k8s cluster, make sure you have plenty of ram allocated for docker, 12 gb if running individual tests and a lot more if you run parallel test like the ones in `make test_smoke` since it can run multiple tests in parallel - -Steps to run the e2e tests: - -1. Build using the `make build` command if you haven't already built the contracts. -2. Make sure your kubectl context is pointing to the cluster you want to run tests against. -3. Run a test, you have several options - - `make test_smoke` will run the ocr2 e2e tests - - `make test_chaos` will run the chaos tests - -## Env variables -```bash -CHAINLINK_ENV_USER=John; -CHAINLINK_IMAGE={AWS_OIDC}.dkr.ecr.{AWS_REGION}.amazonaws.com/chainlink; -CHAINLINK_VERSION=develop; # Can be SHA -SELECTED_NETWORKS=SIMULATED; -INTERNAL_DOCKER_REPO={AWS_OIDC}.dkr.ecr.{AWS_REGION}.amazonaws.com -TTL=72h; # optional -TEST_LOG_LEVEL=debug # optional - -# Running on testnet -LINK_TOKEN=Dmw5mDvteezKfop9zd3RQbJmZfBATF3QuSqDU66axyts; -PROGRAM_ID_ACCESS_CONTROLLER=9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW; -PROGRAM_ID_OCR2=cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ; -PROGRAM_ID_STORE=HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny; -VAULT_ADDRESS=G27m7KxTh4KVLapxB9MXfEA8HLUfYuGYQ1ELEs2zQdiQ; -PRIVATE_KEY=[123, 123, ...]; -RPC_URL=https://api.devnet.solana.com; -WS_URL=wss://api.devnet.solana.com/; -``` - -You can always look at the [Makefile](../Makefile) in this repo to see other commands or tests that have been added since this readme was last updated. \ No newline at end of file +# Running tests + +## Installation +`make build && make install` + + +## Configuration +The main test config logic resides in the `integration-tests/testconfig/` directory. Everything is configured using TOML. The minimum OCR2 required values can be located at `integration-tests/testconfig/default.toml`, these values default to running the tests locally in docker using devnet. + +### Combinations +There are a few possibile combinations to run tests that we support. + +**Devnet** +Devnet requires previously deployed programs that are owned by the person running the tests. The program ID's are required for testnet, but ignored in localnet. + +- `Common.network` needs to be set to `devnet` which will instruct the tests to run against devnet +- `ocr2_program_id`, `access_controller_program_id`, `store_program_id`, `link_token_address`, `vault_address` need to be set so the tests know what programs to use so we avoid deploying each time. +- `rpc_url` and `ws_url` need to be set + +**Localnet** +Setting localnet will instruct the tests to run in localnet, the program ID's are not taken from the TOML in this scenario, but rather defined in the `integration-tests/config/config.go`. + +**K8s** + +Running in Kubernetes will require aws auth. + +- `Common.inside_k8` needs to be set to true if you want to run the tests in k8 + +### Overrides + +By default all values are pulled either from `default.toml` or if we create an `overrides.toml` where we want to set new values or override existing values. Both `default.toml` and `overrides.toml` will end up being merged where values that are set in both files will be taken based on the value in `overrides.toml`. + +## Run tests + +`cd integration-tests/smoke && go test -timeout 24h -count=1 -run TestSolanaOCRV2Smoke -test.timeout 30m;` + + diff --git a/gauntlet/packages/gauntlet-solana-contracts/networks/.env.devnet b/gauntlet/packages/gauntlet-solana-contracts/networks/.env.devnet index 297b1e217..24862931c 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/networks/.env.devnet +++ b/gauntlet/packages/gauntlet-solana-contracts/networks/.env.devnet @@ -1,4 +1,5 @@ NODE_URL=https://api.devnet.solana.com +WS_URL=wss://api.devnet.solana.com PROGRAM_ID_OCR2=cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ PROGRAM_ID_ACCESS_CONTROLLER=9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/token/deploy.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/token/deploy.ts index 3e342bffe..f1c1449c4 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/token/deploy.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/token/deploy.ts @@ -56,6 +56,9 @@ export default class DeployToken extends SolanaCommand { `) return { + data: { + vault: tokenVault.toString(), + }, responses: [ { tx: { ...this.wrapResponse('', token.toString()), wait: async () => ({ success: true }) }, diff --git a/gauntlet/packages/gauntlet-solana/src/commands/middlewares.ts b/gauntlet/packages/gauntlet-solana/src/commands/middlewares.ts index a5612b0dd..b23a6d856 100644 --- a/gauntlet/packages/gauntlet-solana/src/commands/middlewares.ts +++ b/gauntlet/packages/gauntlet-solana/src/commands/middlewares.ts @@ -8,7 +8,7 @@ import SolanaCommand from './internal/solana' import { LedgerWallet, LocalWallet } from './wallet' const isValidURL = (url: string) => { - var pattern = new RegExp('^(https?)://') + const pattern = new RegExp('^(https?|wss?):/') return pattern.test(url) } export const withProvider: Middleware = (c: SolanaCommand, next: Next) => { @@ -17,8 +17,11 @@ export const withProvider: Middleware = (c: SolanaCommand, next: Next) => { nodeURL && isValidURL(nodeURL), `Invalid NODE_URL (${nodeURL}), please add an http:// or https:// prefix`, ) - - c.provider = new AnchorProvider(new Connection(nodeURL), c.wallet, {}) + const wsUrl = process.env.WS_URL + if (wsUrl) { + assertions.assert(isValidURL(wsUrl), `Invalid WS_URL (${wsUrl}), please add an ws:// or wss:// prefix`) + } + c.provider = new AnchorProvider(new Connection(nodeURL, wsUrl ? { wsEndpoint: wsUrl } : {}), c.wallet, {}) return next() } diff --git a/integration-tests/common/common.go b/integration-tests/common/common.go index 3469b5c84..aad73596a 100644 --- a/integration-tests/common/common.go +++ b/integration-tests/common/common.go @@ -5,13 +5,12 @@ import ( "encoding/hex" "fmt" "math/big" - "os" "sort" - "strconv" "strings" "testing" "time" + "github.com/gagliardetto/solana-go" "github.com/google/uuid" "github.com/lib/pq" "github.com/rs/zerolog/log" @@ -22,53 +21,60 @@ import ( "github.com/smartcontractkit/libocr/offchainreporting2/reportingplugin/median" "github.com/smartcontractkit/libocr/offchainreporting2/types" - "github.com/smartcontractkit/chainlink-common/pkg/config" + ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/config" ctf_test_env "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" "github.com/smartcontractkit/chainlink-testing-framework/k8s/environment" - "github.com/smartcontractkit/chainlink-testing-framework/k8s/pkg/alias" "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/k8s/pkg/helm/sol" "github.com/smartcontractkit/chainlink-testing-framework/utils/ptr" + "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/types/config/node" + cl "github.com/smartcontractkit/chainlink/v2/core/services/chainlink" "github.com/smartcontractkit/chainlink/v2/core/services/job" "github.com/smartcontractkit/chainlink/v2/core/store/models" + "github.com/smartcontractkit/chainlink-common/pkg/config" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + chainConfig "github.com/smartcontractkit/chainlink-solana/integration-tests/config" test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/testenv" "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" - "github.com/smartcontractkit/chainlink-solana/pkg/solana" + tc "github.com/smartcontractkit/chainlink-solana/integration-tests/testconfig" + cl_solana "github.com/smartcontractkit/chainlink-solana/pkg/solana" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" ) -const ( - ChainName = "solana" - LocalnetChainID = "localnet" - DevnetChainID = "devnet" - DefaultNodeCount = 5 - DefaultTTL = "3h" - SolanaLocalNetURL = "http://sol:8899" - SolanaDevnetURL = "https://api.devnet.solana.com" -) - type Common struct { - IsK8s bool - ChainName string - ChainID string - NodeCount int - NodeOpts []test_env.ClNodeOption - TTL time.Duration - ClConfig map[string]interface{} - EnvConfig map[string]interface{} - K8Config *environment.Config - Env *environment.Environment - DockerEnv *SolCLClusterTestEnv - SolanaURL string + ChainDetails *ChainDetails + TestConfig *tc.TestConfig + TestEnvDetails *TestEnvDetails + ClConfig map[string]interface{} + EnvConfig map[string]interface{} + Env *environment.Environment + DockerEnv *SolCLClusterTestEnv + AccountDetails *AccountDetails +} + +type TestEnvDetails struct { + TestDuration time.Duration + K8Config *environment.Config + NodeOpts []test_env.ClNodeOption +} + +type ChainDetails struct { + ChainName string + ChainID string + RPCUrl string + RPCURLExternal string + WSURLExternal string + ProgramAddresses *chainConfig.ProgramAddresses + MockserverURLInternal string + MockServerEndpoint string } type SolCLClusterTestEnv struct { @@ -77,6 +83,11 @@ type SolCLClusterTestEnv struct { Killgrave *ctf_test_env.Killgrave } +type AccountDetails struct { + PrivateKey string + PublicKey string +} + // ContractNodeInfo contains the indexes of the nodes, bridges, NodeKeyBundles and nodes relevant to an OCR2 Contract type ContractNodeInfo struct { OCR2 *solclient.OCRv2 @@ -115,78 +126,55 @@ func stripKeyPrefix(key string) string { return key } -func New(env string, isK8s bool) *Common { - var err error +func New(testConfig *tc.TestConfig) *Common { var c *Common - if env == "devnet" { - c = &Common{ - IsK8s: isK8s, - ChainName: ChainName, - ChainID: DevnetChainID, - SolanaURL: SolanaDevnetURL, - } - } else { - c = &Common{ - IsK8s: isK8s, - ChainName: ChainName, - ChainID: LocalnetChainID, - SolanaURL: SolanaLocalNetURL, - } + + // Setting localnet as the default config + config := chainConfig.LocalNetConfig() + // Getting the default localnet private key + privateKey, err := solana.PrivateKeyFromBase58(solclient.DefaultPrivateKeysSolValidator[1]) + if err != nil { + panic("Could not decode private localnet private key") } - // 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)) + privateKeyString := fmt.Sprintf("[%s]", formatBuffer([]byte(privateKey))) + publicKey := privateKey.PublicKey().String() + + if *testConfig.Common.Network == "devnet" { + config = chainConfig.DevnetConfig() + privateKeyString = *testConfig.Common.PrivateKey + + if *testConfig.Common.RPCURL != "" { + config.RPCUrl = *testConfig.Common.RPCURL + config.WSUrl = *testConfig.Common.WsURL + config.ProgramAddresses = &chainConfig.ProgramAddresses{ + OCR2: *testConfig.SolanaConfig.OCR2ProgramID, + AccessController: *testConfig.SolanaConfig.AccessControllerProgramID, + Store: *testConfig.SolanaConfig.StoreProgramID, + } } - } else { - c.NodeCount = DefaultNodeCount } - // 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{ + ChainDetails: &ChainDetails{ + ChainID: config.ChainID, + RPCUrl: config.RPCUrl, + ChainName: config.ChainName, + ProgramAddresses: config.ProgramAddresses, + }, + TestConfig: testConfig, + TestEnvDetails: &TestEnvDetails{ + TestDuration: *testConfig.OCR2.TestDurationParsed, + }, + AccountDetails: &AccountDetails{ + PrivateKey: privateKeyString, + PublicKey: publicKey, + }, + Env: &environment.Environment{}, } return c } -func (c *Common) CreateSolanaChainAndNode(nodes []*client.ChainlinkClient) error { - for _, n := range nodes { - _, _, err := n.CreateSolanaChain(&client.SolanaChainAttributes{ChainID: c.ChainID}) - if err != nil { - return err - } - _, _, err = n.CreateSolanaNode(&client.SolanaNodeAttributes{ - Name: ChainName, - SolanaChainID: c.ChainID, - SolanaURL: c.SolanaURL, - }) - if err != nil { - return err - } - } - return nil -} - func (c *Common) CreateNodeKeysBundle(nodes []*client.ChainlinkClient) ([]client.NodeKeysBundle, error) { nkb := make([]client.NodeKeysBundle, 0) for _, n := range nodes { @@ -196,11 +184,11 @@ 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(c.ChainDetails.ChainName, c.ChainDetails.ChainID) if err != nil { return nil, err } - ocrKey, _, err := n.CreateOCR2Key(ChainName) + ocrKey, _, err := n.CreateOCR2Key(c.ChainDetails.ChainName) if err != nil { return nil, err } @@ -403,7 +391,7 @@ func PluginConfigToTomlFormat(pluginConfig string) job.JSONConfig { func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error { var bootstrapNodeInternalIP string var nodeCount int - if c.IsK8s { + if *c.TestConfig.Common.InsideK8s { nodeCount = len(contractNodeInfo.NodesK8s) bootstrapNodeInternalIP = contractNodeInfo.BootstrapNodeK8s.InternalIP() } else { @@ -411,11 +399,11 @@ func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error bootstrapNodeInternalIP = contractNodeInfo.BootstrapNode.InternalIP() } relayConfig := job.JSONConfig{ - "nodeEndpointHTTP": SolanaLocalNetURL, + "nodeEndpointHTTP": c.ChainDetails.RPCUrl, "ocr2ProgramID": contractNodeInfo.OCR2.ProgramAddress(), "transmissionsID": contractNodeInfo.Store.TransmissionsAddress(), "storeProgramID": contractNodeInfo.Store.ProgramAddress(), - "chainID": LocalnetChainID, + "chainID": c.ChainDetails.ChainID, } bootstrapPeers := []client.P2PData{ { @@ -429,7 +417,7 @@ func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error JobType: "bootstrap", OCR2OracleSpec: job.OCR2OracleSpec{ ContractID: contractNodeInfo.OCR2.Address(), - Relay: ChainName, + Relay: c.ChainDetails.ChainName, RelayConfig: relayConfig, P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, OCRKeyBundleID: null.StringFrom(contractNodeInfo.BootstrapNodeKeysBundle.OCR2Key.Data.ID), @@ -438,7 +426,7 @@ func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error ContractConfigTrackerPollInterval: models.Interval(15 * time.Second), }, } - if c.IsK8s { + if *c.TestConfig.Common.InsideK8s { if _, err := contractNodeInfo.BootstrapNodeK8s.MustCreateJob(jobSpec); err != nil { s, _ := jobSpec.String() return fmt.Errorf("failed creating job for boostrap node: %w\n spec:\n%s", err, s) @@ -457,7 +445,7 @@ func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error ObservationSource: contractNodeInfo.BridgeInfos[nIdx].ObservationSource, OCR2OracleSpec: job.OCR2OracleSpec{ ContractID: contractNodeInfo.OCR2.Address(), - Relay: ChainName, + Relay: c.ChainDetails.ChainName, RelayConfig: relayConfig, P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, OCRKeyBundleID: null.StringFrom(contractNodeInfo.NodeKeysBundle[nIdx].OCR2Key.Data.ID), @@ -468,7 +456,7 @@ func (c *Common) CreateJobsForContract(contractNodeInfo *ContractNodeInfo) error PluginConfig: PluginConfigToTomlFormat(contractNodeInfo.BridgeInfos[nIdx].JuelsSource), }, } - if c.IsK8s { + if *c.TestConfig.Common.InsideK8s { n := contractNodeInfo.NodesK8s[nIdx] if _, err := n.MustCreateJob(jobSpec); err != nil { return fmt.Errorf("failed creating job for node %s: %w", n.URL(), err) @@ -497,18 +485,18 @@ func BuildNodeContractPairID(node *client.ChainlinkClient, ocr2Addr string) (str } func (c *Common) DefaultNodeConfig() *cl.Config { - solConfig := solana.TOMLConfig{ + solConfig := cl_solana.TOMLConfig{ Enabled: ptr.Ptr(true), - ChainID: ptr.Ptr(c.ChainID), + ChainID: ptr.Ptr(c.ChainDetails.ChainID), Nodes: []*solcfg.Node{ { Name: ptr.Ptr("primary"), - URL: config.MustParseURL(c.SolanaURL), + URL: config.MustParseURL(c.ChainDetails.RPCUrl), }, }, } baseConfig := node.NewBaseConfig() - baseConfig.Solana = solana.TOMLConfigs{ + baseConfig.Solana = cl_solana.TOMLConfigs{ &solConfig, } baseConfig.OCR2.Enabled = ptr.Ptr(true) @@ -523,25 +511,47 @@ func (c *Common) DefaultNodeConfig() *cl.Config { } func (c *Common) Default(t *testing.T, namespacePrefix string) (*Common, error) { - c.K8Config = &environment.Config{ + c.TestEnvDetails.K8Config = &environment.Config{ NamespacePrefix: fmt.Sprintf("solana-%s", namespacePrefix), - TTL: c.TTL, + 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). + var overrideFn = func(_ interface{}, target interface{}) { + ctfconfig.MustConfigOverrideChainlinkVersion(c.TestConfig.ChainlinkImage, target) + } + cd := chainlink.NewWithOverride(0, map[string]any{ + "toml": tomlString, + "replicas": *c.TestConfig.OCR2.NodeCount, + "chainlink": map[string]interface{}{ + "resources": map[string]interface{}{ + "requests": map[string]interface{}{ + "cpu": "2000m", + "memory": "4Gi", + }, + "limits": map[string]interface{}{ + "cpu": "2000m", + "memory": "4Gi", + }, + }, + }, + "db": map[string]any{ + "image": map[string]any{ + "version": "15.5", + }, + "stateful": c.TestConfig.Common.Stateful, + }, + }, c.TestConfig.ChainlinkImage, overrideFn) + c.Env = environment.New(c.TestEnvDetails.K8Config). AddHelm(sol.New(nil)). AddHelm(mock_adapter.New(nil)). - AddHelm(chainlink.New(0, map[string]interface{}{ - "toml": tomlString, - "replicas": c.NodeCount, - })) + AddHelm(cd) } return c, nil diff --git a/integration-tests/common/test_common.go b/integration-tests/common/test_common.go index 86bec56c8..1a5d34944 100644 --- a/integration-tests/common/test_common.go +++ b/integration-tests/common/test_common.go @@ -3,169 +3,96 @@ package common import ( "fmt" "math/big" - "os" - "strings" - "sync" + "net/http" "testing" "time" - "github.com/onsi/gomega" - "github.com/rs/zerolog" + "github.com/gagliardetto/solana-go/rpc" + "github.com/gagliardetto/solana-go/rpc/ws" + "github.com/go-resty/resty/v2" + "github.com/google/uuid" + "github.com/lib/pq" "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v4" - "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink-testing-framework/utils/osutil" - "github.com/smartcontractkit/chainlink/integration-tests/testconfig" - - test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/testenv" - "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" - - "golang.org/x/sync/errgroup" + test_env_ctf "github.com/smartcontractkit/chainlink-testing-framework/docker/test_env" + "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" -) - -const ( - ContractsStateFile = "contracts-chaos-state.json" - NewRoundCheckTimeout = 120 * time.Second - NewSoakRoundsCheckTimeout = 3 * time.Hour - NewRoundCheckPollInterval = 1 * time.Second - SourceChangeInterval = 5 * time.Second - ChaosAwaitingApply = 1 * time.Minute - // ChaosGroupFaulty Group of faulty nodes, even if they fail OCR must work - ChaosGroupFaulty = "chaosGroupFaulty" - // ChaosGroupYellow if nodes from that group fail we may not work while some experiments are going - // but after experiment it must recover - ChaosGroupYellow = "chaosGroupYellow" - // ChaosGroupLeftHalf an equal half of all nodes - ChaosGroupLeftHalf = "chaosGroupLeftHalf" - // ChaosGroupRightHalf an equal half of all nodes - ChaosGroupRightHalf = "chaosGroupRightHalf" - // ChaosGroupOnline a group of nodes that are working - ChaosGroupOnline = "chaosGroupOnline" - // UntilStop some chaos experiments doesn't respect absence of duration and got recovered immediately, so we enforce duration - UntilStop = 666 * time.Hour -) -type Contracts struct { - BAC *solclient.AccessController - RAC *solclient.AccessController - OCR2 *solclient.OCRv2 - Store *solclient.Store - StoreAuth string -} - -type OCR2OnChainConfig struct { - Oracles []Operator `json:"oracles"` - F int `json:"f"` - ProposalID string `json:"proposalId"` -} + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/store/models" -type OffchainConfig struct { - DeltaProgressNanoseconds int64 `json:"deltaProgressNanoseconds"` - DeltaResendNanoseconds int64 `json:"deltaResendNanoseconds"` - DeltaRoundNanoseconds int64 `json:"deltaRoundNanoseconds"` - DeltaGraceNanoseconds int64 `json:"deltaGraceNanoseconds"` - DeltaStageNanoseconds int64 `json:"deltaStageNanoseconds"` - RMax int `json:"rMax"` - S []int `json:"s"` - OffchainPublicKeys []string `json:"offchainPublicKeys"` - PeerIds []string `json:"peerIds"` - ReportingPluginConfig ReportingPluginConfig `json:"reportingPluginConfig"` - MaxDurationQueryNanoseconds int64 `json:"maxDurationQueryNanoseconds"` - MaxDurationObservationNanoseconds int64 `json:"maxDurationObservationNanoseconds"` - MaxDurationReportNanoseconds int64 `json:"maxDurationReportNanoseconds"` - MaxDurationShouldAcceptFinalizedReportNanoseconds int64 `json:"maxDurationShouldAcceptFinalizedReportNanoseconds"` - MaxDurationShouldTransmitAcceptedReportNanoseconds int64 `json:"maxDurationShouldTransmitAcceptedReportNanoseconds"` - ConfigPublicKeys []string `json:"configPublicKeys"` -} - -type ReportingPluginConfig struct { - AlphaReportInfinite bool `json:"alphaReportInfinite"` - AlphaReportPpb int `json:"alphaReportPpb"` - AlphaAcceptInfinite bool `json:"alphaAcceptInfinite"` - AlphaAcceptPpb int `json:"alphaAcceptPpb"` - DeltaCNanoseconds int `json:"deltaCNanoseconds"` -} + test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/testenv" + "github.com/smartcontractkit/chainlink-solana/integration-tests/gauntlet" + "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" + "github.com/smartcontractkit/chainlink-solana/integration-tests/testconfig" +) -// TODO - Decouple all OCR2 config structs to be reusable between chains -type OCROffChainConfig struct { - ProposalID string `json:"proposalId"` - OffchainConfig OffchainConfig `json:"offchainConfig"` - UserSecret string `json:"userSecret"` +type OCRv2TestState struct { + ContractDeployer *solclient.ContractDeployer + LinkToken *solclient.LinkToken + ContractsNodeSetup map[int]*ContractNodeInfo + Clients *Clients + Common *Common + Config *Config + Gauntlet *gauntlet.SolanaGauntlet } -type Operator struct { - Signer string `json:"signer"` - Transmitter string `json:"transmitter"` - Payee string `json:"payee"` +type Clients struct { + SolanaClient *solclient.Client + KillgraveClient *test_env_ctf.Killgrave + ChainlinkClient *ChainlinkClient } -type PayeeConfig struct { - Operators []Operator `json:"operators"` - ProposalID string `json:"proposalId"` +type ChainlinkClient struct { + ChainlinkClientDocker *test_env.ClCluster + ChainlinkClientK8s []*client.ChainlinkK8sClient + ChainlinkNodes []*client.ChainlinkClient + NKeys []client.NodeKeysBundle + AccountAddresses []string } -type ProposalAcceptConfig struct { - ProposalID string `json:"proposalId"` - Version int `json:"version"` - F int `json:"f"` - Oracles []Operator `json:"oracles"` - OffchainConfig OffchainConfig `json:"offchainConfig"` - RandomSecret string `json:"randomSecret"` +type Config struct { + T *testing.T + TestConfig *testconfig.TestConfig + Resty *resty.Client + err error } -func NewOCRv2State(t *testing.T, contracts int, namespacePrefix string, env string, isK8s bool, testConfig *testconfig.TestConfig) (*OCRv2TestState, error) { - c, err := New(env, isK8s).Default(t, namespacePrefix) +func NewOCRv2State(t *testing.T, contracts int, namespacePrefix string, testConfig *testconfig.TestConfig) (*OCRv2TestState, error) { + c, err := New(testConfig).Default(t, namespacePrefix) if err != nil { return nil, err } state := &OCRv2TestState{ - Mu: &sync.Mutex{}, - LastRoundTime: make(map[string]time.Time), ContractsNodeSetup: make(map[int]*ContractNodeInfo), Common: c, - Client: &solclient.Client{}, - T: t, - L: log.Logger, - TestConfig: testConfig, - } - if state.T != nil { - state.L = logging.GetTestLogger(state.T) + Clients: &Clients{ + SolanaClient: &solclient.Client{}, + ChainlinkClient: &ChainlinkClient{}, + }, + Config: &Config{ + T: t, + TestConfig: testConfig, + Resty: nil, + err: nil, + }, } - state.Client.Config = state.Client.Config.Default() + state.Clients.SolanaClient.Config = state.Clients.SolanaClient.Config.Default() for i := 0; i < contracts; i++ { state.ContractsNodeSetup[i] = &ContractNodeInfo{} state.ContractsNodeSetup[i].BootstrapNodeIdx = 0 - for n := 1; n < state.Common.NodeCount; n++ { + for n := 1; n < *state.Config.TestConfig.OCR2.NodeCount; n++ { state.ContractsNodeSetup[i].NodesIdx = append(state.ContractsNodeSetup[i].NodesIdx, n) } } return state, nil } -type OCRv2TestState struct { - Mu *sync.Mutex - ChainlinkNodesK8s []*client.ChainlinkK8sClient - ChainlinkNodes []*client.ChainlinkClient - ContractDeployer *solclient.ContractDeployer - LinkToken *solclient.LinkToken - Contracts []Contracts - ContractsNodeSetup map[int]*ContractNodeInfo - NodeKeysBundle []client.NodeKeysBundle - Client *solclient.Client - RoundsFound int - LastRoundTime map[string]time.Time - err error - T *testing.T - Common *Common - L zerolog.Logger - TestConfig *testconfig.TestConfig -} - type ContractsState struct { OCR string `json:"ocr"` Store string `json:"store"` @@ -176,53 +103,69 @@ type ContractsState struct { OCRVault string `json:"ocr_vault"` } -func (m *OCRv2TestState) LabelChaosGroups() { - m.LabelChaosGroup(1, 5, ChaosGroupFaulty) - m.LabelChaosGroup(6, 19, ChaosGroupOnline) - m.LabelChaosGroup(0, 8, ChaosGroupYellow) - m.LabelChaosGroup(0, 9, ChaosGroupLeftHalf) - m.LabelChaosGroup(10, 19, ChaosGroupRightHalf) -} - func (m *OCRv2TestState) DeployCluster(contractsDir string) { - if m.Common.IsK8s { + if *m.Config.TestConfig.Common.InsideK8s { m.DeployEnv(contractsDir) + + // Setting up the URLs + m.Common.ChainDetails.RPCURLExternal = m.Common.Env.URLs["sol"][0] + m.Common.ChainDetails.WSURLExternal = m.Common.Env.URLs["sol"][1] + + if *m.Config.TestConfig.Common.Network == "devnet" { + m.Common.ChainDetails.RPCUrl = *m.Config.TestConfig.Common.RPCURL + m.Common.ChainDetails.RPCURLExternal = *m.Config.TestConfig.Common.RPCURL + m.Common.ChainDetails.WSURLExternal = *m.Config.TestConfig.Common.WsURL + } + + m.Common.ChainDetails.MockserverURLInternal = m.Common.Env.URLs["qa_mock_adapter_internal"][0] + m.Common.ChainDetails.MockServerEndpoint = "five" } else { env, err := test_env.NewTestEnv() - require.NoError(m.T, err) - sol := test_env_sol.NewSolana([]string{env.DockerNetwork.Name}) + require.NoError(m.Config.T, err) + sol := test_env_sol.NewSolana([]string{env.DockerNetwork.Name}, *m.Config.TestConfig.Common.DevnetImage, m.Common.AccountDetails.PublicKey) err = sol.StartContainer() - require.NoError(m.T, err) - m.Common.SolanaURL = sol.InternalHTTPURL + require.NoError(m.Config.T, err) + + // Setting the External RPC url for Gauntlet + m.Common.ChainDetails.RPCUrl = sol.InternalHTTPURL + m.Common.ChainDetails.RPCURLExternal = sol.ExternalHTTPURL + m.Common.ChainDetails.WSURLExternal = sol.ExternalWsURL + + if *m.Config.TestConfig.Common.Network == "devnet" { + m.Common.ChainDetails.RPCUrl = *m.Config.TestConfig.Common.RPCURL + m.Common.ChainDetails.RPCURLExternal = *m.Config.TestConfig.Common.RPCURL + m.Common.ChainDetails.WSURLExternal = *m.Config.TestConfig.Common.WsURL + } + b, err := test_env.NewCLTestEnvBuilder(). WithNonEVM(). - WithTestInstance(m.T). - WithTestConfig(m.TestConfig). + WithTestInstance(m.Config.T). + WithTestConfig(m.Config.TestConfig). WithMockAdapter(). WithCLNodeConfig(m.Common.DefaultNodeConfig()). - WithCLNodes(m.Common.NodeCount). - WithCLNodeOptions(m.Common.NodeOpts...). + WithCLNodes(*m.Config.TestConfig.OCR2.NodeCount). + WithCLNodeOptions(m.Common.TestEnvDetails.NodeOpts...). WithStandardCleanup(). WithTestEnv(env) - require.NoError(m.T, err) + require.NoError(m.Config.T, err) env, err = b.Build() - require.NoError(m.T, err) + require.NoError(m.Config.T, err) m.Common.DockerEnv = &SolCLClusterTestEnv{ CLClusterTestEnv: env, Sol: sol, Killgrave: env.MockAdapter, } + // Setting up Mock adapter + m.Clients.KillgraveClient = env.MockAdapter + m.Common.ChainDetails.MockserverURLInternal = m.Clients.KillgraveClient.InternalEndpoint + m.Common.ChainDetails.MockServerEndpoint = "mockserver-bridge" + err = m.Clients.KillgraveClient.SetAdapterBasedIntValuePath("/mockserver-bridge", []string{http.MethodGet, http.MethodPost}, 5) + require.NoError(m.Config.T, err, "Failed to set mock adapter value") } + m.SetupClients() + m.SetChainlinkNodes() m.DeployContracts(contractsDir) - m.CreateJobs() -} - -func (m *OCRv2TestState) LabelChaosGroup(startInstance int, endInstance int, group string) { - for i := startInstance; i <= endInstance; i++ { - m.err = m.Common.Env.Client.AddLabel(m.Common.Env.Cfg.Namespace, fmt.Sprintf("instance=%d", i), fmt.Sprintf("%s=1", group)) - require.NoError(m.T, m.err) - } } // UploadProgramBinaries uploads programs binary files to solana-validator container @@ -230,21 +173,20 @@ func (m *OCRv2TestState) LabelChaosGroup(startInstance int, endInstance int, gro // can't expose UDP ports required to copy .so chunks when deploying func (m *OCRv2TestState) UploadProgramBinaries(contractsDir string) { pl, err := m.Common.Env.Client.ListPods(m.Common.Env.Cfg.Namespace, "app=sol") - require.NoError(m.T, err) + require.NoError(m.Config.T, err) _, _, _, err = m.Common.Env.Client.CopyToPod(m.Common.Env.Cfg.Namespace, contractsDir, fmt.Sprintf("%s/%s:/programs", m.Common.Env.Cfg.Namespace, pl.Items[0].Name), "sol-val") - require.NoError(m.T, err) + require.NoError(m.Config.T, err) } func (m *OCRv2TestState) DeployEnv(contractsDir string) { err := m.Common.Env.Run() - require.NoError(m.T, err) + require.NoError(m.Config.T, err) - m.Common.SolanaURL = m.Common.Env.URLs[m.Client.Config.Name][0] m.UploadProgramBinaries(contractsDir) } func (m *OCRv2TestState) NewSolanaClientSetup(networkSettings *solclient.SolNetwork) (*solclient.Client, error) { - if m.Common.IsK8s { + if *m.Config.TestConfig.Common.InsideK8s { networkSettings.URLs = m.Common.Env.URLs[networkSettings.Name] } else { networkSettings.URLs = []string{ @@ -256,400 +198,164 @@ func (m *OCRv2TestState) NewSolanaClientSetup(networkSettings *solclient.SolNetw if err != nil { return nil, err } - m.L.Info(). + log.Info(). Interface("URLs", networkSettings.URLs). Msg("Connected Solana client") return ec, nil } func (m *OCRv2TestState) SetupClients() { - m.Client, m.err = m.NewSolanaClientSetup(m.Client.Config) - require.NoError(m.T, m.err) - if m.Common.IsK8s { - m.ChainlinkNodesK8s, m.err = client.ConnectChainlinkNodes(m.Common.Env) - require.NoError(m.T, m.err) + solClient, err := m.NewSolanaClientSetup(m.Clients.SolanaClient.Config) + m.Clients.SolanaClient = solClient + require.NoError(m.Config.T, err) + if *m.Config.TestConfig.Common.InsideK8s { + m.Clients.ChainlinkClient.ChainlinkClientK8s, err = client.ConnectChainlinkNodes(m.Common.Env) + require.NoError(m.Config.T, err) } else { - m.ChainlinkNodes = m.Common.DockerEnv.ClCluster.NodeAPIs() - } -} - -func (m *OCRv2TestState) initializeNodesInContractsMap() { - for i := 0; i < len(m.ContractsNodeSetup); i++ { - for _, nodeIndex := range m.ContractsNodeSetup[i].NodesIdx { - if m.Common.IsK8s { - m.ContractsNodeSetup[i].NodesK8s = append(m.ContractsNodeSetup[i].NodesK8s, m.ChainlinkNodesK8s[nodeIndex]) - } else { - m.ContractsNodeSetup[i].Nodes = append(m.ContractsNodeSetup[i].Nodes, m.ChainlinkNodes[nodeIndex]) - } - m.ContractsNodeSetup[i].NodeKeysBundle = append(m.ContractsNodeSetup[i].NodeKeysBundle, m.NodeKeysBundle[nodeIndex]) - } - if m.Common.IsK8s { - m.ContractsNodeSetup[i].BootstrapNodeK8s = m.ChainlinkNodesK8s[m.ContractsNodeSetup[i].BootstrapNodeIdx] - } else { - m.ContractsNodeSetup[i].BootstrapNode = m.ChainlinkNodes[m.ContractsNodeSetup[i].BootstrapNodeIdx] - } - m.ContractsNodeSetup[i].BootstrapNodeKeysBundle = m.NodeKeysBundle[m.ContractsNodeSetup[i].BootstrapNodeIdx] + m.Clients.ChainlinkClient.ChainlinkClientDocker = m.Common.DockerEnv.ClCluster } } // DeployContracts deploys contracts func (m *OCRv2TestState) DeployContracts(contractsDir string) { - if m.Common.IsK8s { - m.NodeKeysBundle, m.err = m.Common.CreateNodeKeysBundle(m.GetChainlinkNodes()) - } else { - m.NodeKeysBundle, m.err = m.Common.CreateNodeKeysBundle(m.Common.DockerEnv.ClCluster.NodeAPIs()) - } - require.NoError(m.T, m.err) - cd, err := solclient.NewContractDeployer(m.Client, nil) - require.NoError(m.T, err) - err = cd.LoadPrograms(contractsDir) - require.NoError(m.T, err) - if m.Common.IsK8s { + var err error + m.Clients.ChainlinkClient.NKeys, err = m.Common.CreateNodeKeysBundle(m.Clients.ChainlinkClient.ChainlinkNodes) + require.NoError(m.Config.T, err) + cd, err := solclient.NewContractDeployer(m.Clients.SolanaClient, nil) + require.NoError(m.Config.T, err) + if *m.Config.TestConfig.Common.InsideK8s { err = cd.DeployAnchorProgramsRemote(contractsDir, m.Common.Env) } else { err = cd.DeployAnchorProgramsRemoteDocker(contractsDir, m.Common.DockerEnv.Sol) } - require.NoError(m.T, err) - cd.RegisterAnchorPrograms() - require.NoError(m.T, cd.ValidateProgramsDeployed()) - m.Client.LinkToken, err = cd.DeployLinkTokenContract() - require.NoError(m.T, err) - err = FundOracles(m.Client, m.NodeKeysBundle, big.NewFloat(1e4)) - require.NoError(m.T, err) - - m.initializeNodesInContractsMap() - g := errgroup.Group{} - for i := 0; i < len(m.ContractsNodeSetup); i++ { - i := i - g.Go(func() error { - cd, err := solclient.NewContractDeployer(m.Client, m.Client.LinkToken) - require.NoError(m.T, err) - err = cd.GenerateAuthorities([]string{"vault", "store"}) - require.NoError(m.T, err) - bac, err := cd.DeployOCRv2AccessController() - require.NoError(m.T, err) - rac, err := cd.DeployOCRv2AccessController() - require.NoError(m.T, err) - err = m.Client.WaitForEvents() - require.NoError(m.T, err) - - store, err := cd.DeployOCRv2Store(bac.Address()) - require.NoError(m.T, err) - - err = cd.CreateFeed("Feed", uint8(18), 10, 1024) - require.NoError(m.T, err) - - ocr2, err := cd.InitOCR2(bac.Address(), rac.Address()) - require.NoError(m.T, err) - - storeAuth := cd.Accounts.Authorities["store"].PublicKey.String() - err = bac.AddAccess(storeAuth) - require.NoError(m.T, err) - err = m.Client.WaitForEvents() - require.NoError(m.T, err) - - err = store.SetWriter(storeAuth) - require.NoError(m.T, err) - err = store.SetValidatorConfig(80000) - require.NoError(m.T, err) - err = m.Client.WaitForEvents() - require.NoError(m.T, err) - - var nodeCount int - if m.Common.IsK8s { - nodeCount = len(m.ContractsNodeSetup[i].NodesK8s) - } else { - nodeCount = len(m.ContractsNodeSetup[i].Nodes) - } - ocConfig, err := OffChainConfigParamsFromNodes(nodeCount, m.ContractsNodeSetup[i].NodeKeysBundle) - require.NoError(m.T, err) - - err = ocr2.Configure(ocConfig) - require.NoError(m.T, err) - m.Mu.Lock() - m.Contracts = append(m.Contracts, Contracts{ - BAC: bac, - RAC: rac, - OCR2: ocr2, - Store: store, - StoreAuth: storeAuth, - }) - m.Mu.Unlock() - return nil - }) - } - require.NoError(m.T, g.Wait()) - for i := 0; i < len(m.ContractsNodeSetup); i++ { - m.ContractsNodeSetup[i].OCR2 = m.Contracts[i].OCR2 - m.ContractsNodeSetup[i].Store = m.Contracts[i].Store - } + require.NoError(m.Config.T, err) } // CreateJobs creating OCR jobs and EA stubs func (m *OCRv2TestState) CreateJobs() { - var nodes []*client.ChainlinkClient - var mockInternalURL string - if m.Common.IsK8s { - nodes = m.GetChainlinkNodes() - mockInternalURL = m.Common.Env.URLs["qa_mock_adapter_internal"][0] - } else { - nodes = m.Common.DockerEnv.ClCluster.NodeAPIs() - mockInternalURL = m.Common.DockerEnv.Killgrave.InternalEndpoint + // Setting up RPC + c := rpc.New(*m.Config.TestConfig.Common.RPCURL) + wsc, err := ws.Connect(testcontext.Get(m.Config.T), *m.Config.TestConfig.Common.WsURL) + require.NoError(m.Config.T, err, "Error connecting to websocket client") + + relayConfig := job.JSONConfig{ + "nodeEndpointHTTP": m.Common.ChainDetails.RPCUrl, + "ocr2ProgramID": m.Common.ChainDetails.ProgramAddresses.OCR2, + "transmissionsID": m.Gauntlet.FeedAddress, + "storeProgramID": m.Common.ChainDetails.ProgramAddresses.Store, + "chainID": m.Common.ChainDetails.ChainID, + } + boostratInternalIP := m.Clients.ChainlinkClient.ChainlinkNodes[0].InternalIP() + bootstrapPeers := []client.P2PData{ + { + InternalIP: boostratInternalIP, + InternalPort: "6690", + PeerID: m.Clients.ChainlinkClient.NKeys[0].PeerID, + }, } - m.L.Info().Str("Url", mockInternalURL).Msg("Mock adapter url") - m.err = m.Common.CreateSolanaChainAndNode(nodes) - require.NoError(m.T, m.err) - m.err = CreateBridges(m.ContractsNodeSetup, mockInternalURL, m.Common.IsK8s) - require.NoError(m.T, m.err) - g := errgroup.Group{} - for i := 0; i < len(m.ContractsNodeSetup); i++ { - i := i - g.Go(func() error { - m.err = m.Common.CreateJobsForContract(m.ContractsNodeSetup[i]) - require.NoError(m.T, m.err) - return nil - }) + jobSpec := &client.OCR2TaskJobSpec{ + Name: fmt.Sprintf("sol-OCRv2-%s-%s", "bootstrap", uuid.New().String()), + JobType: "bootstrap", + OCR2OracleSpec: job.OCR2OracleSpec{ + ContractID: m.Gauntlet.OcrAddress, + Relay: m.Common.ChainDetails.ChainName, + RelayConfig: relayConfig, + P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, + OCRKeyBundleID: null.StringFrom(m.Clients.ChainlinkClient.NKeys[0].OCR2Key.Data.ID), + TransmitterID: null.StringFrom(m.Clients.ChainlinkClient.NKeys[0].TXKey.Data.ID), + ContractConfigConfirmations: 1, + ContractConfigTrackerPollInterval: models.Interval(15 * time.Second), + }, } - require.NoError(m.T, g.Wait()) -} - -func (m *OCRv2TestState) ValidateNoRoundsAfter(chaosStartTime time.Time) { - m.RoundsFound = 0 - for _, c := range m.Contracts { - m.LastRoundTime[c.OCR2.Address()] = chaosStartTime + sourceValueBridge := client.BridgeTypeAttributes{ + Name: "mockserver-bridge", + URL: fmt.Sprintf("%s/%s", m.Common.ChainDetails.MockserverURLInternal, m.Common.ChainDetails.MockServerEndpoint), + RequestData: "{}", } - gom := gomega.NewWithT(m.T) - gom.Consistently(func(g gomega.Gomega) { - for _, c := range m.Contracts { - _, timestamp, _, err := c.Store.GetLatestRoundData() - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - roundTime := time.Unix(int64(timestamp), 0) - g.Expect(roundTime.Before(m.LastRoundTime[c.OCR2.Address()])).Should(gomega.BeTrue()) - } - }, NewRoundCheckTimeout, NewRoundCheckPollInterval).Should(gomega.Succeed()) -} -type Answer struct { - Answer uint64 - Timestamp uint64 - Error error -} + observationSource := client.ObservationSourceSpecBridge(&sourceValueBridge) + bridgeInfo := BridgeInfo{ObservationSource: observationSource} -func (m *OCRv2TestState) ValidateRoundsAfter(chaosStartTime time.Time, timeout time.Duration, rounds int) { - m.RoundsFound = 0 - for _, c := range m.Contracts { - m.LastRoundTime[c.OCR2.Address()] = chaosStartTime - } - roundsFound := 0 - gom := gomega.NewWithT(m.T) - gom.Eventually(func(g gomega.Gomega) { - answers := make(map[string]*Answer) - for _, c := range m.Contracts { - answer, timestamp, _, err := c.Store.GetLatestRoundData() - g.Expect(err).ShouldNot(gomega.HaveOccurred()) - answers[c.OCR2.Address()] = &Answer{Answer: answer, Timestamp: timestamp, Error: err} - } - for ci, a := range answers { - answerTime := time.Unix(int64(a.Timestamp), 0) - if answerTime.After(m.LastRoundTime[ci]) { - m.LastRoundTime[ci] = answerTime - roundsFound++ - m.L.Debug().Str("Contract", ci).Interface("Answer", a).Int("RoundsFound", roundsFound).Msg("New answer found") - } else { - m.L.Debug().Str("Contract", ci).Interface("Answer", a).Msg("Answer has not changed") - } - } - g.Expect(roundsFound).To(gomega.BeNumerically(">=", rounds*len(m.Contracts))) - }, timeout, NewRoundCheckPollInterval).Should(gomega.Succeed()) -} - -func (m *OCRv2TestState) GenerateOnChainConfig(nodeKeys []client.NodeKeysBundle, vaultAddress string, proposalID string) (OCR2OnChainConfig, error) { - var oracles []Operator + err = m.Clients.ChainlinkClient.ChainlinkNodes[0].MustCreateBridge(&sourceValueBridge) + require.NoError(m.Config.T, err, "Error creating bridge") - for _, nodeKey := range nodeKeys { - oracles = append(oracles, Operator{ - Signer: strings.Replace(nodeKey.OCR2Key.Data.Attributes.OnChainPublicKey, "ocr2on_solana_", "", 1), - Transmitter: nodeKey.TXKey.Data.Attributes.PublicKey, - Payee: vaultAddress, - }) - } + _, err = m.Clients.ChainlinkClient.ChainlinkNodes[0].MustCreateJob(jobSpec) + require.NoError(m.Config.T, err, "Error creating job") - return OCR2OnChainConfig{ - Oracles: oracles, - F: 1, - ProposalID: proposalID, - }, nil -} - -func (m *OCRv2TestState) GenerateOffChainConfig( - nodeKeysBundle []client.NodeKeysBundle, - proposalID string, - reportingConfig ReportingPluginConfig, - deltaProgressNanoseconds int64, - deltaResendNanoseconds int64, - deltaRoundNanoseconds int64, - deltaGraceNanoseconds int64, - deltaStageNanoseconds int64, - rMax int, - maxDurationQueryNanoseconds int64, - maxDurationObservationNanoseconds int64, - maxDurationReportNanoseconds int64, - maxDurationShouldAcceptFinalizedReportNanoseconds int64, - maxDurationShouldTransmitAcceptedReportNanoseconds int64, - secret string, - -) OCROffChainConfig { - offchainPublicKeys := make([]string, len(nodeKeysBundle)) - peerIds := make([]string, len(nodeKeysBundle)) - configPublicKeys := make([]string, len(nodeKeysBundle)) - s := make([]int, len(nodeKeysBundle)) - - for i := range s { - s[i] = 1 - } - - for i, bundle := range nodeKeysBundle { - offchainPublicKeys[i] = strings.Replace(bundle.OCR2Key.Data.Attributes.OffChainPublicKey, "ocr2off_solana_", "", 1) - peerIds[i] = bundle.PeerID - configPublicKeys[i] = strings.Replace(bundle.OCR2Key.Data.Attributes.ConfigPublicKey, "ocr2cfg_solana_", "", 1) - } - - offChainConfig := OCROffChainConfig{ - ProposalID: proposalID, - OffchainConfig: OffchainConfig{ - DeltaProgressNanoseconds: deltaProgressNanoseconds, - DeltaResendNanoseconds: deltaResendNanoseconds, - DeltaRoundNanoseconds: deltaRoundNanoseconds, - DeltaGraceNanoseconds: deltaGraceNanoseconds, - DeltaStageNanoseconds: deltaStageNanoseconds, - RMax: rMax, - S: s, - OffchainPublicKeys: offchainPublicKeys, - PeerIds: peerIds, - ConfigPublicKeys: configPublicKeys, - ReportingPluginConfig: reportingConfig, - MaxDurationQueryNanoseconds: maxDurationQueryNanoseconds, - MaxDurationObservationNanoseconds: maxDurationObservationNanoseconds, - MaxDurationReportNanoseconds: maxDurationReportNanoseconds, - MaxDurationShouldAcceptFinalizedReportNanoseconds: maxDurationShouldAcceptFinalizedReportNanoseconds, - MaxDurationShouldTransmitAcceptedReportNanoseconds: maxDurationShouldTransmitAcceptedReportNanoseconds, - }, - UserSecret: secret, - } - - return offChainConfig -} + for nIdx, node := range m.Clients.ChainlinkClient.ChainlinkNodes { + // Skipping bootstrap + if nIdx == 0 { + continue + } + if *m.Config.TestConfig.Common.Network == "localnet" { + err = m.Clients.SolanaClient.Fund(m.Clients.ChainlinkClient.NKeys[nIdx].TXKey.Data.ID, big.NewFloat(1e4)) + require.NoError(m.Config.T, err, "Error sending funds") + } else { + err = solclient.SendFunds(*m.Config.TestConfig.Common.PrivateKey, m.Clients.ChainlinkClient.NKeys[nIdx].TXKey.Data.ID, 100000000, c, wsc) + require.NoError(m.Config.T, err, "Error sending funds") + } -func (m *OCRv2TestState) GeneratePayees(nodeKeys []client.NodeKeysBundle, vaultAddress string, proposalID string) PayeeConfig { - var operators []Operator - for _, key := range nodeKeys { - operators = append(operators, Operator{ - Signer: strings.Replace(key.OCR2Key.Data.Attributes.OnChainPublicKey, "ocr2on_solana_", "", 1), - Transmitter: key.TXKey.Data.Attributes.PublicKey, - Payee: vaultAddress, - }) - } + sourceValueBridge := client.BridgeTypeAttributes{ + Name: "mockserver-bridge", + URL: fmt.Sprintf("%s/%s", m.Common.ChainDetails.MockserverURLInternal, m.Common.ChainDetails.MockServerEndpoint), + RequestData: "{}", + } - return PayeeConfig{ - Operators: operators, - ProposalID: proposalID, + _, err := node.CreateBridge(&sourceValueBridge) + require.NoError(m.Config.T, err, "Error creating bridge") + + jobSpec := &client.OCR2TaskJobSpec{ + Name: fmt.Sprintf("sol-OCRv2-%d-%s", nIdx, uuid.New().String()), + JobType: "offchainreporting2", + ObservationSource: bridgeInfo.ObservationSource, + OCR2OracleSpec: job.OCR2OracleSpec{ + ContractID: m.Gauntlet.OcrAddress, + Relay: m.Common.ChainDetails.ChainName, + RelayConfig: relayConfig, + P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, + OCRKeyBundleID: null.StringFrom(m.Clients.ChainlinkClient.NKeys[nIdx].OCR2Key.Data.ID), + TransmitterID: null.StringFrom(m.Clients.ChainlinkClient.NKeys[nIdx].TXKey.Data.ID), + ContractConfigConfirmations: 1, + ContractConfigTrackerPollInterval: models.Interval(15 * time.Second), + PluginType: "median", + PluginConfig: PluginConfigToTomlFormat(observationSource), + }, + } + _, err = node.MustCreateJob(jobSpec) + require.NoError(m.Config.T, err, "Error creating job") } } -func (m *OCRv2TestState) GenerateProposalAcceptConfig( - proposalID string, - version int, - f int, - oracles []Operator, - offChainConfig OffchainConfig, - randomSecret string, - -) ProposalAcceptConfig { - return ProposalAcceptConfig{ - ProposalID: proposalID, - Version: version, - F: f, - Oracles: oracles, - OffchainConfig: offChainConfig, - RandomSecret: randomSecret, +func (m *OCRv2TestState) SetChainlinkNodes() { + // retrieve client from K8s client + chainlinkNodes := []*client.ChainlinkClient{} + if *m.Config.TestConfig.Common.InsideK8s { + for i := range m.Clients.ChainlinkClient.ChainlinkClientK8s { + chainlinkNodes = append(chainlinkNodes, m.Clients.ChainlinkClient.ChainlinkClientK8s[i].ChainlinkClient) + } + } else { + chainlinkNodes = append(chainlinkNodes, m.Clients.ChainlinkClient.ChainlinkClientDocker.NodeAPIs()...) } + m.Clients.ChainlinkClient.ChainlinkNodes = chainlinkNodes } -func (m *OCRv2TestState) ConfigureGauntlet(secret string) map[string]string { - err := os.Setenv("SECRET", secret) - if err != nil { - panic("Error setting SECRET") - } - rpcURL, exists := os.LookupEnv("RPC_URL") - if !exists { - panic("Please define RPC_URL") - } - - wsURL, exists := os.LookupEnv("WS_URL") - if !exists { - panic("Please define WS_URL") +func formatBuffer(buf []byte) string { + if len(buf) == 0 { + return "" } - privateKey, exists := os.LookupEnv("PRIVATE_KEY") - if !exists { - panic("Please define PRIVATE_KEY") - } - programIDOCR2, exists := os.LookupEnv("PROGRAM_ID_OCR2") - if !exists { - panic("Please define PROGRAM_ID_OCR2") - } - - programIDAccessController, exists := os.LookupEnv("PROGRAM_ID_ACCESS_CONTROLLER") - if !exists { - panic("Please define PROGRAM_ID_ACCESS_CONTROLLER") + result := fmt.Sprintf("%d", buf[0]) + for _, b := range buf[1:] { + result += fmt.Sprintf(",%d", b) } - - programIDStore, exists := os.LookupEnv("PROGRAM_ID_STORE") - if !exists { - panic("Please define PROGRAM_ID_STORE") - } - - linkToken, exists := os.LookupEnv("LINK_TOKEN") - if !exists { - panic("Please define LINK_TOKEN") - } - - vault, exists := os.LookupEnv("VAULT_ADDRESS") - if !exists { - panic("Please define VAULT_ADDRESS") - } - - return map[string]string{ - "NODE_URL": rpcURL, - "WS_URL": wsURL, - "PRIVATE_KEY": privateKey, - "PROGRAM_ID_OCR2": programIDOCR2, - "PROGRAM_ID_ACCESS_CONTROLLER": programIDAccessController, - "PROGRAM_ID_STORE": programIDStore, - "LINK": linkToken, - "VAULT": vault, - } -} - -// GauntletEnvToRemoteRunner Setup the environment variables that will be needed inside the remote runner -func (m *OCRv2TestState) GauntletEnvToRemoteRunner() { - err := osutil.SetupEnvVarsForRemoteRunner([]string{ - "RPC_URL", - "WS_URL", - "PRIVATE_KEY", - "PROGRAM_ID_OCR2", - "PROGRAM_ID_ACCESS_CONTROLLER", - "PROGRAM_ID_STORE", - "LINK_TOKEN", - "VAULT_ADDRESS", - }) - require.NoError(m.T, err) + return result } -func (m *OCRv2TestState) GetChainlinkNodes() []*client.ChainlinkClient { - // retrieve client from K8s client - chainlinkNodes := []*client.ChainlinkClient{} - for i := range m.ChainlinkNodesK8s { - chainlinkNodes = append(chainlinkNodes, m.ChainlinkNodesK8s[i].ChainlinkClient) +func GetLatestRound(transmissions []gauntlet.Transmission) gauntlet.Transmission { + highestRound := transmissions[0] + for _, t := range transmissions[1:] { + if t.RoundID > highestRound.RoundID { + highestRound = t + } } - return chainlinkNodes + return highestRound } diff --git a/integration-tests/config/config.go b/integration-tests/config/config.go new file mode 100644 index 000000000..232dfa5d3 --- /dev/null +++ b/integration-tests/config/config.go @@ -0,0 +1,41 @@ +package config + +type Config struct { + ChainName string + ChainID string + RPCUrl string + WSUrl string + ProgramAddresses *ProgramAddresses + PrivateKey string +} + +type ProgramAddresses struct { + OCR2 string + AccessController string + Store string +} + +func DevnetConfig() *Config { + return &Config{ + ChainName: "solana", + ChainID: "devnet", + // Will be overridden if set in toml + RPCUrl: "https://api.devnet.solana.com", + WSUrl: "wss://api.devnet.solana.com/", + } +} + +func LocalNetConfig() *Config { + return &Config{ + ChainName: "solana", + ChainID: "localnet", + // Will be overridden if set in toml + RPCUrl: "http://sol:8899", + WSUrl: "ws://sol:8900", + ProgramAddresses: &ProgramAddresses{ + OCR2: "E3j24rx12SyVsG6quKuZPbQqZPkhAUCh8Uek4XrKYD2x", + AccessController: "2ckhep7Mvy1dExenBqpcdevhRu7CLuuctMcx7G9mWEvo", + Store: "9kRNTZmoZSiTBuXC62dzK9E7gC7huYgcmRRhYv3i4osC", + }, + } +} diff --git a/integration-tests/config/ocr2_config.go b/integration-tests/config/ocr2_config.go new file mode 100644 index 000000000..6524257ec --- /dev/null +++ b/integration-tests/config/ocr2_config.go @@ -0,0 +1,195 @@ +package config + +import ( + "sort" + "strings" + + "github.com/smartcontractkit/chainlink/integration-tests/client" +) + +type OCR2Config struct { + OnChainConfig *OCR2OnChainConfig + OffChainConfig *OCROffChainConfig + PayeeConfig *PayeeConfig + ProposalAcceptConfig *ProposalAcceptConfig + NodeKeys []client.NodeKeysBundle + VaultAddress string + Secret string + ProposalID string +} + +type OCR2OnChainConfig struct { + Oracles []Operator `json:"oracles"` + F int `json:"f"` + ProposalID string `json:"proposalId"` +} + +type OffchainConfig struct { + DeltaProgressNanoseconds int64 `json:"deltaProgressNanoseconds"` + DeltaResendNanoseconds int64 `json:"deltaResendNanoseconds"` + DeltaRoundNanoseconds int64 `json:"deltaRoundNanoseconds"` + DeltaGraceNanoseconds int64 `json:"deltaGraceNanoseconds"` + DeltaStageNanoseconds int64 `json:"deltaStageNanoseconds"` + RMax int `json:"rMax"` + S []int `json:"s"` + OffchainPublicKeys []string `json:"offchainPublicKeys"` + PeerIds []string `json:"peerIds"` + ReportingPluginConfig ReportingPluginConfig `json:"reportingPluginConfig"` + MaxDurationQueryNanoseconds int64 `json:"maxDurationQueryNanoseconds"` + MaxDurationObservationNanoseconds int64 `json:"maxDurationObservationNanoseconds"` + MaxDurationReportNanoseconds int64 `json:"maxDurationReportNanoseconds"` + MaxDurationShouldAcceptFinalizedReportNanoseconds int64 `json:"maxDurationShouldAcceptFinalizedReportNanoseconds"` + MaxDurationShouldTransmitAcceptedReportNanoseconds int64 `json:"maxDurationShouldTransmitAcceptedReportNanoseconds"` + ConfigPublicKeys []string `json:"configPublicKeys"` +} + +type ReportingPluginConfig struct { + AlphaReportInfinite bool `json:"alphaReportInfinite"` + AlphaReportPpb int `json:"alphaReportPpb"` + AlphaAcceptInfinite bool `json:"alphaAcceptInfinite"` + AlphaAcceptPpb int `json:"alphaAcceptPpb"` + DeltaCNanoseconds int `json:"deltaCNanoseconds"` +} + +// TODO - Decouple all OCR2 config structs to be reusable between chains +type OCROffChainConfig struct { + ProposalID string `json:"proposalId"` + OffchainConfig OffchainConfig `json:"offchainConfig"` + UserSecret string `json:"userSecret"` +} + +type Operator struct { + Signer string `json:"signer"` + Transmitter string `json:"transmitter"` + Payee string `json:"payee"` +} + +type PayeeConfig struct { + Operators []Operator `json:"operators"` + ProposalID string `json:"proposalId"` +} + +type ProposalAcceptConfig struct { + ProposalID string `json:"proposalId"` + Version int `json:"version"` + F int `json:"f"` + Oracles []Operator `json:"oracles"` + OffchainConfig OffchainConfig `json:"offchainConfig"` + RandomSecret string `json:"randomSecret"` +} + +type OCR2TransmitConfig struct { + MinAnswer string `json:"minAnswer"` + MaxAnswer string `json:"maxAnswer"` + Transmissions string `json:"transmissions"` +} + +type OCR2BillingConfig struct { + ObservationPaymentGjuels int `json:"ObservationPaymentGjuels"` + TransmissionPaymentGjuels int `json:"TransmissionPaymentGjuels"` +} + +type StoreFeedConfig struct { + Store string `json:"store"` + Granularity int `json:"granularity"` + LiveLength int `json:"liveLength"` + Decimals int `json:"decimals"` + Description string `json:"description"` +} + +type StoreWriterConfig struct { + Transmissions string `json:"transmissions"` +} + +func NewOCR2Config(nodeKeys []client.NodeKeysBundle, proposalID string, vaultAddress string, secret string) *OCR2Config { + var oracles []Operator + + nodeKeysSorted := make([]client.NodeKeysBundle, len(nodeKeys)) + copy(nodeKeysSorted, nodeKeys) + + // We have to sort by on_chain_pub_key for the config digest + sort.Slice(nodeKeysSorted, func(i, j int) bool { + return nodeKeysSorted[i].OCR2Key.Data.Attributes.OnChainPublicKey < nodeKeysSorted[j].OCR2Key.Data.Attributes.OnChainPublicKey + }) + + for _, nodeKey := range nodeKeysSorted { + oracles = append(oracles, Operator{ + Signer: strings.Replace(nodeKey.OCR2Key.Data.Attributes.OnChainPublicKey, "ocr2on_solana_", "", 1), + Transmitter: nodeKey.TXKey.Data.Attributes.PublicKey, + Payee: vaultAddress, + }) + } + + return &OCR2Config{ + OnChainConfig: &OCR2OnChainConfig{ + Oracles: oracles, + F: 1, + ProposalID: proposalID, + }, + OffChainConfig: &OCROffChainConfig{}, + PayeeConfig: &PayeeConfig{}, + ProposalAcceptConfig: &ProposalAcceptConfig{}, + NodeKeys: nodeKeysSorted, + VaultAddress: vaultAddress, + Secret: secret, + ProposalID: proposalID, + } +} + +func (o *OCR2Config) Default() { + o.OffChainConfig.OffchainConfig.ReportingPluginConfig = ReportingPluginConfig{ + AlphaReportInfinite: false, + AlphaReportPpb: 0, + AlphaAcceptInfinite: false, + AlphaAcceptPpb: 0, + DeltaCNanoseconds: 0, + } + offchainPublicKeys := make([]string, len(o.NodeKeys)) + peerIds := make([]string, len(o.NodeKeys)) + configPublicKeys := make([]string, len(o.NodeKeys)) + s := make([]int, len(o.NodeKeys)) + + for i := range s { + s[i] = 1 + } + + for i, key := range o.NodeKeys { + offchainPublicKeys[i] = strings.Replace(key.OCR2Key.Data.Attributes.OffChainPublicKey, "ocr2off_solana_", "", 1) + peerIds[i] = key.PeerID + configPublicKeys[i] = strings.Replace(key.OCR2Key.Data.Attributes.ConfigPublicKey, "ocr2cfg_solana_", "", 1) + } + o.OffChainConfig = &OCROffChainConfig{ + UserSecret: o.Secret, + ProposalID: o.ProposalID, + OffchainConfig: OffchainConfig{ + DeltaProgressNanoseconds: int64(20000000000), + DeltaResendNanoseconds: int64(50000000000), + DeltaRoundNanoseconds: int64(1000000000), + DeltaGraceNanoseconds: int64(4000000000), + DeltaStageNanoseconds: int64(50000000000), + RMax: 3, + S: s, + OffchainPublicKeys: offchainPublicKeys, + PeerIds: peerIds, + ConfigPublicKeys: configPublicKeys, + ReportingPluginConfig: o.OffChainConfig.OffchainConfig.ReportingPluginConfig, + MaxDurationQueryNanoseconds: int64(3000000000), + MaxDurationObservationNanoseconds: int64(3000000000), + MaxDurationReportNanoseconds: int64(100000000), + MaxDurationShouldAcceptFinalizedReportNanoseconds: int64(100000000), + MaxDurationShouldTransmitAcceptedReportNanoseconds: int64(100000000), + }, + } + o.PayeeConfig = &PayeeConfig{ + Operators: o.OnChainConfig.Oracles, + ProposalID: o.ProposalID, + } + o.ProposalAcceptConfig = &ProposalAcceptConfig{ + ProposalID: o.ProposalID, + Version: 2, + F: 1, + Oracles: o.OnChainConfig.Oracles, + OffchainConfig: o.OffChainConfig.OffchainConfig, + RandomSecret: o.Secret, + } +} diff --git a/integration-tests/docker/testenv/sol.go b/integration-tests/docker/testenv/sol.go index 501b543ed..9233d1cde 100644 --- a/integration-tests/docker/testenv/sol.go +++ b/integration-tests/docker/testenv/sol.go @@ -10,9 +10,12 @@ import ( "testing" "time" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" "github.com/google/uuid" "github.com/rs/zerolog" "github.com/rs/zerolog/log" + tc "github.com/testcontainers/testcontainers-go" tcwait "github.com/testcontainers/testcontainers-go/wait" "golang.org/x/exp/slices" @@ -39,7 +42,7 @@ commitment: finalized ` var idJSONRaw = ` -[205,246,252,222,193,57,3,13,164,146,52,162,143,135,8,254,37,4,250,48,137,61,49,57,187,210,209,118,108,125,81,235,136,69,202,17,24,209,91,226,206,92,80,45,83,14,222,113,229,190,94,142,188,124,102,122,15,246,40,190,24,247,69,133] +[94,214,238,83,144,226,75,151,226,20,5,188,42,110,64,180,196,244,6,199,29,231,108,112,67,175,110,182,3,242,102,83,103,72,221,132,137,219,215,192,224,17,146,227,94,4,173,67,173,207,11,239,127,174,101,204,65,225,90,88,224,45,205,117] ` type Solana struct { @@ -50,15 +53,19 @@ type Solana struct { InternalWsURL string t *testing.T l zerolog.Logger + Image string + PublicKey string } -func NewSolana(networks []string, opts ...test_env.EnvComponentOption) *Solana { +func NewSolana(networks []string, devnetImage string, publicKey string, opts ...test_env.EnvComponentOption) *Solana { ms := &Solana{ EnvComponent: test_env.EnvComponent{ ContainerName: fmt.Sprintf("%s-%s", "solana", uuid.NewString()[0:8]), Networks: networks, }, - l: log.Logger, + l: log.Logger, + Image: devnetImage, + PublicKey: publicKey, } for _, opt := range opts { opt(&ms.EnvComponent) @@ -158,7 +165,7 @@ func (s *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.Con return &tc.ContainerRequest{ Name: s.ContainerName, - Image: "solanalabs/solana:v1.17.33", + Image: s.Image, ExposedPorts: []string{test_env.NatPortFormat(SolHTTPPort), test_env.NatPortFormat(SolWSPort)}, Env: map[string]string{ "SERVER_PORT": "1080", @@ -167,13 +174,13 @@ func (s *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.Con WaitingFor: tcwait.ForLog("Processed Slot: 1"). WithStartupTimeout(30 * time.Second). WithPollInterval(100 * time.Millisecond), - Mounts: tc.ContainerMounts{ - tc.ContainerMount{ - Source: tc.GenericBindMountSource{ //nolint:staticcheck - HostPath: utils.ContractsDir, - }, - Target: "/programs", - }, + HostConfigModifier: func(hostConfig *container.HostConfig) { + hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: utils.ContractsDir, + Target: "/programs", + ReadOnly: false, + }) }, LifecycleHooks: []tc.ContainerLifecycleHooks{ { @@ -189,7 +196,7 @@ func (s *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.Con }, }, }, - Entrypoint: []string{"sh", "-c", "mkdir -p /root/.config/solana/cli && solana-test-validator -r --mint=AAxAoGfkbWnbgsiQeAanwUvjv6bQrM5JS8Vxv1ckzVxg " + inactiveFeatures.CLIString()}, + Entrypoint: []string{"sh", "-c", "mkdir -p /root/.config/solana/cli && solana-test-validator -r --mint=" + s.PublicKey + " " + inactiveFeatures.CLIString()}, }, nil } diff --git a/integration-tests/gauntlet/gauntlet_solana.go b/integration-tests/gauntlet/gauntlet_solana.go index 3c74464ac..c4f8bcb59 100644 --- a/integration-tests/gauntlet/gauntlet_solana.go +++ b/integration-tests/gauntlet/gauntlet_solana.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - "github.com/smartcontractkit/chainlink-solana/integration-tests/common" + ocr2_config "github.com/smartcontractkit/chainlink-solana/integration-tests/config" "github.com/smartcontractkit/chainlink-testing-framework/gauntlet" ) @@ -15,7 +15,8 @@ var ( ) type SolanaGauntlet struct { - dir string + Dir string + NetworkFilePath string G *gauntlet.Gauntlet gr *Response options *gauntlet.ExecCommandOptions @@ -25,29 +26,9 @@ type SolanaGauntlet struct { FeedAddress string OcrAddress string ProposalAddress string -} - -type StoreFeedConfig struct { - Store string `json:"store"` - Granularity int `json:"granularity"` - LiveLength int `json:"liveLength"` - Decimals int `json:"decimals"` - Description string `json:"description"` -} - -type OCR2Config struct { - MinAnswer string `json:"minAnswer"` - MaxAnswer string `json:"maxAnswer"` - Transmissions string `json:"transmissions"` -} - -type OCR2BillingConfig struct { - ObservationPaymentGjuels int `json:"ObservationPaymentGjuels"` - TransmissionPaymentGjuels int `json:"TransmissionPaymentGjuels"` -} - -type StoreWriterConfig struct { - Transmissions string `json:"transmissions"` + OCR2Config *ocr2_config.OCR2Config + LinkAddress string + VaultAddress string } // Response Default response output for starknet gauntlet commands @@ -70,6 +51,7 @@ type Response struct { Data struct { Proposal *string `json:"proposal,omitempty"` LatestTransmissions *[]Transmission `json:"latestTransmissions,omitempty"` + Vault *string `json:"vault,omitempty"` } } @@ -88,13 +70,20 @@ func NewSolanaGauntlet(workingDir string) (*SolanaGauntlet, error) { return nil, err } sg = &SolanaGauntlet{ - dir: workingDir, - G: g, - gr: &Response{}, + Dir: workingDir, + NetworkFilePath: workingDir + "/packages/gauntlet-solana-contracts/networks", + G: g, + gr: &Response{}, options: &gauntlet.ExecCommandOptions{ ErrHandling: []string{}, CheckErrorsInRead: true, }, + OCR2Config: &ocr2_config.OCR2Config{ + OnChainConfig: &ocr2_config.OCR2OnChainConfig{}, + OffChainConfig: &ocr2_config.OCROffChainConfig{}, + PayeeConfig: &ocr2_config.PayeeConfig{}, + ProposalAcceptConfig: &ocr2_config.ProposalAcceptConfig{}, + }, } return sg, nil } @@ -102,7 +91,7 @@ func NewSolanaGauntlet(workingDir string) (*SolanaGauntlet, error) { // FetchGauntletJSONOutput Parse gauntlet json response that is generated after yarn gauntlet command execution func (sg *SolanaGauntlet) FetchGauntletJSONOutput() (*Response, error) { var payload = &Response{} - gauntletOutput, err := os.ReadFile(sg.dir + "/report.json") + gauntletOutput, err := os.ReadFile(sg.Dir + "/report.json") if err != nil { return payload, err } @@ -118,7 +107,7 @@ func (sg *SolanaGauntlet) SetupNetwork(args map[string]string) error { for key, arg := range args { sg.G.AddNetworkConfigVar(key, arg) } - err := sg.G.WriteNetworkConfigMap(sg.dir + "/packages/gauntlet-solana-contracts/networks") + err := sg.G.WriteNetworkConfigMap(sg.NetworkFilePath) if err != nil { return err } @@ -148,6 +137,21 @@ func (sg *SolanaGauntlet) InitializeAccessController() (string, error) { return sg.gr.Responses[0].Contract, nil } +func (sg *SolanaGauntlet) DeployLinkToken() error { + _, err := sg.G.ExecCommand([]string{"token:deploy"}, *sg.options) + if err != nil { + return err + } + sg.gr, err = sg.FetchGauntletJSONOutput() + if err != nil { + return err + } + sg.VaultAddress = *sg.gr.Data.Vault + sg.LinkAddress = sg.gr.Responses[0].Contract + + return nil +} + func (sg *SolanaGauntlet) InitializeStore(billingController string) (string, error) { _, err := sg.G.ExecCommand([]string{"store:initialize", fmt.Sprintf("--accessController=%s", billingController)}, *sg.options) if err != nil { @@ -160,7 +164,7 @@ func (sg *SolanaGauntlet) InitializeStore(billingController string) (string, err return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) StoreCreateFeed(length int, feedConfig *StoreFeedConfig) (string, error) { +func (sg *SolanaGauntlet) StoreCreateFeed(length int, feedConfig *ocr2_config.StoreFeedConfig) (string, error) { config, err := json.Marshal(feedConfig) if err != nil { return "", err @@ -189,7 +193,7 @@ func (sg *SolanaGauntlet) StoreSetValidatorConfig(feedAddress string, threshold return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) InitializeOCR2(requesterAccessController string, billingAccessController string, ocrConfig *OCR2Config) (string, error) { +func (sg *SolanaGauntlet) InitializeOCR2(requesterAccessController string, billingAccessController string, ocrConfig *ocr2_config.OCR2TransmitConfig) (string, error) { config, err := json.Marshal(ocrConfig) if err != nil { return "", err @@ -210,7 +214,7 @@ func (sg *SolanaGauntlet) InitializeOCR2(requesterAccessController string, billi return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) StoreSetWriter(storeConfig *StoreWriterConfig, ocrAddress string) (string, error) { +func (sg *SolanaGauntlet) StoreSetWriter(storeConfig *ocr2_config.StoreWriterConfig, ocrAddress string) (string, error) { config, err := json.Marshal(storeConfig) if err != nil { return "", err @@ -234,7 +238,7 @@ func (sg *SolanaGauntlet) StoreSetWriter(storeConfig *StoreWriterConfig, ocrAddr return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) OCR2SetBilling(ocr2BillingConfig *OCR2BillingConfig, ocrAddress string) (string, error) { +func (sg *SolanaGauntlet) OCR2SetBilling(ocr2BillingConfig *ocr2_config.OCR2BillingConfig, ocrAddress string) (string, error) { config, err := json.Marshal(ocr2BillingConfig) if err != nil { return "", err @@ -277,12 +281,11 @@ func (sg *SolanaGauntlet) OCR2CreateProposal(version int) (string, error) { return *sg.gr.Data.Proposal, nil } -func (sg *SolanaGauntlet) ProposeOnChainConfig(proposalID string, onChainConfig common.OCR2OnChainConfig, ocrFeedAddress string) (string, error) { +func (sg *SolanaGauntlet) ProposeOnChainConfig(proposalID string, onChainConfig ocr2_config.OCR2OnChainConfig, ocrFeedAddress string) (string, error) { config, err := json.Marshal(onChainConfig) if err != nil { return "", err } - _, err = sg.G.ExecCommand([]string{ "ocr2:propose_config", fmt.Sprintf("--proposalId=%s", proposalID), @@ -303,7 +306,7 @@ func (sg *SolanaGauntlet) ProposeOnChainConfig(proposalID string, onChainConfig return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) ProposeOffChainConfig(proposalID string, offChainConfig common.OCROffChainConfig, ocrFeedAddress string) (string, error) { +func (sg *SolanaGauntlet) ProposeOffChainConfig(proposalID string, offChainConfig ocr2_config.OCROffChainConfig, ocrFeedAddress string) (string, error) { config, err := json.Marshal(offChainConfig) if err != nil { return "", err @@ -329,7 +332,7 @@ func (sg *SolanaGauntlet) ProposeOffChainConfig(proposalID string, offChainConfi return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) ProposePayees(proposalID string, payeesConfig common.PayeeConfig, ocrFeedAddress string) (string, error) { +func (sg *SolanaGauntlet) ProposePayees(proposalID string, payeesConfig ocr2_config.PayeeConfig, ocrFeedAddress string) (string, error) { config, err := json.Marshal(payeesConfig) if err != nil { return "", err @@ -374,7 +377,7 @@ func (sg *SolanaGauntlet) FinalizeProposal(proposalID string) (string, error) { return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) AcceptProposal(proposalID string, secret string, proposalAcceptConfig common.ProposalAcceptConfig, ocrFeedAddres string) (string, error) { +func (sg *SolanaGauntlet) AcceptProposal(proposalID string, secret string, proposalAcceptConfig ocr2_config.ProposalAcceptConfig, ocrFeedAddres string) (string, error) { config, err := json.Marshal(proposalAcceptConfig) if err != nil { return "", err @@ -442,7 +445,7 @@ func (sg *SolanaGauntlet) DeployOCR2() (string, error) { if err != nil { return "", err } - storeConfig := &StoreFeedConfig{ + storeConfig := &ocr2_config.StoreFeedConfig{ Store: sg.StoreAddress, Granularity: 1, LiveLength: 10, @@ -460,7 +463,7 @@ func (sg *SolanaGauntlet) DeployOCR2() (string, error) { return "", err } - ocr2Config := &OCR2Config{ + ocr2Config := &ocr2_config.OCR2TransmitConfig{ MinAnswer: "0", MaxAnswer: "10000000000", Transmissions: sg.FeedAddress, @@ -471,14 +474,14 @@ func (sg *SolanaGauntlet) DeployOCR2() (string, error) { return "", err } - storeWriter := &StoreWriterConfig{Transmissions: sg.FeedAddress} + storeWriter := &ocr2_config.StoreWriterConfig{Transmissions: sg.FeedAddress} _, err = sg.StoreSetWriter(storeWriter, sg.OcrAddress) if err != nil { return "", err } - ocr2BillingConfig := &OCR2BillingConfig{ + ocr2BillingConfig := &ocr2_config.OCR2BillingConfig{ ObservationPaymentGjuels: 1, TransmissionPaymentGjuels: 1, } @@ -492,18 +495,23 @@ func (sg *SolanaGauntlet) DeployOCR2() (string, error) { if err != nil { return "", err } + sg.OCR2Config.OnChainConfig.ProposalID = sg.ProposalAddress + sg.OCR2Config.OffChainConfig.ProposalID = sg.ProposalAddress + sg.OCR2Config.PayeeConfig.ProposalID = sg.ProposalAddress + sg.OCR2Config.ProposalAcceptConfig.ProposalID = sg.ProposalAddress + return "", nil } -func (sg *SolanaGauntlet) ConfigureOCR2(onChainConfig common.OCR2OnChainConfig, offChainConfig common.OCROffChainConfig, payees common.PayeeConfig, proposalAccept common.ProposalAcceptConfig) error { - _, err := sg.ProposeOnChainConfig(sg.ProposalAddress, onChainConfig, sg.OcrAddress) +func (sg *SolanaGauntlet) ConfigureOCR2() error { + _, err := sg.ProposeOnChainConfig(sg.ProposalAddress, *sg.OCR2Config.OnChainConfig, sg.OcrAddress) if err != nil { return err } - _, err = sg.ProposeOffChainConfig(sg.ProposalAddress, offChainConfig, sg.OcrAddress) + _, err = sg.ProposeOffChainConfig(sg.ProposalAddress, *sg.OCR2Config.OffChainConfig, sg.OcrAddress) if err != nil { return err } - _, err = sg.ProposePayees(sg.ProposalAddress, payees, sg.OcrAddress) + _, err = sg.ProposePayees(sg.ProposalAddress, *sg.OCR2Config.PayeeConfig, sg.OcrAddress) if err != nil { return err } @@ -511,7 +519,7 @@ func (sg *SolanaGauntlet) ConfigureOCR2(onChainConfig common.OCR2OnChainConfig, if err != nil { return err } - _, err = sg.AcceptProposal(sg.ProposalAddress, "this is an testing only secret", proposalAccept, sg.OcrAddress) + _, err = sg.AcceptProposal(sg.ProposalAddress, sg.OCR2Config.OffChainConfig.UserSecret, *sg.OCR2Config.ProposalAcceptConfig, sg.OcrAddress) if err != nil { return err } diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 4657015b2..3bf21f6cb 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -5,24 +5,28 @@ go 1.21.7 replace github.com/smartcontractkit/chainlink-solana => ../ require ( + github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df + github.com/docker/docker v25.0.2+incompatible github.com/gagliardetto/binary v0.7.7 github.com/gagliardetto/solana-go v1.8.4 + github.com/go-resty/resty/v2 v2.11.0 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 - github.com/onsi/gomega v1.30.0 + github.com/pelletier/go-toml/v2 v2.1.1 github.com/rs/zerolog v1.30.0 github.com/smartcontractkit/chainlink-common v0.1.7-0.20240516150131-e1be553a9d10 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240510181707-46b1311a5a83 - github.com/smartcontractkit/chainlink-testing-framework v1.28.12 + github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240515225456-aeb9f4d50d65 github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240515225456-aeb9f4d50d65 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c + github.com/smartcontractkit/seth v1.0.9 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.28.0 - go.uber.org/zap v1.26.0 golang.org/x/crypto v0.22.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/sync v0.6.0 + golang.org/x/text v0.14.0 gopkg.in/guregu/null.v4 v4.0.0 ) @@ -66,7 +70,6 @@ require ( github.com/aws/constructs-go/constructs/v10 v10.1.255 // indirect github.com/aws/jsii-runtime-go v1.75.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect @@ -125,7 +128,6 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v25.0.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dominikbraun/graph v0.23.0 // indirect @@ -177,7 +179,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-resty/resty/v2 v2.11.0 // indirect github.com/go-webauthn/webauthn v0.9.4 // indirect github.com/go-webauthn/x v0.1.5 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -314,6 +315,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/onsi/ginkgo/v2 v2.13.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/opencontainers/runc v1.1.10 // indirect @@ -324,7 +326,6 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect @@ -364,7 +365,6 @@ require ( github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240508155030-1024f2b55c69 // indirect github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240328204215-ac91f55f1449 // indirect github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 // indirect - github.com/smartcontractkit/seth v1.0.9 // indirect github.com/smartcontractkit/tdh2/go/ocr2/decryptionplugin v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/tdh2/go/tdh2 v0.0.0-20230906073235-9e478e5e19f1 // indirect github.com/smartcontractkit/wasp v0.4.7 // indirect @@ -431,6 +431,7 @@ require ( go.uber.org/goleak v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect + go.uber.org/zap v1.26.0 // indirect go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect golang.org/x/arch v0.7.0 // indirect golang.org/x/mod v0.15.0 // indirect @@ -438,7 +439,6 @@ require ( golang.org/x/oauth2 v0.17.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/term v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.18.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect diff --git a/integration-tests/go.sum b/integration-tests/go.sum index d50d05972..79c3005cc 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1416,8 +1416,8 @@ github.com/smartcontractkit/chainlink-feeds v0.0.0-20240422130241-13c17a91b2ab h github.com/smartcontractkit/chainlink-feeds v0.0.0-20240422130241-13c17a91b2ab/go.mod h1:RPUY7r8GxgzXxS1ijtU1P/fpJomOXztXgUbEziNmbCA= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240508155030-1024f2b55c69 h1:ssh/w3oXWu+C6bE88GuFRC1+0Bx/4ihsbc80XMLrl2k= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240508155030-1024f2b55c69/go.mod h1:VsfjhvWgjxqWja4q+FlXEtX5lu8BSxn10xRo6gi948g= -github.com/smartcontractkit/chainlink-testing-framework v1.28.12 h1:15ssos9DvWekvj6JjmiPjTYsj/uw12HvTWlm1FHdYaA= -github.com/smartcontractkit/chainlink-testing-framework v1.28.12/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= +github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4 h1:XorHCAKux5+/jYsnGLRYOMBSW7Q3pKZJaxqRCZOW/UU= +github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240328204215-ac91f55f1449 h1:fX/xmGm1GBsD1ZZnooNT+eWA0hiTAqFlHzOC5CY4dy8= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240328204215-ac91f55f1449/go.mod h1:DC8sQMyTlI/44UCTL8QWFwb0bYNoXCfjwCv2hMivYZU= github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 h1:LQmRsrzzaYYN3wEU1l5tWiccznhvbyGnu2N+wHSXZAo= diff --git a/integration-tests/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 53bcf2831..f9c4f09d6 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -3,32 +3,19 @@ package smoke import ( "fmt" "maps" - "sort" "testing" "time" - "github.com/gagliardetto/solana-go/rpc" - "github.com/gagliardetto/solana-go/rpc/ws" - "github.com/google/uuid" - "github.com/lib/pq" - "github.com/stretchr/testify/assert" + "github.com/rs/zerolog/log" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - "gopkg.in/guregu/null.v4" - tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" - - "github.com/smartcontractkit/chainlink-testing-framework/logging" - "github.com/smartcontractkit/chainlink-testing-framework/utils/testcontext" "github.com/smartcontractkit/chainlink/integration-tests/actions" - "github.com/smartcontractkit/chainlink/integration-tests/client" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - "github.com/smartcontractkit/chainlink/v2/core/services/job" - "github.com/smartcontractkit/chainlink/v2/core/store/models" "github.com/smartcontractkit/chainlink-solana/integration-tests/common" + ocr_config "github.com/smartcontractkit/chainlink-solana/integration-tests/config" "github.com/smartcontractkit/chainlink-solana/integration-tests/gauntlet" - "github.com/smartcontractkit/chainlink-solana/integration-tests/solclient" + tc "github.com/smartcontractkit/chainlink-solana/integration-tests/testconfig" "github.com/smartcontractkit/chainlink-solana/integration-tests/utils" ) @@ -47,210 +34,114 @@ func TestSolanaOCRV2Smoke(t *testing.T) { if err != nil { t.Fatal(err) } + test := test t.Run(test.name, func(t *testing.T) { - t.Parallel() - - logging.Init() - state, err := common.NewOCRv2State(t, 1, "smoke-"+test.name, "localnet", false, &config) + state, err := common.NewOCRv2State(t, 1, "gauntlet-"+test.name, &config) require.NoError(t, err, "Could not setup the ocrv2 state") 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{} } maps.Copy(n.ContainerEnvs, test.env) }) } - state.DeployCluster(utils.ContractsDir) - - state.ValidateRoundsAfter(time.Now(), common.NewRoundCheckTimeout, 1) - }) - } -} - -func TestSolanaGauntletOCRV2Smoke(t *testing.T) { - config, err := tc.GetConfig("Smoke", tc.OCR2) - if err != nil { - t.Fatal(err) - } - l := logging.GetTestLogger(t) - secret := "this is an testing only secret" - state, err := common.NewOCRv2State(t, 1, "gauntlet", "devnet", true, &config) - require.NoError(t, err, "Could not setup the ocrv2 state") - if state.Common.Env.WillUseRemoteRunner() { - // run the remote runner and exit - state.GauntletEnvToRemoteRunner() - err = state.Common.Env.Run() - require.NoError(t, err) - return - } - sg, err := gauntlet.NewSolanaGauntlet(fmt.Sprintf("%s/gauntlet", utils.ProjectRoot)) - require.NoError(t, err) - err = state.Common.Env.Run() - require.NoError(t, err) - t.Cleanup(func() { - if err = actions.TeardownSuite(t, state.Common.Env, state.ChainlinkNodesK8s, nil, zapcore.PanicLevel, nil); err != nil { - l.Error().Err(err).Msg("Error tearing down environment") - } - }) - state.SetupClients() - state.NodeKeysBundle, err = state.Common.CreateNodeKeysBundle(state.GetChainlinkNodes()) - require.NoError(t, err) - err = state.Common.CreateSolanaChainAndNode(state.GetChainlinkNodes()) - require.NoError(t, err) - - gauntletConfig := state.ConfigureGauntlet(secret) - err = sg.SetupNetwork(gauntletConfig) - require.NoError(t, err, "Error setting gauntlet network") - - // Setting up RPC - c := rpc.New(gauntletConfig["NODE_URL"]) - wsc, err := ws.Connect(testcontext.Get(t), gauntletConfig["WS_URL"]) - require.NoError(t, err) - - _, err = sg.DeployOCR2() - require.NoError(t, err, "Error deploying OCR") - - bundleData := make([]client.NodeKeysBundle, len(state.NodeKeysBundle)) - copy(bundleData, state.NodeKeysBundle) - - // We have to sort by on_chain_pub_key for the config digest - sort.Slice(bundleData, func(i, j int) bool { - return bundleData[i].OCR2Key.Data.Attributes.OnChainPublicKey < bundleData[j].OCR2Key.Data.Attributes.OnChainPublicKey - }) - - onChainConfig, err := state.GenerateOnChainConfig(bundleData, gauntletConfig["VAULT"], sg.ProposalAddress) - require.NoError(t, err) - - reportingConfig := common.ReportingPluginConfig{ - AlphaReportInfinite: false, - AlphaReportPpb: 0, - AlphaAcceptInfinite: false, - AlphaAcceptPpb: 0, - DeltaCNanoseconds: 0, - } - offChainConfig := state.GenerateOffChainConfig( - bundleData, - sg.ProposalAddress, - reportingConfig, - int64(20000000000), - int64(50000000000), - int64(1000000000), - int64(4000000000), - int64(50000000000), - 3, - int64(0), - int64(3000000000), - int64(3000000000), - int64(100000000), - int64(100000000), - secret, - ) - payees := state.GeneratePayees(bundleData, gauntletConfig["VAULT"], sg.ProposalAddress) - proposalAccept := state.GenerateProposalAcceptConfig(sg.ProposalAddress, 2, 1, onChainConfig.Oracles, offChainConfig.OffchainConfig, secret) - - require.NoError(t, err) - err = sg.ConfigureOCR2(onChainConfig, offChainConfig, payees, proposalAccept) - require.NoError(t, err) - - err = state.Common.CreateSolanaChainAndNode(state.GetChainlinkNodes()) - require.NoError(t, err) + state.DeployCluster(utils.ContractsDir) - // TODO - This needs to be decoupled into one method as in common.go - // TODO - The current setup in common.go is using the solana validator, so we need to create one method for both gauntlet and solana - // Leaving this for the time being as is so we have Testnet runs enabled on Solana - relayConfig := job.JSONConfig{ - "nodeEndpointHTTP": state.Common.SolanaURL, - "ocr2ProgramID": gauntletConfig["PROGRAM_ID_OCR2"], - "transmissionsID": sg.FeedAddress, - "storeProgramID": gauntletConfig["PROGRAM_ID_STORE"], - "chainID": state.Common.ChainID, - } - bootstrapPeers := []client.P2PData{ - { - InternalIP: state.ChainlinkNodesK8s[0].InternalIP(), - InternalPort: "6690", - PeerID: state.NodeKeysBundle[0].PeerID, - }, - } - jobSpec := &client.OCR2TaskJobSpec{ - Name: fmt.Sprintf("sol-OCRv2-%s-%s", "bootstrap", uuid.New().String()), - JobType: "bootstrap", - OCR2OracleSpec: job.OCR2OracleSpec{ - ContractID: sg.OcrAddress, - Relay: common.ChainName, - RelayConfig: relayConfig, - P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, - OCRKeyBundleID: null.StringFrom(state.NodeKeysBundle[0].OCR2Key.Data.ID), - TransmitterID: null.StringFrom(state.NodeKeysBundle[0].TXKey.Data.ID), - ContractConfigConfirmations: 1, - ContractConfigTrackerPollInterval: models.Interval(15 * time.Second), - }, - } - sourceValueBridge := client.BridgeTypeAttributes{ - Name: "mockserver-bridge", - URL: fmt.Sprintf("%s/%s", state.Common.Env.URLs["qa_mock_adapter_internal"][0], "five"), - RequestData: "{}", - } + sg, err := gauntlet.NewSolanaGauntlet(fmt.Sprintf("%s/gauntlet", utils.ProjectRoot)) + require.NoError(t, err) + state.Gauntlet = sg - observationSource := client.ObservationSourceSpecBridge(&sourceValueBridge) - bridgeInfo := common.BridgeInfo{ObservationSource: observationSource} - err = state.ChainlinkNodesK8s[0].MustCreateBridge(&sourceValueBridge) - require.NoError(t, err) - _, err = state.ChainlinkNodesK8s[0].MustCreateJob(jobSpec) - require.NoError(t, err) + if *config.Common.InsideK8s { + t.Cleanup(func() { + if err = actions.TeardownRemoteSuite(t, state.Common.Env.Cfg.Namespace, state.Clients.ChainlinkClient.ChainlinkClientK8s, nil, nil, nil); err != nil { + log.Error().Err(err).Msg("Error tearing down environment") + } + }) + } + state.SetupClients() + require.NoError(t, err) + + gauntletConfig := map[string]string{ + "SECRET": fmt.Sprintf("\"%s\"", *config.SolanaConfig.Secret), + "NODE_URL": state.Common.ChainDetails.RPCURLExternal, + "WS_URL": state.Common.ChainDetails.WSURLExternal, + "PRIVATE_KEY": state.Common.AccountDetails.PrivateKey, + } - // TODO - This needs to be decoupled into one method as in common.go - // TODO - The current setup in common.go is using the solana validator, so we need to create one method for both gauntlet and solana - // Leaving this for the time being as is so we have Testnet runs enabled on Solana - for nIdx, node := range state.ChainlinkNodesK8s { - // Skipping bootstrap - if nIdx == 0 { - continue - } - err = solclient.SendFunds(gauntletConfig["PRIVATE_KEY"], state.NodeKeysBundle[nIdx].TXKey.Data.ID, 100000000, c, wsc) - require.NoError(t, err, "Error sending Funds") - sourceValueBridge := client.BridgeTypeAttributes{ - Name: "mockserver-bridge", - URL: fmt.Sprintf("%s/%s", state.Common.Env.URLs["qa_mock_adapter_internal"][0], "five"), - RequestData: "{}", - } - _, err := node.CreateBridge(&sourceValueBridge) - require.NoError(t, err) - jobSpec := &client.OCR2TaskJobSpec{ - Name: fmt.Sprintf("sol-OCRv2-%d-%s", nIdx, uuid.New().String()), - JobType: "offchainreporting2", - ObservationSource: bridgeInfo.ObservationSource, - OCR2OracleSpec: job.OCR2OracleSpec{ - ContractID: sg.OcrAddress, - Relay: common.ChainName, - RelayConfig: relayConfig, - P2PV2Bootstrappers: pq.StringArray{bootstrapPeers[0].P2PV2Bootstrapper()}, - OCRKeyBundleID: null.StringFrom(state.NodeKeysBundle[nIdx].OCR2Key.Data.ID), - TransmitterID: null.StringFrom(state.NodeKeysBundle[nIdx].TXKey.Data.ID), - ContractConfigConfirmations: 1, - ContractConfigTrackerPollInterval: models.Interval(15 * time.Second), - PluginType: "median", - PluginConfig: common.PluginConfigToTomlFormat(observationSource), - }, - } - _, err = node.MustCreateJob(jobSpec) - require.NoError(t, err) - } + err = sg.SetupNetwork(gauntletConfig) + require.NoError(t, err, "Error setting gauntlet network") + + if *config.Common.Network == "devnet" { + state.Common.ChainDetails.ProgramAddresses.OCR2 = *config.SolanaConfig.OCR2ProgramID + state.Common.ChainDetails.ProgramAddresses.AccessController = *config.SolanaConfig.AccessControllerProgramID + state.Common.ChainDetails.ProgramAddresses.Store = *config.SolanaConfig.StoreProgramID + sg.LinkAddress = *config.SolanaConfig.LinkTokenAddress + sg.VaultAddress = *config.SolanaConfig.VaultAddress + } else { + // Deploying LINK in case of localnet + err = sg.DeployLinkToken() + require.NoError(t, err) + } - // Test start - for i := 1; i < 10; i++ { - transmissions, err := sg.FetchTransmissions(sg.OcrAddress) - require.NoError(t, err) - if len(transmissions) <= 1 { - l.Info().Str("Contract", sg.OcrAddress).Str("No", "Transmissions") - } else { - l.Info().Str("Contract", sg.OcrAddress).Interface("Answer", transmissions[0].Answer).Int64("RoundID", transmissions[0].RoundID).Msg("New answer found") - assert.Equal(t, transmissions[0].Answer, int64(5), fmt.Sprintf("Actual: %d, Expected: 5", transmissions[0].Answer)) - assert.Less(t, transmissions[1].RoundID, transmissions[0].RoundID, fmt.Sprintf("Expected round %d to be less than %d", transmissions[1].RoundID, transmissions[0].RoundID)) - } - time.Sleep(time.Second * 6) + err = sg.G.WriteNetworkConfigVar(sg.NetworkFilePath, "PROGRAM_ID_OCR2", state.Common.ChainDetails.ProgramAddresses.OCR2) + require.NoError(t, err, "Error adding gauntlet variable") + err = sg.G.WriteNetworkConfigVar(sg.NetworkFilePath, "PROGRAM_ID_ACCESS_CONTROLLER", state.Common.ChainDetails.ProgramAddresses.AccessController) + require.NoError(t, err, "Error adding gauntlet variable") + err = sg.G.WriteNetworkConfigVar(sg.NetworkFilePath, "PROGRAM_ID_STORE", state.Common.ChainDetails.ProgramAddresses.Store) + require.NoError(t, err, "Error adding gauntlet variable") + err = sg.G.WriteNetworkConfigVar(sg.NetworkFilePath, "LINK", sg.LinkAddress) + require.NoError(t, err, "Error adding gauntlet variable") + err = sg.G.WriteNetworkConfigVar(sg.NetworkFilePath, "VAULT_ADDRESS", sg.VaultAddress) + require.NoError(t, err, "Error adding gauntlet variable") + + _, err = sg.DeployOCR2() + require.NoError(t, err, "Error deploying OCR") + // Generating default OCR2 config + ocr2Config := ocr_config.NewOCR2Config(state.Clients.ChainlinkClient.NKeys, sg.ProposalAddress, sg.VaultAddress, *config.SolanaConfig.Secret) + ocr2Config.Default() + sg.OCR2Config = ocr2Config + + err = sg.ConfigureOCR2() + require.NoError(t, err) + + state.CreateJobs() + + // Test start + stuck := 0 + successFullRounds := 0 + prevRound := gauntlet.Transmission{ + RoundID: 0, + } + for successFullRounds < *config.OCR2.Smoke.NumberOfRounds { + require.Less(t, stuck, 10, "Rounds have been stuck for more than 10 iterations") + log.Info().Str("Transmission", sg.OcrAddress).Msg("Inspecting transmissions") + transmissions, err := sg.FetchTransmissions(sg.OcrAddress) + require.NoError(t, err) + if len(transmissions) <= 1 { + log.Info().Str("Contract", sg.OcrAddress).Str("No", "Transmissions") + stuck++ + continue + } + currentRound := common.GetLatestRound(transmissions) + if prevRound.RoundID == 0 { + prevRound = currentRound + } + if currentRound.RoundID <= prevRound.RoundID { + log.Info().Str("Transmission", sg.OcrAddress).Msg("No new transmissions") + stuck++ + continue + } + log.Info().Str("Contract", sg.OcrAddress).Interface("Answer", currentRound.Answer).Int64("RoundID", currentRound.Answer).Msg("New answer found") + require.Equal(t, currentRound.Answer, int64(5), fmt.Sprintf("Actual: %d, Expected: 5", currentRound.Answer)) + require.Less(t, prevRound.RoundID, currentRound.RoundID, fmt.Sprintf("Expected round %d to be less than %d", prevRound.RoundID, currentRound.RoundID)) + prevRound = currentRound + successFullRounds++ + time.Sleep(time.Second * 6) + stuck = 0 + } + }) } } diff --git a/integration-tests/soak/ocr2_soak_test.go b/integration-tests/soak/ocr2_soak_test.go deleted file mode 100644 index 837f42c53..000000000 --- a/integration-tests/soak/ocr2_soak_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package tests - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" - - "github.com/smartcontractkit/chainlink-solana/integration-tests/common" - "github.com/smartcontractkit/chainlink-solana/integration-tests/utils" -) - -func TestSolanaOCRV2SoakTest(t *testing.T) { - config, err := tc.GetConfig("Soak", tc.OCR2) - if err != nil { - t.Fatal(err) - } - state, err := common.NewOCRv2State(t, 5, "soak", "devnet", true, &config) - require.NoError(t, err, "Could not setup the ocrv2 state") - if state.Common.Env.WillUseRemoteRunner() { - // run the remote runner and exit - err := state.Common.Env.Run() - require.NoError(t, err) - return - } - state.DeployCluster(utils.ContractsDir) - state.ValidateRoundsAfter(time.Now(), common.NewSoakRoundsCheckTimeout, 20000) -} diff --git a/integration-tests/testconfig/configs_embed.go b/integration-tests/testconfig/configs_embed.go new file mode 100644 index 000000000..ade113975 --- /dev/null +++ b/integration-tests/testconfig/configs_embed.go @@ -0,0 +1,15 @@ + +//go:build embed +// +build embed + +package testconfig + +import "embed" + +//go:embed default.toml +//go:embed ocr2/ocr2.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 new file mode 100644 index 000000000..95572c4a0 --- /dev/null +++ b/integration-tests/testconfig/configs_noembed.go @@ -0,0 +1,12 @@ +//go:build !embed +// +build !embed + +package testconfig + +import "embed" + +var embeddedConfigsFs embed.FS + +func init() { + areConfigsEmbedded = false +} diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index a65c23d70..d064fc9c9 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -1,3 +1,8 @@ +# This is the default configuration so OCR2 tests can run without issues +[ChainlinkImage] +image="public.ecr.aws/chainlink/chainlink" +version="2.9.0" + [Logging] test_log_collect=false @@ -7,16 +12,37 @@ log_producer_timeout="10s" log_producer_retry_limit=10 [Network] -selected_networks=["simulated"] - -[PrivateEthereumNetwork] -consensus_type="pow" -execution_layer="geth" - -[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 +selected_networks=["SIMULATED"] # Not needed for Solana but mandatory from CTF (do not change) + +[Network.RpcHttpUrls] +simulated = ["http://127.0.0.1"] # Not needed for Solana but mandatory from CTF (do not change) + +[Network.RpcWsUrls] +simulated = ["wss://127.0.0.1"] # Not needed for Solana but mandatory from CTF (do not change) + +# Testnet program ID's +[SolanaConfig] +ocr2_program_id = "cjg3oHmg9uuPsP8D6g29NWvhySJkdYdAo9D25PRbKXJ" +access_controller_program_id = "9xi644bRR8birboDGdTiwBq3C7VEeR7VuamRYYXCubUW" +store_program_id = "HEvSKofvBgfaexv23kMabbYqxasxU3mQ4ibBMEmJWHny" +link_token_address = "7CF1GrsZsny5j9JESPj98MdYVZK38RE8ZpmTEMwECK4c" +vault_address = "FdM4dnhVpFQfjPqNG6LEfzArhuGhUjtidYu89qtGwJCS" +secret="thisisatestingonlysecret" + +[Common] +rpc_url = "https://api.devnet.solana.com" +ws_url = "wss://api.devnet.solana.com/" +internal_docker_repo = "public.ecr.aws/chainlink" +inside_k8 = false +network = "localnet" +user = "default" +stateful_db = false +devnet_image = "solanalabs/solana:v1.17.33" + +[OCR2] +node_count = 6 +test_duration = "50m" + +[OCR2.Smoke] +number_of_rounds = 5 + diff --git a/integration-tests/testconfig/ocr2/example.toml b/integration-tests/testconfig/ocr2/example.toml deleted file mode 100644 index 6cbdbef15..000000000 --- a/integration-tests/testconfig/ocr2/example.toml +++ /dev/null @@ -1,96 +0,0 @@ -# Example of full config with all fields -# General part -[ChainlinkImage] -image="public.ecr.aws/chainlink/chainlink" -version="2.7.0" - -[Logging] -# if set to true will save logs even if test did not fail -test_log_collect=false - -[Logging.LogStream] -# supported targets: file, loki, in-memory. if empty no logs will be persistet -log_targets=["file"] -# context timeout for starting log producer and also time-frame for requesting logs -log_producer_timeout="10s" -# number of retries before log producer gives up and stops listening to logs -log_producer_retry_limit=10 - -[Logging.Loki] -tenant_id="tenant_id" -# full URL of Loki ingest endpoint -endpoint="https://loki.url/api/v3/push" -# currently only needed when using public instance -basic_auth="loki-basic-auth" -# only needed for cloud grafana -bearer_token="bearer_token" - -# LogStream will try to shorten Grafana URLs by default (if all 3 variables are set) -[Logging.Grafana] -# grafana url (trailing "/" will be stripped) -base_url="http://grafana.url" -# url of your grafana dashboard (prefix and suffix "/" are stirpped), example: /d/ad61652-2712-1722/my-dashboard -dashboard_url="/d/your-dashboard" -bearer_token="my-awesome-token" - -# if you want to use polygon_mumbial -[Network] -selected_networks=["polygon_mumbai"] - -[Network.RpcHttpUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.RpcWsUrls] -polygon_mumbai = ["https://my-rpc-endpoint.io"] - -[Network.WalletKeys] -polygon_mumbai = ["change-me-to-your-PK"] - -[PrivateEthereumNetwork] -# pos or pow -consensus_type="pos" -# only prysm supported currently -consensus_layer="prysm" -# geth, besu, nethermind or erigon -execution_layer="geth" -# if true after env started it will wait for at least 1 epoch to be finalised before continuing -wait_for_finalization=false - -[PrivateEthereumNetwork.EthereumChainConfig] -# duration of single slot, lower => faster block production, must be >= 4 -seconds_per_slot=12 -# numer of slots in epoch, lower => faster epoch finalisation, must be >= 4 -slots_per_epoch=6 -# extra genesis gelay, no need to modify, but it should be after all validators/beacon chain starts -genesis_delay=15 -# number of validators in the network -validator_count=8 -chain_id=1337 -# list of addresses to be prefunded in genesis -addresses_to_fund=["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"] - -# load test specific configuration -[Load.OCR] -[Load.OCR.Common] -eth_funds = 3 - -[Load.OCR.Load] -test_duration = "3m" -rate_limit_unit_duration = "1m" -rate = 3 -verification_interval = "5s" -verification_timeout = "3m" -ea_change_interval = "5s" - -# soak test specific configuration -[Soak.Common] -chainlink_node_funding = 100 - -[Soak.OCR] -[Soak.OCR.Common] -test_duration="15m" - -[Soak.OCR.Soak] -ocr_version="1" -number_of_contracts=2 -time_between_rounds="1m" \ No newline at end of file diff --git a/integration-tests/testconfig/ocr2/ocr2.go b/integration-tests/testconfig/ocr2/ocr2.go index d5cc48ea5..8a27552fe 100644 --- a/integration-tests/testconfig/ocr2/ocr2.go +++ b/integration-tests/testconfig/ocr2/ocr2.go @@ -2,135 +2,48 @@ package ocr2 import ( "errors" - - "github.com/smartcontractkit/chainlink-testing-framework/blockchain" + "time" ) 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"` + TestDurationParsed *time.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") + duration, err := time.ParseDuration(*o.TestDuration) + if err != nil { + return errors.New("Invalid test duration") } - 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"` -} + o.TestDurationParsed = &duration -func (o *Volume) Validate() error { - if o.TestDuration == nil { - return errors.New("volume test duration must be set") + if o.Smoke == nil { + return errors.New("smoke must be defined") } - 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.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") + err = o.Smoke.Validate() + if 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..0644ddfe7 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" diff --git a/integration-tests/testconfig/testconfig.go b/integration-tests/testconfig/testconfig.go new file mode 100644 index 000000000..f81de5806 --- /dev/null +++ b/integration-tests/testconfig/testconfig.go @@ -0,0 +1,411 @@ +package testconfig + +import ( + "embed" + "encoding/base64" + "errors" + "fmt" + "os" + "strings" + + "github.com/barkimedes/go-deepcopy" + "github.com/google/uuid" + "github.com/pelletier/go-toml/v2" + "github.com/rs/zerolog" + "golang.org/x/text/cases" + "golang.org/x/text/language" + + "github.com/smartcontractkit/seth" + + 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" + + ocr2_config "github.com/smartcontractkit/chainlink-solana/integration-tests/testconfig/ocr2" +) + +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"` + SolanaConfig *SolanaConfig `toml:"SolanaConfig"` + ConfigurationName string `toml:"-"` +} + +func (c *TestConfig) GetLoggingConfig() *ctf_config.LoggingConfig { + return c.Logging +} + +func (c *TestConfig) GetPrivateEthereumNetworkConfig() *ctf_config.EthereumNetworkConfig { + return &ctf_config.EthereumNetworkConfig{} +} + +func (c *TestConfig) GetPyroscopeConfig() *ctf_config.PyroscopeConfig { + return &ctf_config.PyroscopeConfig{} +} + +func (c *TestConfig) GetSethConfig() *seth.Config { + return nil +} + +func (c *TestConfig) GetNodeConfig() *ctf_config.NodeConfig { + 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 "", fmt.Errorf("error marshaling test config: %w", err) + } + + err = os.WriteFile(filePath, content, 0600) + if err != nil { + return "", fmt.Errorf("error writing test config: %w", err) + } + + 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 "", fmt.Errorf("error marshaling test config: %w", err) + } + + return base64.StdEncoding.EncodeToString(content), nil +} + +type Common struct { + Network *string `toml:"network"` + InsideK8s *bool `toml:"inside_k8"` + User *string `toml:"user"` + // if rpc requires api key to be passed as an HTTP header + RPCURL *string `toml:"rpc_url"` + WsURL *string `toml:"ws_url"` + PrivateKey *string `toml:"private_key"` + Stateful *bool `toml:"stateful_db"` + InternalDockerRepo *string `toml:"internal_docker_repo"` + DevnetImage *string `toml:"devnet_image"` +} + +type SolanaConfig struct { + Secret *string `toml:"secret"` + OCR2ProgramID *string `toml:"ocr2_program_id"` + AccessControllerProgramID *string `toml:"access_controller_program_id"` + StoreProgramID *string `toml:"store_program_id"` + LinkTokenAddress *string `toml:"link_token_address"` + VaultAddress *string `toml:"vault_address"` +} + +func (c *SolanaConfig) Validate() error { + if c.Secret == nil { + return fmt.Errorf("secret must be set") + } + if c.OCR2ProgramID == nil { + return fmt.Errorf("ocr2_program_id must be set") + } + if c.AccessControllerProgramID == nil { + return fmt.Errorf("access_controller_program_id must be set") + } + if c.StoreProgramID == nil { + return fmt.Errorf("store_program_id must be set") + } + if c.LinkTokenAddress == nil { + return fmt.Errorf("link_token_address must be set") + } + if c.VaultAddress == nil { + return fmt.Errorf("vault_address must be set") + } + return nil +} + +func (c *Common) Validate() error { + if c.Network == nil { + return fmt.Errorf("network must be set") + } + + switch *c.Network { + case "localnet": + if c.DevnetImage == nil { + return fmt.Errorf("devnet_image must be set") + } + case "devnet": + if c.PrivateKey == nil { + return fmt.Errorf("private_key must be set") + } + if c.RPCURL == nil { + return fmt.Errorf("rpc_url must be set") + } + if c.WsURL == nil { + return fmt.Errorf("rpc_url must be set") + } + + default: + return fmt.Errorf("network must be either 'localnet' or 'devnet'") + } + + if c.InsideK8s == nil { + return fmt.Errorf("inside_k8 must be set") + } + + if c.InternalDockerRepo == nil { + return fmt.Errorf("internal_docker_repo must be set") + } + + err := os.Setenv("INTERNAL_DOCKER_REPO", *c.InternalDockerRepo) + if err != nil { + return fmt.Errorf("could not set INTERNAL_DOCKER_REPO env var") + } + + if c.User == nil { + return fmt.Errorf("user must be set") + } + + err = os.Setenv("CHAINLINK_ENV_USER", *c.User) + if err != nil { + return fmt.Errorf("could not set CHAINLINK_ENV_USER env var") + } + + if c.Stateful == nil { + return fmt.Errorf("stateful_db state for db must be set") + } + + return nil +} + +type Product string + +const ( + OCR2 Product = "ocr2" +) + +const TestTypeEnvVarName = "TEST_TYPE" + +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 fmt.Errorf("error reading file %s: %w", filename, err) + } + + 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{}, fmt.Errorf("error reading embedded config: %w", err) + } + + err = handleSpecialOverrides(logger, fileName, configurationName, &testConfig, file, product) + if err != nil { + return TestConfig{}, fmt.Errorf("error unmarshalling embedded config: %w", err) + } + } + } + + 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{}, fmt.Errorf("error looking for file %s: %w", filePath, err) + } + logger.Debug().Str("location", filePath).Msgf("Found config file %s", fileName) + + content, err := readFile(filePath) + if err != nil { + return TestConfig{}, fmt.Errorf("error reading file %s: %w", filePath, err) + } + + err = handleSpecialOverrides(logger, fileName, configurationName, &testConfig, content, product) + if err != nil { + return TestConfig{}, fmt.Errorf("error reading file %s: %w", filePath, err) + } + } + + 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{}, fmt.Errorf("error unmarshaling base64 config: %w", err) + } + } 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{}, fmt.Errorf("error reading network config: %w", err) + } + + logger.Debug().Msg("Validating test config") + err = testConfig.Validate() + if err != nil { + return TestConfig{}, fmt.Errorf("error validating test config: %w", err) + } + + 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 fmt.Errorf("error reading default network config: %w", err) + } + + 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 fmt.Errorf("chainlink image config validation failed: %w", err) + } + if c.ChainlinkUpgradeImage != nil { + if err := c.ChainlinkUpgradeImage.Validate(); err != nil { + return fmt.Errorf("chainlink upgrade image config validation failed: %w", err) + } + } + if err := c.Network.Validate(); err != nil { + return fmt.Errorf("network config validation failed: %w", err) + } + + if c.Common == nil { + return fmt.Errorf("common config must be set") + } + + if err := c.Common.Validate(); err != nil { + return fmt.Errorf("Common config validation failed: %w", err) + } + + if c.OCR2 == nil { + return fmt.Errorf("OCR2 config must be set") + } + + if err := c.OCR2.Validate(); err != nil { + return fmt.Errorf("OCR2 config validation failed: %w", err) + } + if c.SolanaConfig == nil { + return fmt.Errorf("SolanaConfig config must be set") + } + + if err := c.SolanaConfig.Validate(); err != nil { + return fmt.Errorf("SolanaConfig config validation failed: %w", err) + } + return nil +} + +func readFile(filePath string) ([]byte, error) { + content, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("error reading file %s: %w", filePath, err) + } + + return content, nil +} From 54b7bbaf1ca591bb7eee9af9a2d28f9cd1c210c5 Mon Sep 17 00:00:00 2001 From: Aaron Lu <50029043+aalu1418@users.noreply.github.com> Date: Tue, 21 May 2024 23:30:37 -0600 Subject: [PATCH 2/5] fix: handle new location for e2e test solana version (#716) --- scripts/update-solana.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/update-solana.sh b/scripts/update-solana.sh index 09c9124ae..a115fbf53 100755 --- a/scripts/update-solana.sh +++ b/scripts/update-solana.sh @@ -5,7 +5,7 @@ cliVersion=$(grep -oh "release.solana.com/v[0-9]*.[0-9]*.[0-9]*" scripts/install echo "Current Test CLI Version: $cliVersion" cd integration-tests -testVersion=$(grep -oh "solanalabs/solana:v[0-9]*.[0-9]*.[0-9]*" */**/*.go) +testVersion=$(grep -oh "solanalabs/solana:v[0-9]*.[0-9]*.[0-9]*" testconfig/default.toml) echo "Current E2E Test Version: $testVersion" cd .. @@ -24,11 +24,11 @@ echo "Replacing Solana Image Version" if [ "$(uname -s)" = "Darwin" ]; then sed -i '' -e "s~$cliVersion~$latestCLI~" scripts/install-solana-ci.sh cd integration-tests - sed -i '' -e "s~$testVersion~$latestVersion~" */**/*.go + sed -i '' -e "s~$testVersion~$latestVersion~" testconfig/default.toml else sed -i -e "s~$cliVersion~$latestCLI~" scripts/install-solana-ci.sh cd integration-tests - sed -i -e "s~$testVersion~$latestVersion~" */**/*.go + sed -i -e "s~$testVersion~$latestVersion~" testconfig/default.toml fi cd .. From 7f575962e86ba67ec50b106a28f289cb1ea6fd2b Mon Sep 17 00:00:00 2001 From: "app-token-issuer-infra-releng[bot]" <120227048+app-token-issuer-infra-releng[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 13:59:50 +0000 Subject: [PATCH 3/5] [automated] bump solana image to v1.17.34 (#717) * fix: handle new location for e2e test solana version * [automated] bump solana dependencies --------- Co-authored-by: aalu1418 <50029043+aalu1418@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- integration-tests/testconfig/default.toml | 2 +- scripts/install-solana-ci.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-tests/testconfig/default.toml b/integration-tests/testconfig/default.toml index d064fc9c9..525ef3de9 100644 --- a/integration-tests/testconfig/default.toml +++ b/integration-tests/testconfig/default.toml @@ -37,7 +37,7 @@ inside_k8 = false network = "localnet" user = "default" stateful_db = false -devnet_image = "solanalabs/solana:v1.17.33" +devnet_image = "solanalabs/solana:v1.17.34" [OCR2] node_count = 6 diff --git a/scripts/install-solana-ci.sh b/scripts/install-solana-ci.sh index 57c060a00..99db76f2f 100755 --- a/scripts/install-solana-ci.sh +++ b/scripts/install-solana-ci.sh @@ -2,5 +2,5 @@ set -euxo pipefail -sh -c "$(curl -sSfL https://release.solana.com/v1.17.33/install)" +sh -c "$(curl -sSfL https://release.solana.com/v1.17.34/install)" echo "PATH=$HOME/.local/share/solana/install/active_release/bin:$PATH" >> $GITHUB_ENV From 2b32fda72aa6ff4fb65aa7aa4a34d7d3bd37e835 Mon Sep 17 00:00:00 2001 From: Aaron Lu <50029043+aalu1418@users.noreply.github.com> Date: Wed, 22 May 2024 09:42:03 -0600 Subject: [PATCH 4/5] bump ctf to v1.28.15 (#719) * bump ctf to v1.28.15 * remove soak test reference --- .github/actions/build-test-image/action.yml | 2 +- Makefile | 4 ---- integration-tests/go.mod | 2 +- integration-tests/go.sum | 4 ++-- integration-tests/scripts/buildTestImage | 2 +- integration-tests/test.Dockerfile | 2 +- 6 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml index ae57a5616..6a2e50d60 100644 --- a/.github/actions/build-test-image/action.yml +++ b/.github/actions/build-test-image/action.yml @@ -57,7 +57,7 @@ runs: build-args: | BASE_IMAGE=${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/test-base-image IMAGE_VERSION=${{ steps.version.outputs.version }} - SUITES="soak smoke" + SUITES="smoke" AWS_REGION: ${{ inputs.QA_AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ inputs.QA_AWS_ROLE_TO_ASSUME }} - name: Print Image Built diff --git a/Makefile b/Makefile index 4030022bd..288ea388e 100644 --- a/Makefile +++ b/Makefile @@ -84,10 +84,6 @@ test_smoke: cd ./integration-tests &&\ SELECTED_NETWORKS=SIMULATED go test -timeout 24h -count=1 -json $(args) -run TestSolanaOCRV2Smoke ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt -test_ocr_soak: - cd ./integration-tests &&\ - SELECTED_NETWORKS=SIMULATED go test -timeout 24h -count=1 -json $(args) ./soak 2>&1 | tee /tmp/gotest.log | gotestfmt - gomodtidy: go mod tidy cd ./integration-tests && go mod tidy diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 3bf21f6cb..13d79e8bb 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.30.0 github.com/smartcontractkit/chainlink-common v0.1.7-0.20240516150131-e1be553a9d10 github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240510181707-46b1311a5a83 - github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4 + github.com/smartcontractkit/chainlink-testing-framework v1.28.15 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240515225456-aeb9f4d50d65 github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240515225456-aeb9f4d50d65 github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 79c3005cc..e959f07bf 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1416,8 +1416,8 @@ github.com/smartcontractkit/chainlink-feeds v0.0.0-20240422130241-13c17a91b2ab h github.com/smartcontractkit/chainlink-feeds v0.0.0-20240422130241-13c17a91b2ab/go.mod h1:RPUY7r8GxgzXxS1ijtU1P/fpJomOXztXgUbEziNmbCA= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240508155030-1024f2b55c69 h1:ssh/w3oXWu+C6bE88GuFRC1+0Bx/4ihsbc80XMLrl2k= github.com/smartcontractkit/chainlink-starknet/relayer v0.0.1-beta-test.0.20240508155030-1024f2b55c69/go.mod h1:VsfjhvWgjxqWja4q+FlXEtX5lu8BSxn10xRo6gi948g= -github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4 h1:XorHCAKux5+/jYsnGLRYOMBSW7Q3pKZJaxqRCZOW/UU= -github.com/smartcontractkit/chainlink-testing-framework v1.28.15-0.20240520085642-b994043307a4/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= +github.com/smartcontractkit/chainlink-testing-framework v1.28.15 h1:mga7N6jtXQ3UOCt43IdsEnCMBh9xjOWPaE9BiM6kr6Q= +github.com/smartcontractkit/chainlink-testing-framework v1.28.15/go.mod h1:x1zDOz8zcLjEvs9fNA9y/DMguLam/2+CJdpxX0+rM8A= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240328204215-ac91f55f1449 h1:fX/xmGm1GBsD1ZZnooNT+eWA0hiTAqFlHzOC5CY4dy8= github.com/smartcontractkit/chainlink-testing-framework/grafana v0.0.0-20240328204215-ac91f55f1449/go.mod h1:DC8sQMyTlI/44UCTL8QWFwb0bYNoXCfjwCv2hMivYZU= github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772 h1:LQmRsrzzaYYN3wEU1l5tWiccznhvbyGnu2N+wHSXZAo= diff --git a/integration-tests/scripts/buildTestImage b/integration-tests/scripts/buildTestImage index b76598951..22c70e098 100755 --- a/integration-tests/scripts/buildTestImage +++ b/integration-tests/scripts/buildTestImage @@ -13,7 +13,7 @@ cd "$SCRIPT_DIR"/../../ || exit 1 TAG_VERSION="${1}" BASE_IMAGE_VERSION="${2}" SUITES=$3 -DEFAULT_SUITES="smoke soak" +DEFAULT_SUITES="smoke" ACCOUNT=$(aws sts get-caller-identity | jq -r .Account) AWS_BASE="${ACCOUNT}".dkr.ecr.us-west-2.amazonaws.com TAG="${AWS_BASE}"/chainlink-solana-tests:"${TAG_VERSION}" diff --git a/integration-tests/test.Dockerfile b/integration-tests/test.Dockerfile index 5964b75df..8b7feeafc 100644 --- a/integration-tests/test.Dockerfile +++ b/integration-tests/test.Dockerfile @@ -2,7 +2,7 @@ ARG BASE_IMAGE ARG IMAGE_VERSION=latest FROM ${BASE_IMAGE}:${IMAGE_VERSION} -ARG SUITES=smoke soak +ARG SUITES=smoke COPY . testdir/ WORKDIR /go/testdir From 822b5f3cd76dd30ac536205ce4f1c78469c4a629 Mon Sep 17 00:00:00 2001 From: Tate Date: Wed, 22 May 2024 10:49:45 -0600 Subject: [PATCH 5/5] [TT-1032] Replace usages of gotestfmt with gotestloghelper (#694) * [TT-1032] Replace usages of gotestfmt with gotestloghelper * Add metrics collection for test results * relay metrics collect and logs cleanup * fix missing id in tests publish workflow * bump gotestloghelper and actions to get latest versions * copy paste error * remove cgo_enabled * merge conflict fix --------- Co-authored-by: Aaron Lu <50029043+aalu1418@users.noreply.github.com> --- .github/actions/build-test-image/action.yml | 6 +++--- .github/workflows/e2e_custom_cl.yml | 11 ++++++----- .github/workflows/e2e_testnet_daily.yml | 19 ++++++++++--------- .../workflows/integration-tests-publish.yml | 2 +- .github/workflows/relay.yml | 14 +++++++++++++- .github/workflows/soak.yml | 10 +++++----- .github/workflows/sonar-scan.yml | 2 +- .tool-versions | 5 ++--- Makefile | 4 ++-- shell.nix | 6 +++++- 10 files changed, 48 insertions(+), 31 deletions(-) diff --git a/.github/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml index 6a2e50d60..b1643afe8 100644 --- a/.github/actions/build-test-image/action.yml +++ b/.github/actions/build-test-image/action.yml @@ -28,7 +28,7 @@ runs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink-solana-tests tag: ${{ inputs.tag }} @@ -41,14 +41,14 @@ runs: path: ${{ inputs.artifacts_path }} - name: Get CTF Version id: version - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/mod-version@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: go-project-path: ./integration-tests module-name: github.com/smartcontractkit/chainlink-testing-framework enforce-semantic-tag: false - name: Build and Publish Test Runner if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/docker/build-push@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/build-push@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: tags: | ${{ inputs.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ inputs.QA_AWS_REGION }}.amazonaws.com/chainlink-solana-tests:${{ inputs.tag }} diff --git a/.github/workflows/e2e_custom_cl.yml b/.github/workflows/e2e_custom_cl.yml index 6036cfdc9..cd63b51b1 100644 --- a/.github/workflows/e2e_custom_cl.yml +++ b/.github/workflows/e2e_custom_cl.yml @@ -90,7 +90,7 @@ jobs: fi - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink tag: solana.${{ env.CUSTOM_CORE_REF || github.event.inputs.cl_branch_ref || github.sha }} @@ -98,7 +98,7 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - name: Build Image if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: should_checkout: true cl_repo: smartcontractkit/chainlink @@ -129,11 +129,12 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: e2e_custom_run_smoke_tests + id: solana-e2e-smoke org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} this-job-name: E2E Custom Run Smoke Tests + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true - name: Get core ref from PR body if: github.event_name == 'pull_request' @@ -168,9 +169,9 @@ jobs: # shellcheck disable=SC2086 echo "BASE64_CONFIG_OVERRIDE=$BASE64_CONFIG_OVERRIDE" >> $GITHUB_ENV - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@7f2d504e0b6ebd4aa5ece371d9c5eb9762803ca0 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: - test_command_to_run: cd ./integration-tests && go test -timeout 24h -count=1 -run TestSolanaOCRV2Smoke -json $(args) ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 24h -count=1 -run TestSolanaOCRV2Smoke -json $(args) ./smoke 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage test_download_vendor_packages_command: cd ./integration-tests && go mod download download_contract_artifacts_path: ${{ env.CONTRACT_ARTIFACTS_PATH }} go_mod_path: ./integration-tests/go.mod diff --git a/.github/workflows/e2e_testnet_daily.yml b/.github/workflows/e2e_testnet_daily.yml index a554d0fd4..a6acfe644 100644 --- a/.github/workflows/e2e_testnet_daily.yml +++ b/.github/workflows/e2e_testnet_daily.yml @@ -42,7 +42,7 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: e2e_testnet_daily-changes + id: solana-e2e-detect-changes org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} @@ -124,7 +124,7 @@ jobs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink-solana-tests tag: ${{ needs.get_solana_sha.outputs.sha }} @@ -152,7 +152,7 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: e2e_testnet_daily-build-artifacts + id: solana-e2e-build-artivacts org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} @@ -183,7 +183,7 @@ jobs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink tag: solana.${{ github.sha }} @@ -191,7 +191,7 @@ jobs: AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - name: Build Image if: steps.check-image.outputs.exists == 'false' - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: should_checkout: true cl_repo: smartcontractkit/chainlink @@ -214,7 +214,7 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: e2e_testnet_daily-build-test-image + id: solana-e2e-build-test-image org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} @@ -268,18 +268,19 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: e2e_custom_run_daily_testnet_smoke_tests + id: solana-e2e-daily org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} this-job-name: E2E Run Daily Smoke Tests + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' continue-on-error: true - name: Checkout the repo uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: - test_command_to_run: cd ./integration-tests && go test -timeout 24h -count=1 -run TestSolanaGauntletOCRV2Smoke -json $(args) ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 24h -count=1 -run TestSolanaGauntletOCRV2Smoke -json $(args) ./smoke 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage go_mod_path: ./integration-tests/go.mod cl_repo: ${{ env.CL_ECR }} cl_image_tag: solana.${{ github.sha }} diff --git a/.github/workflows/integration-tests-publish.yml b/.github/workflows/integration-tests-publish.yml index f03809af1..a07fdea5d 100644 --- a/.github/workflows/integration-tests-publish.yml +++ b/.github/workflows/integration-tests-publish.yml @@ -60,7 +60,7 @@ jobs: id: collect-gha-metrics uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 with: - id: integration-tests-publish-image + id: solana-e2e-publish org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} diff --git a/.github/workflows/relay.yml b/.github/workflows/relay.yml index d9e47aea2..5eefe2890 100644 --- a/.github/workflows/relay.yml +++ b/.github/workflows/relay.yml @@ -11,6 +11,16 @@ jobs: name: Relay Run Unit Tests runs-on: ubuntu-latest steps: + - name: Collect Metrics + id: collect-gha-metrics + uses: smartcontractkit/push-gha-metrics-action@dea9b546553cb4ca936607c2267a09c004e4ab3f # v3.0.0 + with: + id: solana-relay-unit + org-id: ${{ secrets.GRAFANA_INTERNAL_TENANT_ID }} + basic-auth: ${{ secrets.GRAFANA_INTERNAL_BASIC_AUTH }} + hostname: ${{ secrets.GRAFANA_INTERNAL_HOST }} + this-job-name: Relay Run Unit Tests + test-results-file: '{"testType":"go","filePath":"/tmp/gotest.log"}' - name: Checkout sources uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Setup go @@ -18,6 +28,8 @@ jobs: with: go-version-file: "go.mod" check-latest: true + - name: Install gotestloghelper + run: go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@latest - name: Check go mod tidy run: | go mod tidy @@ -30,7 +42,7 @@ jobs: - name: Build run: go build -v ./pkg/... - name: Test - run: go test ./pkg/... -v -tags integration -covermode=atomic -coverpkg=./... -coverprofile=integration_coverage.txt + run: go test ./pkg/... -json -tags integration -covermode=atomic -coverpkg=./... -coverprofile=integration_coverage.txt 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci - name: Test with the race detector enabled run: go test ./pkg/... -v -race -count=10 -timeout=15m -covermode=atomic -coverpkg=./... -coverprofile=race_coverage.txt - name: Upload Go test results diff --git a/.github/workflows/soak.yml b/.github/workflows/soak.yml index 1ceb94155..182904a82 100644 --- a/.github/workflows/soak.yml +++ b/.github/workflows/soak.yml @@ -52,7 +52,7 @@ jobs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink-solana-tests tag: ${{ github.sha }} @@ -92,14 +92,14 @@ jobs: steps: - name: Check if image exists id: check-image - uses: smartcontractkit/chainlink-github-actions/docker/image-exists@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/docker/image-exists@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: repository: chainlink tag: solana.${{ github.sha }} AWS_REGION: ${{ secrets.QA_AWS_REGION }} AWS_ROLE_TO_ASSUME: ${{ secrets.QA_AWS_ROLE_TO_ASSUME }} - name: Build Image - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/build-image@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: should_checkout: true cl_repo: smartcontractkit/chainlink @@ -142,9 +142,9 @@ jobs: QA_AWS_REGION: ${{ secrets.QA_AWS_REGION }} QA_AWS_ACCOUNT_NUMBER: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }} - name: Run Tests - uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/chainlink-testing-framework/run-tests@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: - test_command_to_run: cd ./integration-tests && go test -timeout 5h -count=1 -json $(args) ./soak 2>&1 | tee /tmp/gotest.log | gotestfmt + test_command_to_run: cd ./integration-tests && go test -timeout 5h -count=1 -json $(args) ./soak 2>&1 | tee /tmp/gotest.log | gotestloghelper -ci -singlepackage go_mod_path: ./integration-tests/go.mod cl_repo: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink cl_image_tag: solana.${{ github.sha }} diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index 2e225e4f1..8e5b8fd57 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -19,7 +19,7 @@ jobs: - name: Wait for Workflows id: wait - uses: smartcontractkit/chainlink-github-actions/utils/wait-for-workflows@e29366cdecfe6befff9ab8c3cfe4825218505d58 # v2.3.16 + uses: smartcontractkit/chainlink-github-actions/utils/wait-for-workflows@b49a9d04744b0237908831730f8553f26d73a94b # v2.3.17 with: max-timeout: "1200" polling-interval: "30" diff --git a/.tool-versions b/.tool-versions index 52ebfcbb2..3e82cc776 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,10 +1,9 @@ -nodejs 18.13.0 +nodejs 18.20.2 yarn 1.22.19 rust 1.59.0 -golang 1.21.6 +golang 1.21.7 golangci-lint 1.55.2 pulumi 3.40.1 -ginkgo 2.5.1 actionlint 1.6.22 shellcheck 0.8.0 helm 3.9.4 diff --git a/Makefile b/Makefile index 288ea388e..7c6f01314 100644 --- a/Makefile +++ b/Makefile @@ -39,13 +39,13 @@ ifeq ($(OSFLAG),$(OSX)) asdf plugin add shellcheck || true asdf plugin add kubectl || true asdf install + go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@latest endif ifeq ($(OSFLAG),$(LINUX)) ifneq ($(CI),true) # install nix sh <(curl -L https://nixos-nix-install-tests.cachix.org/serve/vij683ly7sl95nnhb67bdjjfabclr85m/install) --daemon --tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve --nix-extra-conf-file ./nix.conf endif - go install github.com/onsi/ginkgo/v2/ginkgo@v$(shell cat ./.tool-versions | grep ginkgo | sed -En "s/ginkgo.(.*)/\1/p") endif .PHONY: projectserum_version @@ -82,7 +82,7 @@ test_relay_unit: test_smoke: cd ./integration-tests &&\ - SELECTED_NETWORKS=SIMULATED go test -timeout 24h -count=1 -json $(args) -run TestSolanaOCRV2Smoke ./smoke 2>&1 | tee /tmp/gotest.log | gotestfmt + SELECTED_NETWORKS=SIMULATED go test -timeout 24h -count=1 -json $(args) -run TestSolanaOCRV2Smoke ./smoke 2>&1 | tee /tmp/gotest.log | gotestloghelper -json -tlogprefix -singlepackage -color gomodtidy: go mod tidy diff --git a/shell.nix b/shell.nix index dd6049a1d..f00b6b6ee 100644 --- a/shell.nix +++ b/shell.nix @@ -39,7 +39,11 @@ pkgs.mkShell { LD_LIBRARY_PATH = lib.makeLibraryPath [pkgs.zlib stdenv.cc.cc.lib]; # lib64 - # Avoids issues with delve CGO_CPPFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"; + + shellHook = '' + # install gotestloghelper + go install github.com/smartcontractkit/chainlink-testing-framework/tools/gotestloghelper@latest + ''; }