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/actions/build-test-image/action.yml b/.github/actions/build-test-image/action.yml index ae57a5616..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 }} @@ -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/.github/workflows/e2e_custom_cl.yml b/.github/workflows/e2e_custom_cl.yml index a7c40bf2b..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 @@ -122,22 +122,19 @@ 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 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' @@ -150,19 +147,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 +169,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@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 cl_repo: ${{ env.CL_ECR }} cl_image_tag: solana.${{ env.CUSTOM_CORE_REF || github.event.inputs.cl_branch_ref || github.sha }} 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/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 18f1cbe66..772dc434e 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -16,6 +16,10 @@ jobs: nix_path: nixpkgs=channel:nixos-unstable - name: golangci-lint run: nix develop -c make lint-go-integration-tests + - name: Print lint report artifact + if: failure() + shell: bash + run: cat ./integration-tests/golangci-lint-integration-tests-report.xml - name: Store lint report artifact if: always() uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 @@ -35,9 +39,13 @@ jobs: nix_path: nixpkgs=channel:nixos-unstable - name: golangci-lint run: nix develop -c make lint-go-relay + - name: Print lint report artifact + if: failure() + shell: bash + run: cat ./pkg/golangci-lint-relay-report.xml - name: Store lint report artifact if: always() uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: golangci-lint-relay-report - path: ./pkg/golangci-lint-relay-report.xml \ No newline at end of file + path: ./pkg/golangci-lint-relay-report.xml 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/.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 4451aa237..3e82cc776 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,10 +1,9 @@ -nodejs 16.13.2 +nodejs 18.20.2 yarn 1.22.19 rust 1.59.0 -golang 1.21.1 -golangci-lint 1.52.1 +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 43acdd31a..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,11 +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 - -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 + 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 @@ -94,11 +90,11 @@ gomodtidy: .PHONY: lint-go-integration-tests lint-go-integration-tests: - cd ./integration-tests && golangci-lint --max-issues-per-linter 0 --max-same-issues 0 --color=always --exclude=dot-imports --timeout 10m --out-format checkstyle:golangci-lint-integration-tests-report.xml run || true + cd ./integration-tests && golangci-lint --max-issues-per-linter 0 --max-same-issues 0 --color=always --exclude=dot-imports --timeout 10m --out-format checkstyle:golangci-lint-integration-tests-report.xml run .PHONY: lint-go-relay lint-go-relay: - cd ./pkg && golangci-lint --max-issues-per-linter 0 --max-same-issues 0 --color=always --exclude=dot-imports --timeout 10m --out-format checkstyle:golangci-lint-relay-report.xml run || true + cd ./pkg && golangci-lint --max-issues-per-linter 0 --max-same-issues 0 --color=always --exclude=dot-imports --timeout 10m --out-format checkstyle:golangci-lint-relay-report.xml run .PHONY: upgrade-e2e-solana-image upgrade-e2e-solana-image: diff --git a/cmd/monitoring/main.go b/cmd/monitoring/main.go index 80d58abf8..3fd8c97cf 100644 --- a/cmd/monitoring/main.go +++ b/cmd/monitoring/main.go @@ -95,17 +95,22 @@ func main() { metrics.NewFeedBalances(logger.With(log, "component", promMetrics)), ) reportObservationsFactory := exporter.NewReportObservationsFactory( - logger.With(log, "component", "solana-prome-exporter"), + logger.With(log, "component", promExporter), metrics.NewReportObservations(logger.With(log, "component", promMetrics)), ) feesFactory := exporter.NewFeesFactory( - logger.With(log, "component", "solana-prome-exporter"), + logger.With(log, "component", promExporter), metrics.NewFees(logger.With(log, "component", promMetrics)), ) + nodeSuccessFactory := exporter.NewNodeSuccessFactory( + logger.With(log, "component", promExporter), + metrics.NewNodeSuccess(logger.With(log, "component", promMetrics)), + ) monitor.ExporterFactories = append(monitor.ExporterFactories, feedBalancesExporterFactory, reportObservationsFactory, feesFactory, + nodeSuccessFactory, ) // network exporters 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/flake.lock b/flake.lock index 5aacf0d5e..0b9d870fd 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1692799911, - "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { @@ -23,11 +23,11 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1705309234, + "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1694183432, - "narHash": "sha256-YyPGNapgZNNj51ylQMw9lAgvxtM2ai1HZVUu3GS8Fng=", + "lastModified": 1707689078, + "narHash": "sha256-UUGmRa84ZJHpGZ1WZEBEUOzaPOWG8LZ0yPg1pdDF/yM=", "owner": "nixos", "repo": "nixpkgs", - "rev": "db9208ab987cdeeedf78ad9b4cf3c55f5ebd269b", + "rev": "f9d39fb9aff0efee4a3d5f4a6d7c17701d38a1d8", "type": "github" }, "original": { @@ -54,11 +54,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1681358109, - "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", + "lastModified": 1706487304, + "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", + "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", "type": "github" }, "original": { @@ -81,11 +81,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1694398298, - "narHash": "sha256-Hi904+2V5DJhFdEy9DcARSRrGJOlYSILHUC6CgTtuZU=", + "lastModified": 1707790272, + "narHash": "sha256-KQXPNl3BLdRbz7xx+mwIq/017fxLRk6JhXHxVWCKsTU=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6c520f2e31f4bebeb29cc4563543de7187013575", + "rev": "8dfbe2dffc28c1a18a29ffa34d5d0b269622b158", "type": "github" }, "original": { diff --git a/gauntlet/package.json b/gauntlet/package.json index c71817d41..84485cf22 100644 --- a/gauntlet/package.json +++ b/gauntlet/package.json @@ -14,6 +14,7 @@ "bin": "packages/gauntlet-solana-contracts/dist/cli.js", "scripts": { "gauntlet": "yarn build && node ./packages/gauntlet-solana-contracts/dist/cli.js", + "gauntlet-nobuild": "node ./packages/gauntlet-solana-contracts/dist/cli.js", "gauntlet-serum-multisig": "yarn build && node ./packages/gauntlet-serum-multisig/dist/index.js", "lint": "tsc -b ./tsconfig.json", "eslint": "eslint -f json -o eslint-report.json ./packages || true", 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/go.mod b/go.mod index 58a26ac2a..344023b4b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/smartcontractkit/chainlink-solana -go 1.21 +go 1.21.6 require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc diff --git a/integration-tests/common/common.go b/integration-tests/common/common.go index 9053d188e..f9528fb34 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,59 @@ 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" - test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/test_env" + 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" 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 +82,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 +125,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 +183,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 } @@ -300,7 +287,7 @@ func OffChainConfigParamsFromNodes(nodeCount int, nkb []client.NodeKeysBundle) ( }, nil } -func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, mockUrl string, isK8s bool) error { +func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, mockURL string, isK8s bool) error { for i, nodesInfo := range ContractsIdxMapToContractsNodeInfo { // Bootstrap node first var err error @@ -315,7 +302,7 @@ func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, } sourceValueBridge := client.BridgeTypeAttributes{ Name: nodeContractPairID, - URL: fmt.Sprintf("%s/%s", mockUrl, "five"), + URL: fmt.Sprintf("%s/%s", mockURL, "five"), RequestData: "{}", } observationSource := client.ObservationSourceSpecBridge(&sourceValueBridge) @@ -329,7 +316,7 @@ func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, } juelsBridge := client.BridgeTypeAttributes{ Name: nodeContractPairID + "juels", - URL: fmt.Sprintf("%s/%s", mockUrl, "five"), + URL: fmt.Sprintf("%s/%s", mockURL, "five"), RequestData: "{}", } juelsSource := client.ObservationSourceSpecBridge(&juelsBridge) @@ -362,7 +349,7 @@ func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, } sourceValueBridge := client.BridgeTypeAttributes{ Name: nodeContractPairID, - URL: fmt.Sprintf("%s/%s", mockUrl, "five"), + URL: fmt.Sprintf("%s/%s", mockURL, "five"), RequestData: "{}", } observationSource := client.ObservationSourceSpecBridge(&sourceValueBridge) @@ -376,7 +363,7 @@ func CreateBridges(ContractsIdxMapToContractsNodeInfo map[int]*ContractNodeInfo, } juelsBridge := client.BridgeTypeAttributes{ Name: nodeContractPairID + "juels", - URL: fmt.Sprintf("%s/%s", mockUrl, "five"), + URL: fmt.Sprintf("%s/%s", mockURL, "five"), RequestData: "{}", } juelsSource := client.ObservationSourceSpecBridge(&juelsBridge) @@ -403,7 +390,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 +398,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 +416,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 +425,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 +444,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 +455,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 +484,18 @@ func BuildNodeContractPairID(node *client.ChainlinkClient, ocr2Addr string) (str } func (c *Common) DefaultNodeConfig() *cl.Config { - solConfig := solana.TOMLConfig{ + solConfig := solcfg.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 = solcfg.TOMLConfigs{ &solConfig, } baseConfig.OCR2.Enabled = ptr.Ptr(true) @@ -523,25 +510,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 c809a6f1d..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/test_env" - "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"` -} -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"` -} + "github.com/smartcontractkit/chainlink/v2/core/services/job" + "github.com/smartcontractkit/chainlink/v2/core/store/models" -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,425 +173,189 @@ 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{ - m.Common.DockerEnv.Sol.ExternalHttpUrl, - m.Common.DockerEnv.Sol.ExternalWsUrl, + m.Common.DockerEnv.Sol.ExternalHTTPURL, + m.Common.DockerEnv.Sol.ExternalWsURL, } } ec, err := solclient.NewClient(networkSettings) 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 -} -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()) -} + observationSource := client.ObservationSourceSpecBridge(&sourceValueBridge) + bridgeInfo := BridgeInfo{ObservationSource: observationSource} -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") - } - 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") - } - - programIdStore, exists := os.LookupEnv("PROGRAM_ID_STORE") - if !exists { - panic("Please define PROGRAM_ID_STORE") +func formatBuffer(buf []byte) string { + if len(buf) == 0 { + return "" } - - linkToken, exists := os.LookupEnv("LINK_TOKEN") - if !exists { - panic("Please define LINK_TOKEN") + result := fmt.Sprintf("%d", buf[0]) + for _, b := range buf[1:] { + result += fmt.Sprintf(",%d", b) } - - 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() { - osutil.SetupEnvVarsForRemoteRunner([]string{ - "RPC_URL", - "WS_URL", - "PRIVATE_KEY", - "PROGRAM_ID_OCR2", - "PROGRAM_ID_ACCESS_CONTROLLER", - "PROGRAM_ID_STORE", - "LINK_TOKEN", - "VAULT_ADDRESS", - }) + 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/test_env/sol.go b/integration-tests/docker/testenv/sol.go similarity index 69% rename from integration-tests/docker/test_env/sol.go rename to integration-tests/docker/testenv/sol.go index 8bec26f7f..9233d1cde 100644 --- a/integration-tests/docker/test_env/sol.go +++ b/integration-tests/docker/testenv/sol.go @@ -1,4 +1,4 @@ -package test_env +package testenv import ( "context" @@ -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" @@ -25,11 +28,11 @@ import ( ) const ( - SOL_HTTP_PORT = "8899" - SOL_WS_PORT = "8900" + SolHTTPPort = "8899" + SolWSPort = "8900" ) -var config_yml = ` +var configYmlRaw = ` json_rpc_url: http://0.0.0.0:8899 websocket_url: ws://0.0.0.0:8900 keypair_path: /root/.config/solana/cli/id.json @@ -38,27 +41,31 @@ address_labels: commitment: finalized ` -var id_json = ` -[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] +var idJSONRaw = ` +[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 { test_env.EnvComponent - ExternalHttpUrl string - ExternalWsUrl string - InternalHttpUrl string - InternalWsUrl string + ExternalHTTPURL string + ExternalWsURL string + InternalHTTPURL string + 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) @@ -105,29 +112,29 @@ func (s *Solana) StartContainer() error { if err != nil { return err } - httpPort, err := c.MappedPort(testcontext.Get(s.t), test_env.NatPort(SOL_HTTP_PORT)) + httpPort, err := c.MappedPort(testcontext.Get(s.t), test_env.NatPort(SolHTTPPort)) if err != nil { return err } - wsPort, err := c.MappedPort(testcontext.Get(s.t), test_env.NatPort(SOL_WS_PORT)) + wsPort, err := c.MappedPort(testcontext.Get(s.t), test_env.NatPort(SolWSPort)) if err != nil { return err } - s.ExternalHttpUrl = fmt.Sprintf("http://%s:%s", host, httpPort.Port()) - s.InternalHttpUrl = fmt.Sprintf("http://%s:%s", s.ContainerName, SOL_HTTP_PORT) - s.ExternalWsUrl = fmt.Sprintf("ws://%s:%s", host, wsPort.Port()) - s.InternalWsUrl = fmt.Sprintf("ws://%s:%s", s.ContainerName, SOL_WS_PORT) + s.ExternalHTTPURL = fmt.Sprintf("http://%s:%s", host, httpPort.Port()) + s.InternalHTTPURL = fmt.Sprintf("http://%s:%s", s.ContainerName, SolHTTPPort) + s.ExternalWsURL = fmt.Sprintf("ws://%s:%s", host, wsPort.Port()) + s.InternalWsURL = fmt.Sprintf("ws://%s:%s", s.ContainerName, SolWSPort) s.l.Info(). - Any("ExternalHttpUrl", s.ExternalHttpUrl). - Any("InternalHttpUrl", s.InternalHttpUrl). - Any("ExternalWsUrl", s.ExternalWsUrl). - Any("InternalWsUrl", s.InternalWsUrl). + Any("ExternalHTTPURL", s.ExternalHTTPURL). + Any("InternalHTTPURL", s.InternalHTTPURL). + Any("ExternalWsURL", s.ExternalWsURL). + Any("InternalWsURL", s.InternalWsURL). Str("containerName", s.ContainerName). Msgf("Started Solana container") // validate features are properly set - inactiveLocalFeatures, err := GetInactiveFeatureHashes(s.ExternalHttpUrl) + inactiveLocalFeatures, err := GetInactiveFeatureHashes(s.ExternalHTTPURL) if err != nil { return err } @@ -137,43 +144,43 @@ func (s *Solana) StartContainer() error { return nil } -func (ms *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.ContainerRequest, error) { +func (s *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.ContainerRequest, error) { configYml, err := os.CreateTemp("", "config.yml") if err != nil { return nil, err } - _, err = configYml.WriteString(config_yml) + _, err = configYml.WriteString(configYmlRaw) if err != nil { return nil, err } - idJson, err := os.CreateTemp("", "id.json") + idJSON, err := os.CreateTemp("", "id.json") if err != nil { return nil, err } - _, err = idJson.WriteString(id_json) + _, err = idJSON.WriteString(idJSONRaw) if err != nil { return nil, err } return &tc.ContainerRequest{ - Name: ms.ContainerName, - Image: "solanalabs/solana:v1.17.33", - ExposedPorts: []string{test_env.NatPortFormat(SOL_HTTP_PORT), test_env.NatPortFormat(SOL_WS_PORT)}, + Name: s.ContainerName, + Image: s.Image, + ExposedPorts: []string{test_env.NatPortFormat(SolHTTPPort), test_env.NatPortFormat(SolWSPort)}, Env: map[string]string{ "SERVER_PORT": "1080", }, - Networks: ms.Networks, + Networks: s.Networks, WaitingFor: tcwait.ForLog("Processed Slot: 1"). WithStartupTimeout(30 * time.Second). WithPollInterval(100 * time.Millisecond), - Mounts: tc.ContainerMounts{ - tc.ContainerMount{ - Source: tc.GenericBindMountSource{ - 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{ { @@ -183,13 +190,13 @@ func (ms *Solana) getContainerRequest(inactiveFeatures InactiveFeatures) (*tc.Co if err != nil { return err } - err = container.CopyFileToContainer(ctx, idJson.Name(), "/root/.config/solana/cli/id.json", 0644) + err = container.CopyFileToContainer(ctx, idJSON.Name(), "/root/.config/solana/cli/id.json", 0644) return err }, }, }, }, - 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 } @@ -215,7 +222,7 @@ func (f InactiveFeatures) CLIString() string { // This is used in conjunction with the solana-test-validator command to produce a solana network that has the same features as mainnet // the solana-test-validator has all features on by default (released + unreleased) func GetInactiveFeatureHashes(url string) (output InactiveFeatures, err error) { - cmd := exec.Command("solana", "feature", "status", "-u="+url, "--output=json") // -um is for mainnet url + cmd := exec.Command("solana", "feature", "status", "-u="+url, "--output=json") //nolint:gosec // -um is for mainnet url stdout, err := cmd.Output() if err != nil { return nil, fmt.Errorf("Failed to get feature status: %w", err) diff --git a/integration-tests/gauntlet/gauntlet_solana.go b/integration-tests/gauntlet/gauntlet_solana.go index 5244dc6cf..ef3884fe2 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,9 +15,10 @@ var ( ) type SolanaGauntlet struct { - dir string + Dir string + NetworkFilePath string G *gauntlet.Gauntlet - gr *GauntletResponse + gr *Response options *gauntlet.ExecCommandOptions AccessControllerAddress string BillingControllerAddress string @@ -25,33 +26,13 @@ type SolanaGauntlet struct { FeedAddress string OcrAddress string ProposalAddress string + OCR2Config *ocr2_config.OCR2Config + LinkAddress string + VaultAddress 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"` -} - -// GauntletResponse Default response output for starknet gauntlet commands -type GauntletResponse struct { +// Response Default response output for starknet gauntlet commands +type Response struct { Responses []struct { Tx struct { Hash string `json:"hash"` @@ -70,12 +51,13 @@ type GauntletResponse struct { Data struct { Proposal *string `json:"proposal,omitempty"` LatestTransmissions *[]Transmission `json:"latestTransmissions,omitempty"` + Vault *string `json:"vault,omitempty"` } } type Transmission struct { LatestTransmissionNo int64 `json:"latestTransmissionNo"` - RoundId int64 `json:"roundId"` + RoundID int64 `json:"roundId"` Answer int64 `json:"answer"` Transmitter string `json:"transmitter"` } @@ -83,26 +65,32 @@ type Transmission struct { // NewSolanaGauntlet Creates a default gauntlet config func NewSolanaGauntlet(workingDir string) (*SolanaGauntlet, error) { g, err := gauntlet.NewGauntlet() - g.SetWorkingDir(workingDir) if err != nil { return nil, err } sg = &SolanaGauntlet{ - dir: workingDir, - G: g, - gr: &GauntletResponse{}, + 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 } -// FetchGauntletJsonOutput Parse gauntlet json response that is generated after yarn gauntlet command execution -func (sg *SolanaGauntlet) FetchGauntletJsonOutput() (*GauntletResponse, error) { - var payload = &GauntletResponse{} - gauntletOutput, err := os.ReadFile(sg.dir + "/report.json") +// 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") if err != nil { return payload, err } @@ -118,7 +106,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 } @@ -128,48 +116,82 @@ func (sg *SolanaGauntlet) SetupNetwork(args map[string]string) error { func (sg *SolanaGauntlet) InstallDependencies() error { sg.G.Command = "yarn" - _, err := sg.G.ExecCommand([]string{"install"}, *sg.options) + _, err := sg.G.ExecCommand([]string{"--cwd", sg.Dir, "install"}, *sg.options) if err != nil { return err } - sg.G.Command = "gauntlet" + _, err = sg.G.ExecCommand([]string{"--cwd", sg.Dir, "build"}, *sg.options) // initial build + if err != nil { + return err + } + sg.G.Command = "gauntlet-nobuild" // optimization to not rebuild packages each time return nil } +// exect is a custom wrapper to use custom set gauntlet command + error wrapping +func (sg *SolanaGauntlet) exec(args []string, options gauntlet.ExecCommandOptions) (string, error) { + updatedArgs := []string{"--cwd", sg.Dir, sg.G.Command, args[0], sg.G.Flag("network", sg.G.Network)} + if len(args) > 1 { + updatedArgs = append(updatedArgs, args[1:]...) + } + + out, err := sg.G.ExecCommand(updatedArgs, options) + // wrapping output into err if err present + if err != nil { + err = fmt.Errorf("%w\ngauntlet command: %s\nstdout: %s", err, updatedArgs, out) + } + return out, err +} + func (sg *SolanaGauntlet) InitializeAccessController() (string, error) { - _, err := sg.G.ExecCommand([]string{"access_controller:initialize"}, *sg.options) + _, err := sg.exec([]string{"access_controller:initialize"}, *sg.options) if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } return sg.gr.Responses[0].Contract, nil } +func (sg *SolanaGauntlet) DeployLinkToken() error { + _, err := sg.exec([]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) + _, err := sg.exec([]string{"store:initialize", fmt.Sprintf("--accessController=%s", billingController)}, *sg.options) if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", 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 } - _, err = sg.G.ExecCommand([]string{"store:create_feed", fmt.Sprintf("--length=%d", length), fmt.Sprintf("--input=%v", string(config))}, *sg.options) + _, err = sg.exec([]string{"store:create_feed", fmt.Sprintf("--length=%d", length), fmt.Sprintf("--input=%v", string(config))}, *sg.options) if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -178,23 +200,23 @@ func (sg *SolanaGauntlet) StoreCreateFeed(length int, feedConfig *StoreFeedConfi } func (sg *SolanaGauntlet) StoreSetValidatorConfig(feedAddress string, threshold int) (string, error) { - _, err := sg.G.ExecCommand([]string{"store:set_validator_config", fmt.Sprintf("--feed=%s", feedAddress), fmt.Sprintf("--threshold=%d", threshold)}, *sg.options) + _, err := sg.exec([]string{"store:set_validator_config", fmt.Sprintf("--feed=%s", feedAddress), fmt.Sprintf("--threshold=%d", threshold)}, *sg.options) if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "ocr2:initialize", fmt.Sprintf("--requesterAccessController=%s", requesterAccessController), fmt.Sprintf("--billingAccessController=%s", billingAccessController), @@ -202,7 +224,7 @@ func (sg *SolanaGauntlet) InitializeOCR2(requesterAccessController string, billi if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -210,12 +232,12 @@ 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "store:set_writer", fmt.Sprintf("--input=%v", string(config)), ocrAddress, @@ -226,7 +248,7 @@ func (sg *SolanaGauntlet) StoreSetWriter(storeConfig *StoreWriterConfig, ocrAddr if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -234,12 +256,12 @@ 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "ocr2:set_billing", fmt.Sprintf("--input=%v", string(config)), ocrAddress, @@ -250,7 +272,7 @@ func (sg *SolanaGauntlet) OCR2SetBilling(ocr2BillingConfig *OCR2BillingConfig, o if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -259,7 +281,7 @@ func (sg *SolanaGauntlet) OCR2SetBilling(ocr2BillingConfig *OCR2BillingConfig, o } func (sg *SolanaGauntlet) OCR2CreateProposal(version int) (string, error) { - _, err := sg.G.ExecCommand([]string{ + _, err := sg.exec([]string{ "ocr2:create_proposal", fmt.Sprintf("--version=%d", version), }, @@ -269,7 +291,7 @@ func (sg *SolanaGauntlet) OCR2CreateProposal(version int) (string, error) { if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -277,15 +299,14 @@ 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{ + _, err = sg.exec([]string{ "ocr2:propose_config", - fmt.Sprintf("--proposalId=%s", proposalId), + fmt.Sprintf("--proposalId=%s", proposalID), fmt.Sprintf("--input=%v", string(config)), ocrFeedAddress, }, @@ -295,7 +316,7 @@ func (sg *SolanaGauntlet) ProposeOnChainConfig(proposalId string, onChainConfig if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -303,15 +324,15 @@ 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "ocr2:propose_offchain_config", - fmt.Sprintf("--proposalId=%s", proposalId), + fmt.Sprintf("--proposalId=%s", proposalID), fmt.Sprintf("--input=%v", string(config)), ocrFeedAddress, }, @@ -321,7 +342,7 @@ func (sg *SolanaGauntlet) ProposeOffChainConfig(proposalId string, offChainConfi if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -329,15 +350,15 @@ 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "ocr2:propose_payees", - fmt.Sprintf("--proposalId=%s", proposalId), + fmt.Sprintf("--proposalId=%s", proposalID), fmt.Sprintf("--input=%v", string(config)), ocrFeedAddress, }, @@ -347,7 +368,7 @@ func (sg *SolanaGauntlet) ProposePayees(proposalId string, payeesConfig common.P if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -355,10 +376,10 @@ func (sg *SolanaGauntlet) ProposePayees(proposalId string, payeesConfig common.P return sg.gr.Responses[0].Contract, nil } -func (sg *SolanaGauntlet) FinalizeProposal(proposalId string) (string, error) { - _, err := sg.G.ExecCommand([]string{ +func (sg *SolanaGauntlet) FinalizeProposal(proposalID string) (string, error) { + _, err := sg.exec([]string{ "ocr2:finalize_proposal", - fmt.Sprintf("--proposalId=%s", proposalId), + fmt.Sprintf("--proposalId=%s", proposalID), }, *sg.options, ) @@ -366,7 +387,7 @@ func (sg *SolanaGauntlet) FinalizeProposal(proposalId string) (string, error) { if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -374,15 +395,15 @@ 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 } - _, err = sg.G.ExecCommand([]string{ + _, err = sg.exec([]string{ "ocr2:accept_proposal", - fmt.Sprintf("--proposalId=%s", proposalId), + fmt.Sprintf("--proposalId=%s", proposalID), fmt.Sprintf("--secret=%s", secret), fmt.Sprintf("--input=%s", string(config)), ocrFeedAddres, @@ -393,7 +414,7 @@ func (sg *SolanaGauntlet) AcceptProposal(proposalId string, secret string, propo if err != nil { return "", err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return "", err } @@ -403,7 +424,7 @@ func (sg *SolanaGauntlet) AcceptProposal(proposalId string, secret string, propo // FetchTransmissions returns the last 10 transmissions func (sg *SolanaGauntlet) FetchTransmissions(ocrState string) ([]Transmission, error) { - _, err := sg.G.ExecCommand([]string{ + _, err := sg.exec([]string{ "ocr2:inspect:responses", ocrState, }, @@ -413,7 +434,7 @@ func (sg *SolanaGauntlet) FetchTransmissions(ocrState string) ([]Transmission, e if err != nil { return nil, err } - sg.gr, err = sg.FetchGauntletJsonOutput() + sg.gr, err = sg.FetchGauntletJSONOutput() if err != nil { return nil, err } @@ -423,11 +444,6 @@ func (sg *SolanaGauntlet) FetchTransmissions(ocrState string) ([]Transmission, e func (sg *SolanaGauntlet) DeployOCR2() (string, error) { var err error - err = sg.InstallDependencies() - if err != nil { - return "", err - } - sg.AccessControllerAddress, err = sg.InitializeAccessController() if err != nil { return "", err @@ -442,7 +458,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 +476,7 @@ func (sg *SolanaGauntlet) DeployOCR2() (string, error) { return "", err } - ocr2Config := &OCR2Config{ + ocr2Config := &ocr2_config.OCR2TransmitConfig{ MinAnswer: "0", MaxAnswer: "10000000000", Transmissions: sg.FeedAddress, @@ -471,14 +487,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 +508,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 +532,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 0a15ea9de..44e450646 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -5,25 +5,28 @@ go 1.21.7 replace github.com/smartcontractkit/chainlink-solana => ../ require ( - github.com/ethereum/go-ethereum v1.13.8 + 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.20240516194305-68ad445a20fc - github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240510181707-46b1311a5a83 - github.com/smartcontractkit/chainlink-testing-framework v1.28.12 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240517134904-f4446b816a28 + github.com/smartcontractkit/chainlink-solana v1.0.3-0.20240521200803-6c605f618787 + 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/chainlink/v2 v2.10.0-beta0.0.20240521201249-c00f33248fe4 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 ) @@ -67,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 @@ -126,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 @@ -136,6 +137,7 @@ require ( github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/esote/minmaxheap v1.0.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/ethereum/go-ethereum v1.13.8 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // 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 8d824d2b3..dba5d06f8 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1406,8 +1406,8 @@ github.com/smartcontractkit/chain-selectors v1.0.10 h1:t9kJeE6B6G+hKD0GYR4kGJSCq github.com/smartcontractkit/chain-selectors v1.0.10/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.3 h1:h/ijT0NiyV06VxYVgcNfsE3+8OEzT3Q0Z9au0z1BPWs= github.com/smartcontractkit/chainlink-automation v1.0.3/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240516194305-68ad445a20fc h1:5e+avLgoMHqXA31TLDAcRmSNp/knVEdkB2fyQq6LYRA= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240516194305-68ad445a20fc/go.mod h1:sj0pjL+METqeYL9ibp0T8SXquymlaQsofa6bdfLgXX8= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240517134904-f4446b816a28 h1:Pr8/CdiTNnzRwpYc2z7NpHYbw3Dpl1eqiqt9/J/Bcqc= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240517134904-f4446b816a28/go.mod h1:s+68EchlrXqHKRW3JJgZLEARvzMSKRI5+cE5Zx7pVJA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240508101745-af1ed7bc8a69 h1:Sec/GpBpUVaTEax1kSHlTvkzF/+d3w5roAQXaj5+SLA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240508101745-af1ed7bc8a69/go.mod h1:ZQKf+0OLzCLYIisH/OdOIQuFRI6bDuw+jPBTATyHfFM= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= @@ -1416,16 +1416,16 @@ 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 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= github.com/smartcontractkit/chainlink-vrf v0.0.0-20240222010609-cd67d123c772/go.mod h1:Kn1Hape05UzFZ7bOUnm3GVsHzP0TNrVmpfXYNHdqGGs= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240515225456-aeb9f4d50d65 h1:8AoBDPHOLgZA1JodqysYK/JxcVbjwNhyGfmwzQuep4s= github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240515225456-aeb9f4d50d65/go.mod h1:DOeyxJuvSV8No26UHAtmvZTycuGe0S4w/XMMj1EGMV8= -github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240515225456-aeb9f4d50d65 h1:ba2ZooA598i9P2qakTggT81f5TIFI9efPVsGy7MjY9Y= -github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240515225456-aeb9f4d50d65/go.mod h1:ICLCfUotU6Zk+S2kry3XE6i3lyhk30sr2rz89Y5QkGI= +github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240521201249-c00f33248fe4 h1:KrQxqehwHCpbJJtltv2iWlVR+cxNzEoBN8EFtbwWtPg= +github.com/smartcontractkit/chainlink/v2 v2.10.0-beta0.0.20240521201249-c00f33248fe4/go.mod h1:10XUZ0WuFDdW+RYD0PdTpHlnaxkh/sqeAtjrAV6vUvQ= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306 h1:ko88+ZznniNJZbZPWAvHQU8SwKAdHngdDZ+pvVgB5ss= github.com/smartcontractkit/go-plugin v0.0.0-20231003134350-e49dad63b306/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/smartcontractkit/grpc-proxy v0.0.0-20230731113816-f1be6620749f h1:hgJif132UCdjo8u43i7iPN1/MFnu49hv7lFGFftCHKU= 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/smoke/ocr2_test.go b/integration-tests/smoke/ocr2_test.go index 3c972d161..77ca5d0fa 100644 --- a/integration-tests/smoke/ocr2_test.go +++ b/integration-tests/smoke/ocr2_test.go @@ -3,32 +3,20 @@ package smoke import ( "fmt" "maps" - "sort" + "os/exec" "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 +35,125 @@ 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) + name := "gauntlet-" + test.name + state, err := common.NewOCRv2State(t, 1, 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) + state.DeployCluster(utils.ContractsDir) - err = state.Common.CreateSolanaChainAndNode(state.GetChainlinkNodes()) - require.NoError(t, err) + // copy gauntlet folder to run in parallel (gauntlet generates an output file that is read by the e2e tests - causes conflict if shared) + gauntletCopyPath := utils.ProjectRoot + "/" + name + if out, cpErr := exec.Command("cp", "-r", utils.ProjectRoot+"/gauntlet", gauntletCopyPath).Output(); cpErr != nil { // nolint:gosec + require.NoError(t, err, "output: "+string(out)) + } - // 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(gauntletCopyPath) + 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") + err = sg.InstallDependencies() + require.NoError(t, err, "Error installing gauntlet dependencies") + + 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/solclient/deployer.go b/integration-tests/solclient/deployer.go index 63089f1f5..f24a18a27 100644 --- a/integration-tests/solclient/deployer.go +++ b/integration-tests/solclient/deployer.go @@ -19,7 +19,7 @@ import ( access_controller2 "github.com/smartcontractkit/chainlink-solana/contracts/generated/access_controller" ocr_2 "github.com/smartcontractkit/chainlink-solana/contracts/generated/ocr2" store2 "github.com/smartcontractkit/chainlink-solana/contracts/generated/store" - test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/test_env" + test_env_sol "github.com/smartcontractkit/chainlink-solana/integration-tests/docker/testenv" ) // All account sizes are calculated from Rust structures, ex. programs/access-controller/src/lib.rs:L80 @@ -234,17 +234,6 @@ func (c *ContractDeployer) CreateFeed(desc string, decimals uint8, granularity i return nil } -func (c *ContractDeployer) addMintToAccInstr(instr *[]solana.Instruction, dest solana.PublicKey, amount uint64) error { - *instr = append(*instr, token.NewMintToInstruction( - amount, - c.Accounts.Mint.PublicKey(), - dest, - c.Accounts.MintAuthority.PublicKey(), - nil, - ).Build()) - return nil -} - func (c *ContractDeployer) DeployLinkTokenContract() (*LinkToken, error) { var err error payer := c.Client.DefaultWallet diff --git a/integration-tests/solclient/ocr2.go b/integration-tests/solclient/ocr2.go index 2c8ac7598..c2f0f7e29 100644 --- a/integration-tests/solclient/ocr2.go +++ b/integration-tests/solclient/ocr2.go @@ -190,12 +190,6 @@ func (m *OCRv2) makeDigest() ([]byte, error) { func (m *OCRv2) fetchProposalAccount() (*ocr_2.Proposal, error) { var proposal ocr_2.Proposal - err := m.Client.RPC.GetAccountDataInto( - context.Background(), - m.Proposal.PublicKey(), - &proposal, - ) - // reimplement GetAccountDataInto with options resp, err := m.Client.RPC.GetAccountInfoWithOpts( context.Background(), m.Proposal.PublicKey(), diff --git a/integration-tests/solclient/solclient.go b/integration-tests/solclient/solclient.go index ea54027bb..d2d3a638c 100644 --- a/integration-tests/solclient/solclient.go +++ b/integration-tests/solclient/solclient.go @@ -10,9 +10,6 @@ import ( "path/filepath" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/smartcontractkit/chainlink-testing-framework/blockchain" "github.com/gagliardetto/solana-go" @@ -89,14 +86,6 @@ type Client struct { LinkToken *LinkToken } -func (c *Client) BalanceAt(ctx context.Context, address common.Address) (*big.Int, error) { - panic("implement me") -} - -func (c *Client) GetTxReceipt(txHash common.Hash) (*types.Receipt, error) { - panic("implement me") -} - func (c *Client) GetNetworkType() string { return c.Config.Type } @@ -472,22 +461,6 @@ func (c *Client) LatestBlockNumber(ctx context.Context) (uint64, error) { panic("implement me") } -func (c *Client) DeployContract(contractName string, deployer blockchain.ContractDeployer) (*common.Address, *types.Transaction, interface{}, error) { - panic("implement me") -} - -func (c *Client) TransactionOpts(from *blockchain.EthereumWallet) (*bind.TransactOpts, error) { - panic("implement me") -} - -func (c *Client) ProcessTransaction(tx *types.Transaction) error { - panic("implement me") -} - -func (c *Client) IsTxConfirmed(txHash common.Hash) (bool, error) { - panic("implement me") -} - func (c *Client) GasStats() *blockchain.GasStats { panic("implement me") } 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 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..c6744a9c2 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.34" + +[OCR2] +node_count = 6 +test_duration = "50m" + +[OCR2.Smoke] +number_of_rounds = 2 + 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 +} diff --git a/ops/monitoring/Dockerfile b/ops/monitoring/Dockerfile index 60092d296..7ae9a9c25 100644 --- a/ops/monitoring/Dockerfile +++ b/ops/monitoring/Dockerfile @@ -1,6 +1,6 @@ # Build image -FROM golang:1.21.5 AS build +FROM golang:1.21.10 AS build # OS dependencies RUN apt-get update && apt-get install -y wget gcc diff --git a/pkg/monitoring/exporter/nodesuccess.go b/pkg/monitoring/exporter/nodesuccess.go new file mode 100644 index 000000000..8c3b660b2 --- /dev/null +++ b/pkg/monitoring/exporter/nodesuccess.go @@ -0,0 +1,111 @@ +package exporter + +import ( + "context" + + "github.com/gagliardetto/solana-go" + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/config" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func NewNodeSuccessFactory( + log commonMonitoring.Logger, + metrics metrics.NodeSuccess, +) commonMonitoring.ExporterFactory { + return &nodeSuccessFactory{ + log, + metrics, + } +} + +type nodeSuccessFactory struct { + log commonMonitoring.Logger + metrics metrics.NodeSuccess +} + +func (p *nodeSuccessFactory) NewExporter( + params commonMonitoring.ExporterParams, +) (commonMonitoring.Exporter, error) { + nodes, err := config.MakeSolanaNodeConfigs(params.Nodes) + if err != nil { + return nil, err + } + + nodesMap := map[solana.PublicKey]string{} + for _, v := range nodes { + pubkey, err := v.PublicKey() + if err != nil { + return nil, err + } + nodesMap[pubkey] = v.GetName() + } + + return &nodeSuccess{ + metrics.FeedInput{ + AccountAddress: params.FeedConfig.GetContractAddress(), + FeedID: params.FeedConfig.GetContractAddress(), + ChainID: params.ChainConfig.GetChainID(), + ContractStatus: params.FeedConfig.GetContractStatus(), + ContractType: params.FeedConfig.GetContractType(), + FeedName: params.FeedConfig.GetName(), + FeedPath: params.FeedConfig.GetPath(), + NetworkID: params.ChainConfig.GetNetworkID(), + NetworkName: params.ChainConfig.GetNetworkName(), + }, + nodesMap, + p.log, + p.metrics, + }, nil +} + +type nodeSuccess struct { + feedLabel metrics.FeedInput // static for each feed + nodes map[solana.PublicKey]string + log commonMonitoring.Logger + metrics metrics.NodeSuccess +} + +func (p *nodeSuccess) Export(ctx context.Context, data interface{}) { + details, err := types.MakeTxDetails(data) + if err != nil { + return // skip if input could not be parsed + } + + // skip on no updates + if len(details) == 0 { + return + } + + // calculate count + count := map[solana.PublicKey]int{} + for _, d := range details { + count[d.Sender]++ + } + + for k, v := range count { + name, isOperator := p.nodes[k] + if !isOperator { + p.log.Debugw("Sender does not match known operator", "sender", k) + continue // skip if not known operator + } + + p.metrics.Add(v, metrics.NodeFeedInput{ + NodeAddress: k.String(), + NodeOperator: name, + FeedInput: p.feedLabel, + }) + } +} + +func (p *nodeSuccess) Cleanup(_ context.Context) { + for k, v := range p.nodes { + p.metrics.Cleanup(metrics.NodeFeedInput{ + NodeAddress: k.String(), + NodeOperator: v, + FeedInput: p.feedLabel, + }) + } +} diff --git a/pkg/monitoring/exporter/nodesuccess_test.go b/pkg/monitoring/exporter/nodesuccess_test.go new file mode 100644 index 000000000..3f6f8bfa0 --- /dev/null +++ b/pkg/monitoring/exporter/nodesuccess_test.go @@ -0,0 +1,55 @@ +package exporter + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/config" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics/mocks" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/testutils" + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func TestNodeSuccess(t *testing.T) { + zeroAddress := solana.PublicKey{} + ctx := tests.Context(t) + lgr, logs := logger.TestObserved(t, zapcore.DebugLevel) + m := mocks.NewNodeSuccess(t) + m.On("Add", mock.Anything, mock.Anything).Once() + m.On("Cleanup", mock.Anything).Once() + + factory := NewNodeSuccessFactory(lgr, m) + + chainConfig := testutils.GenerateChainConfig() + feedConfig := testutils.GenerateFeedConfig() + exporter, err := factory.NewExporter(commonMonitoring.ExporterParams{ChainConfig: chainConfig, + FeedConfig: feedConfig, + Nodes: []commonMonitoring.NodeConfig{ + config.SolanaNodeConfig{ + NodeAddress: []string{zeroAddress.String()}}, + }}) + require.NoError(t, err) + + // happy path - only one call (only 1 address is recognized) + exporter.Export(ctx, []types.TxDetails{ + {Sender: zeroAddress}, + {Sender: solana.PublicKey{1}}, + }) + exporter.Cleanup(ctx) + assert.Equal(t, 1, logs.FilterMessageSnippet("Sender does not match known operator").Len()) + + // not txdetails type - no calls to mock + assert.NotPanics(t, func() { exporter.Export(ctx, 1) }) + + // zero txdetails - no calls to mock + exporter.Export(ctx, []types.TxDetails{}) +} diff --git a/pkg/monitoring/metrics/common.go b/pkg/monitoring/metrics/common.go index 0de63e97d..5ddf868a7 100644 --- a/pkg/monitoring/metrics/common.go +++ b/pkg/monitoring/metrics/common.go @@ -20,30 +20,30 @@ func newSimpleGauge(log commonMonitoring.Logger, name string) simpleGauge { return simpleGauge{log, name} } -func (sg simpleGauge) set(value float64, labels prometheus.Labels) { +func (sg simpleGauge) run( + f func(*prometheus.GaugeVec), +) { if gauges == nil { sg.log.Fatalw("gauges is nil") return } gauge, ok := gauges[sg.metricName] - if !ok { + if !ok || gauge == nil { sg.log.Errorw("gauge not found", "name", sg.metricName) return } - gauge.With(labels).Set(value) + f(gauge) +} + +func (sg simpleGauge) set(value float64, labels prometheus.Labels) { + sg.run(func(g *prometheus.GaugeVec) { g.With(labels).Set(value) }) } func (sg simpleGauge) delete(labels prometheus.Labels) { - if gauges == nil { - sg.log.Fatalw("gauges is nil") - return - } + sg.run(func(g *prometheus.GaugeVec) { g.Delete(labels) }) +} - gauge, ok := gauges[sg.metricName] - if !ok { - sg.log.Errorw("gauge not found", "name", sg.metricName) - return - } - gauge.Delete(labels) +func (sg simpleGauge) add(value float64, labels prometheus.Labels) { + sg.run(func(g *prometheus.GaugeVec) { g.With(labels).Add(value) }) } diff --git a/pkg/monitoring/metrics/metrics.go b/pkg/monitoring/metrics/metrics.go index e258854bd..b108736eb 100644 --- a/pkg/monitoring/metrics/metrics.go +++ b/pkg/monitoring/metrics/metrics.go @@ -23,6 +23,11 @@ var ( "network_name", } + nodeFeedLabels = append([]string{ + "node_address", + "node_operator", + }, feedLabels...) + nodeLabels = []string{ "account_address", "node_operator", @@ -67,6 +72,14 @@ func init() { ) } + // init gauge for node success per feed per node + gauges[types.NodeSuccessMetric] = promauto.NewGaugeVec( + prometheus.GaugeOpts{ + Name: types.NodeSuccessMetric, + }, + nodeFeedLabels, + ) + // init gauge for slot height gauges[types.SlotHeightMetric] = promauto.NewGaugeVec( prometheus.GaugeOpts{ @@ -93,3 +106,15 @@ func (i FeedInput) ToPromLabels() prometheus.Labels { "network_name": i.NetworkName, } } + +type NodeFeedInput struct { + NodeAddress, NodeOperator string + FeedInput +} + +func (i NodeFeedInput) ToPromLabels() prometheus.Labels { + l := i.FeedInput.ToPromLabels() + l["node_address"] = i.NodeAddress + l["node_operator"] = i.NodeOperator + return l +} diff --git a/pkg/monitoring/metrics/mocks/NodeSuccess.go b/pkg/monitoring/metrics/mocks/NodeSuccess.go new file mode 100644 index 000000000..10c336db1 --- /dev/null +++ b/pkg/monitoring/metrics/mocks/NodeSuccess.go @@ -0,0 +1,38 @@ +// Code generated by mockery v2.20.0. DO NOT EDIT. + +package mocks + +import ( + metrics "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/metrics" + mock "github.com/stretchr/testify/mock" +) + +// NodeSuccess is an autogenerated mock type for the NodeSuccess type +type NodeSuccess struct { + mock.Mock +} + +// Add provides a mock function with given fields: count, i +func (_m *NodeSuccess) Add(count int, i metrics.NodeFeedInput) { + _m.Called(count, i) +} + +// Cleanup provides a mock function with given fields: i +func (_m *NodeSuccess) Cleanup(i metrics.NodeFeedInput) { + _m.Called(i) +} + +type mockConstructorTestingTNewNodeSuccess interface { + mock.TestingT + Cleanup(func()) +} + +// NewNodeSuccess creates a new instance of NodeSuccess. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewNodeSuccess(t mockConstructorTestingTNewNodeSuccess) *NodeSuccess { + mock := &NodeSuccess{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/monitoring/metrics/nodesuccess.go b/pkg/monitoring/metrics/nodesuccess.go new file mode 100644 index 000000000..73cc00d94 --- /dev/null +++ b/pkg/monitoring/metrics/nodesuccess.go @@ -0,0 +1,32 @@ +package metrics + +import ( + commonMonitoring "github.com/smartcontractkit/chainlink-common/pkg/monitoring" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +//go:generate mockery --name NodeSuccess --output ./mocks/ + +type NodeSuccess interface { + Add(count int, i NodeFeedInput) + Cleanup(i NodeFeedInput) +} + +var _ NodeSuccess = (*nodeSuccess)(nil) + +type nodeSuccess struct { + simpleGauge +} + +func NewNodeSuccess(log commonMonitoring.Logger) *nodeSuccess { + return &nodeSuccess{newSimpleGauge(log, types.NodeSuccessMetric)} +} + +func (ro *nodeSuccess) Add(count int, i NodeFeedInput) { + ro.add(float64(count), i.ToPromLabels()) +} + +func (ro *nodeSuccess) Cleanup(i NodeFeedInput) { + ro.delete(i.ToPromLabels()) +} diff --git a/pkg/monitoring/metrics/nodesuccess_test.go b/pkg/monitoring/metrics/nodesuccess_test.go new file mode 100644 index 000000000..44f238fdf --- /dev/null +++ b/pkg/monitoring/metrics/nodesuccess_test.go @@ -0,0 +1,36 @@ +package metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + + "github.com/smartcontractkit/chainlink-solana/pkg/monitoring/types" +) + +func TestNodeSuccess(t *testing.T) { + lgr := logger.Test(t) + m := NewNodeSuccess(lgr) + + // fetching gauges + g, ok := gauges[types.NodeSuccessMetric] + require.True(t, ok) + + v := 100 + inputs := NodeFeedInput{FeedInput: FeedInput{NetworkName: t.Name()}} + + // set gauge + assert.NotPanics(t, func() { m.Add(v, inputs) }) + assert.NotPanics(t, func() { m.Add(v, inputs) }) + promBal := testutil.ToFloat64(g.With(inputs.ToPromLabels())) + assert.Equal(t, float64(2*v), promBal) + + // cleanup gauges + assert.Equal(t, 1, testutil.CollectAndCount(g)) + assert.NotPanics(t, func() { m.Cleanup(inputs) }) + assert.Equal(t, 0, testutil.CollectAndCount(g)) +} diff --git a/pkg/monitoring/testutils/testutils.go b/pkg/monitoring/testutils/testutils.go index d1998fef7..17e46b966 100644 --- a/pkg/monitoring/testutils/testutils.go +++ b/pkg/monitoring/testutils/testutils.go @@ -2,6 +2,7 @@ package testutils import ( "context" + crand "crypto/rand" "fmt" "math" "math/rand" @@ -59,7 +60,7 @@ func GenerateFeedConfig() config.SolanaFeedConfig { func Generate32ByteArr() [32]byte { buf := make([]byte, 32) - _, err := rand.Read(buf) + _, err := crand.Read(buf) if err != nil { panic("unable to Generate [32]byte from rand") } diff --git a/pkg/monitoring/types/txdetails.go b/pkg/monitoring/types/txdetails.go index d26ddd626..f5149677a 100644 --- a/pkg/monitoring/types/txdetails.go +++ b/pkg/monitoring/types/txdetails.go @@ -19,7 +19,9 @@ var ( ReportObservationMetric = "report_observations" TxFeeMetric = "tx_fee" ComputeUnitPriceMetric = "tx_compute_unit_price" + NodeSuccessMetric = "node_success" // per node per feed + // these metrics are per feed TxDetailsMetrics = []string{ ReportObservationMetric, TxFeeMetric, @@ -142,7 +144,7 @@ func ParseTx(tx *solanaGo.Transaction, programAddr solanaGo.PublicKey) (TxDetail } // find compute budget program instruction - if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.COMPUTE_BUDGET_PROGRAM) { + if tx.Message.AccountKeys[instruction.ProgramIDIndex] == solanaGo.MustPublicKeyFromBase58(fees.ComputeBudgetProgram) { // parsing compute unit price var err error txDetails.ComputeUnitPrice, err = fees.ParseComputeUnitPrice(instruction.Data) diff --git a/pkg/solana/cache_test.go b/pkg/solana/cache_test.go index 59e6c7bb6..3351ecf68 100644 --- a/pkg/solana/cache_test.go +++ b/pkg/solana/cache_test.go @@ -23,7 +23,6 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" ) var mockTransmission = []byte{ @@ -94,7 +93,7 @@ func testTransmissionsResponse(t *testing.T, body []byte, sub uint64) []byte { func testSetupReader(t *testing.T, endpoint string) client.Reader { lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() client, err := client.NewClient(endpoint, cfg, 1*time.Second, lggr) require.NoError(t, err) return client @@ -158,19 +157,19 @@ func TestCache(t *testing.T) { // state query if bytes.Contains(body, []byte("11111111111111111111111111111111")) { // Drop error, client may cancel ctx. - w.Write(testStateResponse()) + w.Write(testStateResponse()) //nolint:errcheck return } // transmissions query // Drop error, client may cancel ctx. - w.Write(testTransmissionsResponse(t, body, 0)) + w.Write(testTransmissionsResponse(t, body, 0)) //nolint:errcheck })) lggr := logger.Test(t) stateCache := StateCache{ StateID: solana.MustPublicKeyFromBase58("11111111111111111111111111111111"), - cfg: config.NewConfig(db.ChainCfg{}, lggr), + cfg: config.NewDefault(), reader: testSetupReader(t, mockServer.URL), lggr: lggr, } @@ -182,7 +181,7 @@ func TestCache(t *testing.T) { transmissionsCache := TransmissionsCache{ TransmissionsID: solana.MustPublicKeyFromBase58("11111111111111111111111111111112"), - cfg: config.NewConfig(db.ChainCfg{}, lggr), + cfg: config.NewDefault(), reader: testSetupReader(t, mockServer.URL), lggr: lggr, } diff --git a/pkg/solana/chain.go b/pkg/solana/chain.go index bc69a453e..e62aa4531 100644 --- a/pkg/solana/chain.go +++ b/pkg/solana/chain.go @@ -24,7 +24,6 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" "github.com/smartcontractkit/chainlink-solana/pkg/solana/monitor" "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" ) @@ -65,7 +64,7 @@ func (o *ChainOpts) GetLogger() logger.Logger { return o.Logger } -func NewChain(cfg *TOMLConfig, opts ChainOpts) (Chain, error) { +func NewChain(cfg *config.TOMLConfig, opts ChainOpts) (Chain, error) { if !cfg.IsEnabled() { return nil, fmt.Errorf("cannot create new chain with ID %s: chain is disabled", *cfg.ChainID) } @@ -81,7 +80,7 @@ var _ Chain = (*chain)(nil) type chain struct { services.StateMachine id string - cfg *TOMLConfig + cfg *config.TOMLConfig txm *txm.Txm balanceMonitor services.Service lggr logger.Logger @@ -214,7 +213,7 @@ func (v *verifiedCachedClient) GetAccountInfoWithOpts(ctx context.Context, addr return v.ReaderWriter.GetAccountInfoWithOpts(ctx, addr, opts) } -func newChain(id string, cfg *TOMLConfig, ks loop.Keystore, lggr logger.Logger) (*chain, error) { +func newChain(id string, cfg *config.TOMLConfig, ks loop.Keystore, lggr logger.Logger) (*chain, error) { lggr = logger.With(lggr, "chainID", id, "chain", "solana") var ch = chain{ id: id, @@ -262,7 +261,7 @@ func (c *chain) listNodeStatuses(start, end int) ([]relaytypes.NodeStatus, int, } nodes := c.cfg.Nodes[start:end] for _, node := range nodes { - stat, err := nodeStatus(node, c.ChainID()) + stat, err := config.NodeStatus(node, c.ChainID()) if err != nil { return stats, total, err } @@ -297,12 +296,9 @@ func (c *chain) ChainID() string { // getClient returns a client, randomly selecting one from available and valid nodes func (c *chain) getClient() (client.ReaderWriter, error) { - var node db.Node + var node *config.Node var client client.ReaderWriter - nodes, err := c.cfg.ListNodes() - if err != nil { - return nil, fmt.Errorf("failed to get nodes: %w", err) - } + nodes := c.cfg.ListNodes() if len(nodes) == 0 { return nil, errors.New("no nodes available") } @@ -311,10 +307,11 @@ func (c *chain) getClient() (client.ReaderWriter, error) { for _, i := range index { node = nodes[i] // create client and check + var err error client, err = c.verifiedClient(node) // if error, try another node if err != nil { - c.lggr.Warnw("failed to create node", "name", node.Name, "solana-url", node.SolanaURL, "err", err.Error()) + c.lggr.Warnw("failed to create node", "name", node.Name, "solana-url", node.URL, "err", err.Error()) continue } // if all checks passed, mark found and break loop @@ -324,15 +321,23 @@ func (c *chain) getClient() (client.ReaderWriter, error) { if client == nil { return nil, errors.New("no node valid nodes available") } - c.lggr.Debugw("Created client", "name", node.Name, "solana-url", node.SolanaURL) + c.lggr.Debugw("Created client", "name", node.Name, "solana-url", node.URL) return client, nil } // verifiedClient returns a client for node or an error if fails to create the client. // The client will still be returned if the nodes are not valid, or the chain id doesn't match. // Further client calls will try and verify the client, and fail if the client is still not valid. -func (c *chain) verifiedClient(node db.Node) (client.ReaderWriter, error) { - url := node.SolanaURL +func (c *chain) verifiedClient(node *config.Node) (client.ReaderWriter, error) { + if node == nil { + return nil, fmt.Errorf("nil node") + } + + if node.Name == nil || node.URL == nil { + return nil, fmt.Errorf("node config contains nil: %+v", node) + } + + url := node.URL.String() var err error // check if cached client exists @@ -346,7 +351,7 @@ func (c *chain) verifiedClient(node db.Node) (client.ReaderWriter, error) { expectedChainID: c.id, } // create client - cl.ReaderWriter, err = client.NewClient(url, c.cfg, DefaultRequestTimeout, logger.Named(c.lggr, "Client."+node.Name)) + cl.ReaderWriter, err = client.NewClient(url, c.cfg, DefaultRequestTimeout, logger.Named(c.lggr, "Client."+*node.Name)) if err != nil { return nil, fmt.Errorf("failed to create client: %w", err) } diff --git a/pkg/solana/chain_test.go b/pkg/solana/chain_test.go index f01406e81..aa52b8b4d 100644 --- a/pkg/solana/chain_test.go +++ b/pkg/solana/chain_test.go @@ -10,6 +10,7 @@ import ( "sync" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +19,6 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" ) const TestSolanaGenesisHashTemplate = `{"jsonrpc":"2.0","result":"%s","id":1}` @@ -46,7 +46,7 @@ func TestSolanaChain_GetClient(t *testing.T) { ch := solcfg.Chain{} ch.SetDefaults() - cfg := &TOMLConfig{ + cfg := &solcfg.TOMLConfig{ ChainID: ptr("devnet"), Chain: ch, } @@ -143,7 +143,7 @@ func TestSolanaChain_VerifiedClient(t *testing.T) { ch := solcfg.Chain{} ch.SetDefaults() - cfg := &TOMLConfig{ + cfg := &solcfg.TOMLConfig{ ChainID: ptr("devnet"), Chain: ch, } @@ -152,28 +152,32 @@ func TestSolanaChain_VerifiedClient(t *testing.T) { lggr: logger.Test(t), clientCache: map[string]*verifiedCachedClient{}, } - node := db.Node{SolanaURL: mockServer.URL} + nName := t.Name() + "-" + uuid.NewString() + node := &solcfg.Node{ + Name: &nName, + URL: config.MustParseURL(mockServer.URL), + } // happy path testChain.id = "devnet" _, err := testChain.verifiedClient(node) - assert.NoError(t, err) + require.NoError(t, err) // retrieve cached client and retrieve slot height c, err := testChain.verifiedClient(node) - assert.NoError(t, err) + require.NoError(t, err) slot, err := c.SlotHeight() assert.NoError(t, err) assert.Equal(t, uint64(1234), slot) - node.SolanaURL = mockServer.URL + "/mismatch" + node.URL = config.MustParseURL(mockServer.URL + "/mismatch") testChain.id = "incorrect" c, err = testChain.verifiedClient(node) assert.NoError(t, err) _, err = c.ChainID() // expect error from id mismatch (even if using a cached client) when performing RPC calls assert.Error(t, err) - assert.Equal(t, fmt.Sprintf("client returned mismatched chain id (expected: %s, got: %s): %s", "incorrect", "devnet", node.SolanaURL), err.Error()) + assert.Equal(t, fmt.Sprintf("client returned mismatched chain id (expected: %s, got: %s): %s", "incorrect", "devnet", node.URL), err.Error()) } func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) { @@ -186,7 +190,7 @@ func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) { ch := solcfg.Chain{} ch.SetDefaults() - cfg := &TOMLConfig{ + cfg := &solcfg.TOMLConfig{ ChainID: ptr("devnet"), Enabled: ptr(true), Chain: ch, @@ -197,7 +201,11 @@ func TestSolanaChain_VerifiedClient_ParallelClients(t *testing.T) { lggr: logger.Test(t), clientCache: map[string]*verifiedCachedClient{}, } - node := db.Node{SolanaURL: mockServer.URL} + nName := t.Name() + "-" + uuid.NewString() + node := &solcfg.Node{ + Name: &nName, + URL: config.MustParseURL(mockServer.URL), + } var wg sync.WaitGroup wg.Add(2) diff --git a/pkg/solana/client/client_test.go b/pkg/solana/client/client_test.go index fa19d5c7c..4427330c4 100644 --- a/pkg/solana/client/client_test.go +++ b/pkg/solana/client/client_test.go @@ -19,7 +19,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" ) func TestClient_Reader_Integration(t *testing.T) { @@ -31,7 +30,7 @@ func TestClient_Reader_Integration(t *testing.T) { requestTimeout := 5 * time.Second lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() c, err := NewClient(url, cfg, requestTimeout, lggr) require.NoError(t, err) @@ -112,7 +111,7 @@ func TestClient_Reader_ChainID(t *testing.T) { requestTimeout := 5 * time.Second lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() c, err := NewClient(mockServer.URL, cfg, requestTimeout, lggr) require.NoError(t, err) @@ -133,7 +132,7 @@ func TestClient_Writer_Integration(t *testing.T) { requestTimeout := 5 * time.Second lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() ctx := context.Background() c, err := NewClient(url, cfg, requestTimeout, lggr) @@ -141,10 +140,10 @@ func TestClient_Writer_Integration(t *testing.T) { // create + sign transaction createTx := func(to solana.PublicKey) *solana.Transaction { - hash, err := c.LatestBlockhash() - assert.NoError(t, err) + hash, hashErr := c.LatestBlockhash() + assert.NoError(t, hashErr) - tx, err := solana.NewTransaction( + tx, txErr := solana.NewTransaction( []solana.Instruction{ system.NewTransferInstruction( 1, @@ -155,8 +154,8 @@ func TestClient_Writer_Integration(t *testing.T) { hash.Value.Blockhash, solana.TransactionPayer(pubKey), ) - assert.NoError(t, err) - _, err = tx.Sign( + assert.NoError(t, txErr) + _, signErr := tx.Sign( func(key solana.PublicKey) *solana.PrivateKey { if pubKey.Equals(key) { return &privKey @@ -164,7 +163,7 @@ func TestClient_Writer_Integration(t *testing.T) { return nil }, ) - assert.NoError(t, err) + assert.NoError(t, signErr) return tx } @@ -219,7 +218,7 @@ func TestClient_SendTxDuplicates_Integration(t *testing.T) { // create client requestTimeout := 5 * time.Second lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() c, err := NewClient(url, cfg, requestTimeout, lggr) require.NoError(t, err) @@ -263,8 +262,8 @@ func TestClient_SendTxDuplicates_Integration(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond) // randomly submit txs - sig, err := c.SendTx(ctx, tx) - assert.NoError(t, err) + sig, sendErr := c.SendTx(ctx, tx) + assert.NoError(t, sendErr) sigs[i] = sig wg.Done() }(i) @@ -278,8 +277,8 @@ func TestClient_SendTxDuplicates_Integration(t *testing.T) { // try waiting for tx to execute - reduce flakiness require.Eventually(t, func() bool { - res, err := c.SignatureStatuses(ctx, []solana.Signature{sigs[0]}) - require.NoError(t, err) + res, statusErr := c.SignatureStatuses(ctx, []solana.Signature{sigs[0]}) + require.NoError(t, statusErr) require.Equal(t, 1, len(res)) if res[0] == nil { return false diff --git a/pkg/solana/client/test_helpers_test.go b/pkg/solana/client/test_helpers_test.go index dd9e682a8..1f530da2b 100644 --- a/pkg/solana/client/test_helpers_test.go +++ b/pkg/solana/client/test_helpers_test.go @@ -11,7 +11,6 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" ) func TestSetupLocalSolNode_SimultaneousNetworks(t *testing.T) { @@ -25,7 +24,7 @@ func TestSetupLocalSolNode_SimultaneousNetworks(t *testing.T) { // client configs requestTimeout := 5 * time.Second lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() // check & fund address checkFunded := func(t *testing.T, url string) { diff --git a/pkg/solana/cmd/chainlink-solana/main.go b/pkg/solana/cmd/chainlink-solana/main.go index 22f614a18..e8a211af2 100644 --- a/pkg/solana/cmd/chainlink-solana/main.go +++ b/pkg/solana/cmd/chainlink-solana/main.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/loop" "github.com/smartcontractkit/chainlink-solana/pkg/solana" + solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" ) const ( @@ -53,7 +54,7 @@ func (c *pluginRelayer) NewRelayer(ctx context.Context, config string, keystore d := toml.NewDecoder(strings.NewReader(config)) d.DisallowUnknownFields() var cfg struct { - Solana solana.TOMLConfig + Solana solcfg.TOMLConfig } if err := d.Decode(&cfg); err != nil { diff --git a/pkg/solana/config/config.go b/pkg/solana/config/config.go index 15a96c62d..df5be635f 100644 --- a/pkg/solana/config/config.go +++ b/pkg/solana/config/config.go @@ -2,15 +2,11 @@ package config import ( "errors" - "strings" "time" "github.com/gagliardetto/solana-go/rpc" "github.com/smartcontractkit/chainlink-common/pkg/config" - - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/logger" ) // Global solana defaults. @@ -24,7 +20,7 @@ var defaultConfigSet = configSet{ TxConfirmTimeout: 30 * time.Second, // duration before discarding tx as unconfirmed SkipPreflight: true, // to enable or disable preflight checks Commitment: rpc.CommitmentConfirmed, - MaxRetries: new(uint), // max number of retries, when nil - rpc node will do a reasonable number of retries + MaxRetries: new(uint), // max number of retries (default = *new(uint) = 0). when config.MaxRetries < 0, interpreted as MaxRetries = nil and rpc node will do a reasonable number of retries // fee estimator FeeEstimatorMode: "fixed", @@ -75,172 +71,6 @@ type configSet struct { FeeBumpPeriod time.Duration } -var _ Config = (*cfg)(nil) - -// Deprecated -type cfg struct { - defaults configSet - chain db.ChainCfg - lggr logger.Logger -} - -// NewConfig returns a Config with defaults overridden by dbcfg. -// Deprecated -func NewConfig(dbcfg db.ChainCfg, lggr logger.Logger) *cfg { - return &cfg{ - defaults: defaultConfigSet, - chain: dbcfg, - lggr: lggr, - } -} - -func (c *cfg) BalancePollPeriod() time.Duration { - ch := c.chain.BalancePollPeriod - if ch != nil { - return ch.Duration() - } - return c.defaults.BalancePollPeriod -} - -func (c *cfg) ConfirmPollPeriod() time.Duration { - ch := c.chain.ConfirmPollPeriod - if ch != nil { - return ch.Duration() - } - return c.defaults.ConfirmPollPeriod -} - -func (c *cfg) OCR2CachePollPeriod() time.Duration { - ch := c.chain.OCR2CachePollPeriod - if ch != nil { - return ch.Duration() - } - return c.defaults.OCR2CachePollPeriod -} - -func (c *cfg) OCR2CacheTTL() time.Duration { - ch := c.chain.OCR2CacheTTL - if ch != nil { - return ch.Duration() - } - return c.defaults.OCR2CacheTTL -} - -func (c *cfg) TxTimeout() time.Duration { - ch := c.chain.TxTimeout - if ch != nil { - return ch.Duration() - } - return c.defaults.TxTimeout -} - -func (c *cfg) TxRetryTimeout() time.Duration { - ch := c.chain.TxRetryTimeout - if ch != nil { - return ch.Duration() - } - return c.defaults.TxRetryTimeout -} - -func (c *cfg) TxConfirmTimeout() time.Duration { - ch := c.chain.TxConfirmTimeout - if ch != nil { - return ch.Duration() - } - return c.defaults.TxConfirmTimeout -} - -func (c *cfg) SkipPreflight() bool { - ch := c.chain.SkipPreflight - if ch.Valid { - return ch.Bool - } - return c.defaults.SkipPreflight -} - -func (c *cfg) Commitment() rpc.CommitmentType { - ch := c.chain.Commitment - if ch.Valid { - str := ch.String - var commitment rpc.CommitmentType - switch str { - case "processed": - commitment = rpc.CommitmentProcessed - case "confirmed": - commitment = rpc.CommitmentConfirmed - case "finalized": - commitment = rpc.CommitmentFinalized - default: - c.lggr.Warnf(`Invalid value provided for %s, "%s" - falling back to default "%s"`, "CommitmentType", str, c.defaults.Commitment) - commitment = rpc.CommitmentConfirmed - } - return commitment - } - return c.defaults.Commitment -} - -func (c *cfg) FeeEstimatorMode() string { - ch := c.chain.FeeEstimatorMode - if ch.Valid { - return strings.ToLower(ch.String) - } - return c.defaults.FeeEstimatorMode -} - -func (c *cfg) ComputeUnitPriceMax() uint64 { - ch := c.chain.ComputeUnitPriceMax - if ch.Valid { - if ch.Int64 >= 0 { - return uint64(ch.Int64) - } - c.lggr.Warnf("Negative value provided for ComputeUnitPriceMax, falling back to default: %d", c.defaults.ComputeUnitPriceMax) - } - return c.defaults.ComputeUnitPriceMax -} - -func (c *cfg) ComputeUnitPriceMin() uint64 { - ch := c.chain.ComputeUnitPriceMin - if ch.Valid { - if ch.Int64 >= 0 { - return uint64(ch.Int64) - } - c.lggr.Warnf("Negative value provided for ComputeUnitPriceMin, falling back to default: %d", c.defaults.ComputeUnitPriceMin) - } - return c.defaults.ComputeUnitPriceMin -} - -func (c *cfg) ComputeUnitPriceDefault() uint64 { - ch := c.chain.ComputeUnitPriceDefault - if ch.Valid { - if ch.Int64 >= 0 { - return uint64(ch.Int64) - } - c.lggr.Warnf("Negative value provided for ComputeUnitPriceDefault, falling back to default: %d", c.defaults.ComputeUnitPriceDefault) - } - return c.defaults.ComputeUnitPriceDefault -} - -func (c *cfg) MaxRetries() *uint { - ch := c.chain.MaxRetries - if ch.Valid { - if ch.Int64 < 0 { - c.lggr.Warnf(`Negative value provided for %s: %d, falling back to - let RPC node do a reasonable amount of tries`, "MaxRetries", ch.Int64) - return nil - } - val := uint(ch.Int64) - return &val - } - return c.defaults.MaxRetries -} - -func (c *cfg) FeeBumpPeriod() time.Duration { - ch := c.chain.FeeBumpPeriod - if ch != nil { - return ch.Duration() - } - return c.defaults.FeeBumpPeriod -} - type Chain struct { BalancePollPeriod *config.Duration ConfirmPollPeriod *config.Duration diff --git a/pkg/solana/config.go b/pkg/solana/config/toml.go similarity index 83% rename from pkg/solana/config.go rename to pkg/solana/config/toml.go index 82c463b64..04cf0a08f 100644 --- a/pkg/solana/config.go +++ b/pkg/solana/config/toml.go @@ -1,4 +1,4 @@ -package solana +package config import ( "errors" @@ -12,14 +12,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/config" relaytypes "github.com/smartcontractkit/chainlink-common/pkg/types" - - solcfg "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - soldb "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" ) -// Deprecated: use TOMLConfigs -type SolanaConfigs = TOMLConfigs - type TOMLConfigs []*TOMLConfig func (cs TOMLConfigs) ValidateConfig() (err error) { @@ -76,7 +70,7 @@ func (cs *TOMLConfigs) SetFrom(fs *TOMLConfigs) (err error) { return } -func nodeStatus(n *solcfg.Node, id string) (relaytypes.NodeStatus, error) { +func NodeStatus(n *Node, id string) (relaytypes.NodeStatus, error) { var s relaytypes.NodeStatus s.ChainID = id s.Name = *n.Name @@ -88,13 +82,13 @@ func nodeStatus(n *solcfg.Node, id string) (relaytypes.NodeStatus, error) { return s, nil } -type SolanaNodes []*solcfg.Node +type Nodes []*Node -func (ns *SolanaNodes) SetFrom(fs *SolanaNodes) { +func (ns *Nodes) SetFrom(fs *Nodes) { for _, f := range *fs { if f.Name == nil { *ns = append(*ns, f) - } else if i := slices.IndexFunc(*ns, func(n *solcfg.Node) bool { + } else if i := slices.IndexFunc(*ns, func(n *Node) bool { return n.Name != nil && *n.Name == *f.Name }); i == -1 { *ns = append(*ns, f) @@ -104,7 +98,7 @@ func (ns *SolanaNodes) SetFrom(fs *SolanaNodes) { } } -func setFromNode(n, f *solcfg.Node) { +func setFromNode(n, f *Node) { if f.Name != nil { n.Name = f.Name } @@ -113,23 +107,12 @@ func setFromNode(n, f *solcfg.Node) { } } -func legacySolNode(n *solcfg.Node, id string) soldb.Node { - return soldb.Node{ - Name: *n.Name, - SolanaChainID: id, - SolanaURL: (*url.URL)(n.URL).String(), - } -} - -// Deprecated: use TOMLConfig -type SolanaConfig = TOMLConfig - type TOMLConfig struct { ChainID *string // Do not access directly, use [IsEnabled] Enabled *bool - solcfg.Chain - Nodes SolanaNodes + Chain + Nodes Nodes } func (c *TOMLConfig) IsEnabled() bool { @@ -147,7 +130,7 @@ func (c *TOMLConfig) SetFrom(f *TOMLConfig) { c.Nodes.SetFrom(&f.Nodes) } -func setFromChain(c, f *solcfg.Chain) { +func setFromChain(c, f *Chain) { if f.BalancePollPeriod != nil { c.BalancePollPeriod = f.BalancePollPeriod } @@ -178,6 +161,21 @@ func setFromChain(c, f *solcfg.Chain) { if f.MaxRetries != nil { c.MaxRetries = f.MaxRetries } + if f.FeeEstimatorMode != nil { + c.FeeEstimatorMode = f.FeeEstimatorMode + } + if f.ComputeUnitPriceMax != nil { + c.ComputeUnitPriceMax = f.ComputeUnitPriceMax + } + if f.ComputeUnitPriceMin != nil { + c.ComputeUnitPriceMin = f.ComputeUnitPriceMin + } + if f.ComputeUnitPriceDefault != nil { + c.ComputeUnitPriceDefault = f.ComputeUnitPriceDefault + } + if f.FeeBumpPeriod != nil { + c.FeeBumpPeriod = f.FeeBumpPeriod + } } func (c *TOMLConfig) ValidateConfig() (err error) { @@ -201,7 +199,7 @@ func (c *TOMLConfig) TOMLString() (string, error) { return string(b), nil } -var _ solcfg.Config = &TOMLConfig{} +var _ Config = &TOMLConfig{} func (c *TOMLConfig) BalancePollPeriod() time.Duration { return c.Chain.BalancePollPeriod.Duration() @@ -243,6 +241,9 @@ func (c *TOMLConfig) MaxRetries() *uint { if c.Chain.MaxRetries == nil { return nil } + if *c.Chain.MaxRetries < 0 { + return nil // interpret negative numbers as nil (prevents unlikely case of overflow) + } mr := uint(*c.Chain.MaxRetries) return &mr } @@ -267,10 +268,12 @@ func (c *TOMLConfig) FeeBumpPeriod() time.Duration { return c.Chain.FeeBumpPeriod.Duration() } -func (c *TOMLConfig) ListNodes() ([]soldb.Node, error) { - var allNodes []soldb.Node - for _, n := range c.Nodes { - allNodes = append(allNodes, legacySolNode(n, *c.ChainID)) - } - return allNodes, nil +func (c *TOMLConfig) ListNodes() Nodes { + return c.Nodes +} + +func NewDefault() *TOMLConfig { + cfg := &TOMLConfig{} + cfg.SetDefaults() + return cfg } diff --git a/pkg/solana/db/db.go b/pkg/solana/db/db.go deleted file mode 100644 index 30c3e6a1a..000000000 --- a/pkg/solana/db/db.go +++ /dev/null @@ -1,54 +0,0 @@ -package db - -import ( - "database/sql/driver" - "encoding/json" - "errors" - "time" - - "gopkg.in/guregu/null.v4" - - "github.com/smartcontractkit/chainlink-common/pkg/config" -) - -type Node struct { - ID int32 - Name string - SolanaChainID string `json:"solanaChainId" db:"solana_chain_id"` - SolanaURL string `json:"solanaURL" db:"solana_url"` - CreatedAt time.Time - UpdatedAt time.Time -} - -// Deprecated -type ChainCfg struct { - BalancePollPeriod *config.Duration - ConfirmPollPeriod *config.Duration - OCR2CachePollPeriod *config.Duration - OCR2CacheTTL *config.Duration - TxTimeout *config.Duration - TxRetryTimeout *config.Duration - TxConfirmTimeout *config.Duration - SkipPreflight null.Bool // to enable or disable preflight checks - Commitment null.String - MaxRetries null.Int - - FeeEstimatorMode null.String - ComputeUnitPriceMax null.Int - ComputeUnitPriceMin null.Int - ComputeUnitPriceDefault null.Int - FeeBumpPeriod *config.Duration -} - -func (c *ChainCfg) Scan(value interface{}) error { - b, ok := value.([]byte) - if !ok { - return errors.New("type assertion to []byte failed") - } - - return json.Unmarshal(b, c) -} - -func (c *ChainCfg) Value() (driver.Value, error) { - return json.Marshal(c) -} diff --git a/pkg/solana/fees/computebudget.go b/pkg/solana/fees/computebudget.go index ba980f0a2..fec4a4442 100644 --- a/pkg/solana/fees/computebudget.go +++ b/pkg/solana/fees/computebudget.go @@ -11,27 +11,27 @@ import ( // https://github.com/solana-labs/solana/blob/60858d043ca612334de300805d93ea3014e8ab37/sdk/src/compute_budget.rs#L25 const ( // deprecated: will not support for building instruction - Instruction_RequestUnitsDeprecated uint8 = iota + InstructionRequestUnitsDeprecated uint8 = iota // Request a specific transaction-wide program heap region size in bytes. // The value requested must be a multiple of 1024. This new heap region // size applies to each program executed in the transaction, including all // calls to CPIs. // note: uses ag_binary.Varuint32 - Instruction_RequestHeapFrame + InstructionRequestHeapFrame // Set a specific compute unit limit that the transaction is allowed to consume. // note: uses ag_binary.Varuint32 - Instruction_SetComputeUnitLimit + InstructionSetComputeUnitLimit // Set a compute unit price in "micro-lamports" to pay a higher transaction // fee for higher transaction prioritization. // note: uses ag_binary.Uint64 - Instruction_SetComputeUnitPrice + InstructionSetComputeUnitPrice ) const ( - COMPUTE_BUDGET_PROGRAM = "ComputeBudget111111111111111111111111111111" + ComputeBudgetProgram = "ComputeBudget111111111111111111111111111111" ) // https://docs.solana.com/developing/programming-model/runtime @@ -39,7 +39,7 @@ type ComputeUnitPrice uint64 // returns the compute budget program func (val ComputeUnitPrice) ProgramID() solana.PublicKey { - return solana.MustPublicKeyFromBase58(COMPUTE_BUDGET_PROGRAM) + return solana.MustPublicKeyFromBase58(ComputeBudgetProgram) } // No accounts needed @@ -52,7 +52,7 @@ func (val ComputeUnitPrice) Data() ([]byte, error) { buf := new(bytes.Buffer) // encode method identifier - if err := buf.WriteByte(Instruction_SetComputeUnitPrice); err != nil { + if err := buf.WriteByte(InstructionSetComputeUnitPrice); err != nil { return []byte{}, err } @@ -69,7 +69,7 @@ func ParseComputeUnitPrice(data []byte) (ComputeUnitPrice, error) { return 0, fmt.Errorf("invalid length: %d", len(data)) } - if data[0] != Instruction_SetComputeUnitPrice { + if data[0] != InstructionSetComputeUnitPrice { return 0, fmt.Errorf("not SetComputeUnitPrice identifier: %d", data[0]) } @@ -117,7 +117,7 @@ func SetComputeUnitPrice(tx *solana.Transaction, price ComputeUnitPrice) error { for i := range tx.Message.Instructions { if tx.Message.Instructions[i].ProgramIDIndex == programIdx && len(tx.Message.Instructions[i].Data) > 0 && - tx.Message.Instructions[i].Data[0] == Instruction_SetComputeUnitPrice { + tx.Message.Instructions[i].Data[0] == InstructionSetComputeUnitPrice { found = true instructionIdx = i break diff --git a/pkg/solana/fees/computebudget_test.go b/pkg/solana/fees/computebudget_test.go index 64ce7fdce..c80550297 100644 --- a/pkg/solana/fees/computebudget_test.go +++ b/pkg/solana/fees/computebudget_test.go @@ -32,7 +32,7 @@ func TestSetComputeUnitPrice(t *testing.T) { currentCount := len(tx.Message.Instructions) assert.Greater(t, currentCount, instructionCount) assert.Equal(t, 2, currentCount) - assert.Equal(t, COMPUTE_BUDGET_PROGRAM, tx.Message.AccountKeys[tx.Message.Instructions[0].ProgramIDIndex].String()) + assert.Equal(t, ComputeBudgetProgram, tx.Message.AccountKeys[tx.Message.Instructions[0].ProgramIDIndex].String()) data, err := ComputeUnitPrice(1).Data() assert.NoError(t, err) assert.Equal(t, data, []byte(tx.Message.Instructions[0].Data)) @@ -58,7 +58,7 @@ func TestSetComputeUnitPrice(t *testing.T) { // accounts should not have changed assert.Equal(t, accountCount, len(tx.Message.AccountKeys)) assert.Equal(t, 2, len(tx.Message.Instructions)) - assert.Equal(t, COMPUTE_BUDGET_PROGRAM, tx.Message.AccountKeys[tx.Message.Instructions[0].ProgramIDIndex].String()) + assert.Equal(t, ComputeBudgetProgram, tx.Message.AccountKeys[tx.Message.Instructions[0].ProgramIDIndex].String()) data, err := ComputeUnitPrice(1).Data() assert.NoError(t, err) assert.Equal(t, data, []byte(tx.Message.Instructions[0].Data)) @@ -115,7 +115,7 @@ func TestParseComputeUnitPrice(t *testing.T) { assert.ErrorContains(t, err, "invalid length") invalidData := data - invalidData[0] = Instruction_RequestHeapFrame + invalidData[0] = InstructionRequestHeapFrame _, err = ParseComputeUnitPrice(invalidData) assert.ErrorContains(t, err, "not SetComputeUnitPrice identifier") } diff --git a/pkg/solana/relay.go b/pkg/solana/relay.go index 8b5df44ad..68652fcd2 100644 --- a/pkg/solana/relay.go +++ b/pkg/solana/relay.go @@ -26,7 +26,7 @@ type TxManager interface { Enqueue(accountID string, msg *solana.Transaction) error } -var _ relaytypes.Relayer = &Relayer{} +var _ relaytypes.Relayer = &Relayer{} //nolint:staticcheck type Relayer struct { lggr logger.Logger diff --git a/pkg/solana/txm/txm_internal_test.go b/pkg/solana/txm/txm_internal_test.go index 267dcff29..7b165621c 100644 --- a/pkg/solana/txm/txm_internal_test.go +++ b/pkg/solana/txm/txm_internal_test.go @@ -23,7 +23,6 @@ import ( "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/client/mocks" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" "github.com/smartcontractkit/chainlink-solana/pkg/solana/fees" keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" @@ -99,7 +98,6 @@ func TestTxm(t *testing.T) { t.Logf("Starting new iteration: %s", id) ctx := tests.Context(t) - lggr := logger.Test(t) cfg := config.NewConfig(db.ChainCfg{ FeeEstimatorMode: null.StringFrom(estimator), @@ -589,7 +587,7 @@ func TestTxm(t *testing.T) { func TestTxm_Enqueue(t *testing.T) { // set up configs needed in txm lggr := logger.Test(t) - cfg := config.NewConfig(db.ChainCfg{}, lggr) + cfg := config.NewDefault() mc := mocks.NewReaderWriter(t) // mock solana keystore diff --git a/pkg/solana/txm/txm_test.go b/pkg/solana/txm/txm_test.go index 97e6c2fc7..f8ae5af3c 100644 --- a/pkg/solana/txm/txm_test.go +++ b/pkg/solana/txm/txm_test.go @@ -16,7 +16,6 @@ import ( solanaClient "github.com/smartcontractkit/chainlink-solana/pkg/solana/client" "github.com/smartcontractkit/chainlink-solana/pkg/solana/config" - "github.com/smartcontractkit/chainlink-solana/pkg/solana/db" "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm" keyMocks "github.com/smartcontractkit/chainlink-solana/pkg/solana/txm/mocks" 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 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 .. diff --git a/shell.nix b/shell.nix index 83369d06f..f00b6b6ee 100644 --- a/shell.nix +++ b/shell.nix @@ -3,7 +3,7 @@ pkgs.mkShell { nativeBuildInputs = with pkgs; [ (rust-bin.stable.latest.default.override { extensions = ["rust-src"]; }) - lld_10 + # lld_10 llvm_11 stdenv.cc.cc.lib pkg-config @@ -16,7 +16,7 @@ pkgs.mkShell { # Golang # Keep this golang version in sync with the version in .tool-versions please - go_1_20 + go_1_21 gopls delve golangci-lint @@ -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 + ''; }